Getting the Most Out of the Global Catalog
The GC can be thought of as a small database within AD that is replicated around the whole forest. The GC holds partial information about the objects within the AD environment, and it is what is used when a user from one region travels to another and logs onto their home domain from the region they have travelled to.
The GC is a very useful tool when trying to get information about the objects in AD when they are disbursed around the world.
Listing 1 shows you how to connect to AD via ADO, and how to query the GC for a user's details.
Listing 1Querying Our GC for User Information
Private Sub QueryGC() 'General strings and integers Dim strConn As String Dim strGCQuery As String Dim strADRoot As String Dim strUserAccount As String Dim i As Integer 'ADO objects Dim objCmd As ADODB.Command Dim objRs As ADODB.Recordset
Set objCmd = New ADODB.Command Set objRs = New ADODB.Recordset 'A user name and password can be specified in the connection string 'But when reading from the AD any domain user can read strConn = "Provider=ADsDSOObject;" & _ "App=HGI-AMPAM;" 'Just subsititue these variable with your domain and username you want to find... strADRoot = "root.mydomain.com" strUserAccount = "rhawthorne" 'Build the Global Catalog query string (notice the GC prefix)... strGCQuery = "<GC://" & strADRoot & ">;(&(objectClass=user)" & _ "(sAMAccountName=" & strUserAccount & "));" & _ "AdsPath,objectGUID;subtree" With objCmd .CommandTimeout = 60 .ActiveConnection = strConn 'Notice that we cannot specify the type of query like we would if it was 'a query against a SQL DB .CommandType = adCmdUnknown .CommandText = strGCQuery End With With objRs .CursorLocation = adUseClient .CursorType = adOpenStatic .LockType = adLockReadOnly .Open objCmd End With If Not objRs.EOF Then While Not objRs.EOF For i = 0 To objRs.Fields.Count - 1 Debug.Print objRs.Fields(i).Name & " :: " & objRs.Fields(i).Value Next i objRs.MoveNext Wend End If 'Be good and clean up your objects now!!! objRs.Close Set objCmd = Nothing Set objRs = Nothing End Sub
For any of you who have done LDAP queries before, this will not look too dissimilar. However, there are some special attributes that we request back: objectGUID and ADsPath. With these two attributes, we can retrieve the relative path of the user (ADsPath); that is, where the user exists within AD and the GUID of the user.
Nine times out of 10, this code will do exactly what you need. However, what you need to be careful about is where you specify the username. Notice we have not specified the domain that the user resides in. This means that if we have two (or more) rhawthornes in different domains, we will return more than one record in our recordset.
The million dollar question is, "Why don't you just specify the domain that the user resides in?" The answer, my reader? You can't! Yes, I was as shocked as you are, but after numerous calls to Microsoft PSS, it was discovered that you can't specify the domain that you want a user from in the query string in GC queries to the AD. Instead, you have to get back the whole recordset and then iterate through the records to get the user that you want from a specific domain. The following code snippet is one I use to get the correct user's domain information from the ADsPath.
Public Function GetDomain(ByVal strDomainName As String) As String Dim strDomain As String Dim strSearch As String Dim strEndString As String Dim intStart As Integer Dim intEnd As Integer 'Look for the occurrence of the string DC separated by a comma strSearch = "DC=" strEndString = "," If InStr(strDomainName, strSearch) <> 0 Then intStart = InStr(strDomainName, strSearch) intEnd = InStr(intStart, strDomainName, strEndString) strDomain = Mid(strDomainName, intStart + Len(strSearch), intEnd - (intStart + Len(strSearch))) End If GetDomain = strDomain End Function
The ADsPath of a user comes in the format DC= root,DC=mydomain,DC=com. This code assumes that the first occurrence of the DC= is the user's domain (which, by the way, it always is!). AD always build a path from the lowest attribute to the highest: Username, Organizational Unit, User Domain, Parent Domain(s), and Parent Domain suffix.
One last thing: The objectGUID is not a string, or in fact even a number. It is a byte-array, and as such needs to be converted into a string so that you can read it. The following code snippet will convert the byte-array into a string so that you can read it.
Public Function ParseGUID(ByVal varObjectGUID As Variant) As String Dim i As Integer Dim strGUID As String 'Just reset the variable strGUID to ensure that we do not get invalid GUIDs strGUID = "" For i = 0 To UBound(varObjectGUID) If Len(Hex(Trim(varObjectGUID(i)))) < 2 Then strGUID = strGUID & "0" & Hex(Trim$(varObjectGUID(i))) Else strGUID = strGUID & Hex(Trim$(varObjectGUID(i))) End If Next i ParseGUID = LCase$(Trim$(strGUID)) End Function
You might have noticed the little "hack" that needed to be implemented to ensure that the GUID is not invalid. When you use the HEX function to convert "01" from a byte array, it returns "1". This means that when you look for a user in the AD with a GUID, it won't find them! You need to "pad" the converted value (if its length is less than 2) with a "0" to ensure that you do not lose any values when doing the conversion.