In every script or application, the programmer needs to select an object and perform some action over it. To implement these general tasks, ADSI provides operations of the following types.
Binding to an object and authenticating in Active Directory. Before any operation begins, the programmer selects an object, or binds to the object using either current user credentials (recommended choice) or the specified (alternative) ones.
Accessing in the object's attributes. Depending on the ADSI object's declaration and methods implemented by the object, you can retrieve different values of an object's attributes.
Enumerating objects. There are a few programming methods that allow you to obtain a set (group) of objects (users, computers, groups, etc.) - or to enumerate objects - selected on a specific criterion. You will not know the number of objects beforehand. (You can see a few examples of enumerating in the next chapter.) It is possible to enumerate:
Child objects in a container (Listings 17.6 and 17.7)
All objects of a specific type in a container (this is also called filtering)
Members of a group (section "Manipulating Group Memberships")
Manipulating objects. An object can be created, moved to another container or domain, or deleted.
Querying a Directory. ADSI provides search access via the ADO read-only interface.
Managing security descriptors. ADSI can manipulate permissions on various objects, including Active Directory objects, files, etc.
Managing the schema. The schema can be changed or extended via ADSI. Schema extension is the process of adding new attributes or classes to the schema.
The next chapter contains examples that illustrate all the listed operations.
The Active Directory objects have three types of properties that are stored and accessed differently. Beginners sometimes ignore this fact, which often results in program errors.
Most properties are stored in Active Directory (such as cn, description, objectGUID, etc.) and replicated between all domain controllers within a domain. Some of them are indexed, and some are replicated to Global Catalog. (Including an indexed property in a query improves the performance of the query.)
To read these properties, use the obj.Get method or the methods of IADsProperty* interfaces (IADsPropertyEntry, etc.). Use the obj.Put method for updating these properties.
To read and set multi-valued properties, use the obj.GetEx and obj.PutEx methods, respectively.
Some properties (lastLogon, lastLogoff, badPwdCount, etc.) are not replicated between domain controllers. To determine the last logon time of a user, you need to read the lastLogon property for the user object on every domain controller in the domain, and compare the values. These properties can be retrieved the way they usually would be.
There are properties (such as primaryGroupToken, distinguishedName, canonicalName, etc.) that Active Directory uses for its own administrative purposes. These properties are not stored in the directory, but calculated by a domain controller. (Note that the primaryGroup Token or distinguishedName attributes are not even defined as optional attributes of the user class.) These properties require the obj.GetInfoEx method to retrieve them (use the obj.PutInfoEx method for updating such properties), for example:
obj.GetInfoEx Array ("canonicalName"), 0 WScript.Echo obj.Get("canonicalName") 'for a single value property
ADSI provides search operations via ActiveX Data Objects (ADO). There are two syntaxes used by ADSI in query statements.
LDAP dialect consists of the base DN, search filter (according to RFC 2254), list of attributes, and search scope:
SQL dialect is similar to the SELECT statement from standard SQL language. The following search string performs the same operation as the preceding string, but in addition, produces a sorted result:
SELECT ALL ADsPath FROM 'LDAP://DC=net, DC=dom' WHERE objectCategory='group' ORDER BY name
ADSI provides read-only access to OLE DB interfaces. If you need to change a found object after a search operation, bind directly to that object and modify its attributes.
The following program finds all groups in the domain and displays their ADsPath. Both LDAP and SQL dialects can be used in this example. The program also illustrates how to navigate the resulting record set.
Listing 16.1. ADOQuery.bas— Searching for Groups in an Entire Domain (net.dom)
Option Explicit Sub Main() Dim objConnection As Connection Dim objCommand As Command Dim objRSet As Recordset Dim i As Integer ' Open a connection: Set objConnection = CreateObject ("ADODB.Connection") objConnection.Provider = "ADsDSOObject" ' Prepare the command: Set objCommand = CreateObject ("ADODB.Command") objConnection.Open "Active Directory Provider" Set objCommand.ActiveConnection = objConnection ' To find all groups (or any objects) in the forest, you must specify ' "GC:" instead of "LDAP:" for both dialects, for example: ' <GC://DC=net, DC=dom... ' The LDAP dialect: objCommand.CommandText = _ "<LDAP://DC=net, DC=dom>; (objectCategory=group); ADsPath" ' The search scope could be defined directly in the command string: ' objCommand.CommandText = _ ' "<LDAP://DC=net, DC=dom'; (objectCategory=group) ; ADsPath;subtree" ' The SQL dialect allows you to sort (notice the ORDER BY clause) ' the record set by some attribute: ' objCommand.CommandText = "SELECT ALL ADsPath" + _ ' "FROM 'LDAP://DC=net, DC=dom' WHERE objectCategory='group'" + _ ' " ORDER BY name" 'The search scope can be defined here or directly in the string 'assigned to the CommandText property (above): objCommand.Properties("SearchScope") = ADS_SCOPE_SUBTREE '=2 'This constant is defined in the ADS_SCOPEENUM enumeration objCommand.Properties ("Page Size") = 1000 ' Many other command properties can be set here. For details, search ' for the "Searching Properties" string in the ADSI SDK documentation: ' objCommand.Properties ("Timeout") = 30 'seconds ' objCommand.Properties ("Chase referrals") = 0x20 '=ADS_CHASE_REFERRALS_SUBORDINATE - chase only referrals ' which are subordinate naming contexts ' Execute the prepared query: Set objRSet = objCommand.Execute Debug.Print "Ready... (Total) " + CStr (objRSet.RecordCount) If objRSet.RecordCount = 0 Then Debug.Print "No records found!" Exit Sub End If ' Display the record set objRSet.MoveFirst While Not objRSet.EOF ' The following lines must be modified to accept ' multi-valued properties! For i = 0 To objRSet.Fields.Count - 1 Debug.Print objRSet.Fields(i).Name + " = " + _ objRSet.Fields(i).Value Next objRSet.MoveNext Wend Set objConnection = Nothing Set objCommand = Nothing Set objRSet = Nothing Debug.Print "End." End Sub
To compile the shown program, do not forget to add the reference to the Microsoft ActiveX Data Objects Library (besides common for all ADSI projects reference to the Active DS Type Library) to a VB project.
The number of rows returned from a normal (non-paged) search operation is limited by the MaxPageSize parameter (by default, 1,000) of Default Query Policy (see Index for details). (This is also applicable to other search tools, such as Ldp.exe or Search.vbs.) If you explicitly specify the Page Size parameter in your script or program, the paged search is performed, and you can retrieve any number of rows.