In "Easy Active Directory Scripting for Systems Administrators, Part 1," September 2000, I discussed that you need only 3 of the more than 50 Active Directory Scripting Interfaces (ADSI) to accomplish 80 percent of your Active Directory (AD) scripting tasks. Part 1 examined one of the three core interfaces, IADsOpenDSObject. You'll recall that the IADsOpenDSObject interface exposes the OpenDSObject method that you use to authenticate your connection to AD. Part 1 also included an overview of AD and ADSI terminologies and distinguished names (DNs). Part 2 covers the remaining core interfaces—IADs and IADsContainer.
The Keys to ADSI Scripting
Before we examine the IADs and IADsContainer interfaces in detail, let me emphasize their importance. You can't accomplish even simple ADSI scripting tasks without encountering one or both of these interfaces. You use the properties and methods that IADs and IADsContainer provide to create, delete, and modify almost every AD object. Therefore, I encourage you to commit the two interfaces to memory if you're serious about ADSI scripting. These interfaces involve only half a dozen interface properties and about a dozen methods, so the task isn't as overwhelming as you might think.
The IADs Interface
IADs is the identity interface, and every AD object implements IADs. IADs exposes six properties and seven methods. The six IADs properties that Table 1, page 174, lists provide important identity information about an object.
Let's examine a few scenarios in which you might use the IADs properties. Suppose you want to use the ADSI OLEDB/ADO provider and query AD, then subsequently modify the objects returned in the query results. However, the record set that the ADSI OLEDB/ADO provider returns is read-only, so you need to bind to each object in the query results before you can modify the object. To do so, you must have a valid ADsPath to perform the bind operation. Because every object exposes its ADsPath through the IADs interface, you can identify ADsPath as one of the values to return in the query results. Returning the ADsPath is a best practice to follow whenever you query AD.
You can use the Class property to determine an object's schema class (e.g., computer, organizational unit—OU, group, user), which is useful for filtering operations. For example, if you enumerate nested groups, you can check the Class property inside a loop to determine when you encounter a group versus a user and respond accordingly.
The globally unique ID (GUID) is the only object property that never changes. You use the GUID property to bind to an object in a way that is protected against move and rename operations. The Name property is the object's relative distinguished name (RDN); it represents the object's primary name and uniquely identifies an object in its current container. You can use the Parent property with the ADsPath and Schema properties to walk the entire Directory Information Tree (DIT).
You use the Schema property to create a reference to an object's schema class to determine an object's mandatory and optional properties, as Listing 1, page 174, shows. (You can download complete listings from Windows 2000 Magazine's Web site at http://www.win2000mag.com/. Enter 15734 in the InstantDoc ID text box, and click the 15734.zip file.) After binding to the target object, I echo the values of the six IADs properties at callout A in Listing 1. At callout B in Listing 1, I use the path that the object's Schema property provides to bind to the object's schema class. In return, I get a reference to the schema's user class definition based on the object type that I connected to in the first line of Listing 1. After I get the reference, I simply echo the contents of the user class attributes mustContain and mayContain. Figure 1, page 175, shows a portion of Listing 1's output.
The seven IADs methods shown in Table 2, page 175, provide the mechanisms that you use to add, modify, and delete an object's data. Although the IADs methods are easy to use, some of the usage scenarios can be tricky. However, before I discuss those scenarios, you need to understand the ADSI property cache.
The ADSI property cache is a local memory buffer that temporarily stores attribute values while your script is creating, modifying, or reading them. The purpose of the cache is to minimize the impact on the network by reducing the number of network round trips that might otherwise occur when you manage a large number of objects and attributes. The cache batches multiple read and write operations into one or a small number of network transactions to accomplish its goal. This process ensures that the majority of your scripts' read and write operations are carried out on the client workstation (i.e., the workstation on which your script is running).
The property cache is a per-object address space that ADSI allocates when you bind to an object. However, the cache is initially empty. You use IADs methods to initialize and interact with the values in the local property cache and the underlying directory, as Figure 2, page 176, shows.
Of the seven IADs methods, only GetInfo, GetInfoEx, and SetInfo explicitly interact with the underlying directory. Get, GetEx, Put, and PutEx interact only with values in the property cache, with one exception that I discuss later. The process you use to work with an object's data and the property cache largely depends on the task at hand. For example, if you want to create an object and initialize its properties, perform the following four steps:
- Use VBScript's GetObject function to bind to the target container that will hold the new object (network traffic).
- Use the IADsContainer Create method to create the new object in the local property cache (no network traffic).
- Use the IADs Put or PutEx methods, or both, to set the new object's mandatory and optional properties in the local property cache (no network traffic).
- Use the IADs SetInfo method to commit (write) the new object and corresponding properties in the property cache to the directory (network traffic).
If you want to read and modify an existing object's properties, perform the next six steps:
- Use VBScript's GetObject function to bind to the target object (network traffic).
- Use the IADs GetInfo or GetInfoEx method to retrieve the object's properties from the underlying directory to prime (initialize) the local property cache (network traffic).
- Use the IADs Get or GetEx methods, or both, to read the desired properties in the local property cache (no network traffic).
- Use the IADs Put or PutEx methods, or both, to modify the desired properties in the local property cache (no network traffic).
- Use the IADs SetInfo method to commit (write) the modified properties in the property cache to the directory (network traffic).
- Use the IADs GetInfo or GetInfoEx method to reinitialize the local property cache, thereby reflecting the changes you made in Step 5 (network traffic).
Let's apply your newfound knowledge to a modified version of the easyadsi.vbs script that I introduced in Part 1. I use the code in Listing 2, page 176, as the basis for the discussion that follows. Rather than walking through the script line-by-line, I explain each IADs method, then identify an example of the method in Listing 2. However, I don't use all the available IADs methods because Listing 2 is what you might use in the real world (in which you rarely use several of the IADs methods).
GetInfo. GetInfo retrieves an object's properties from AD and loads the property values into the local property cache to initialize it. GetInfo loads into the cache only those properties that contain values.
Listing 2 doesn't use the GetInfo method for two primary reasons. First, I create a new user object at callout A in Listing 2, so I wouldn't invoke GetInfo on an object that doesn't yet exist. And although I update several existing properties at callout B in Listing 2, I'm not concerned with the previous property values. You don't need to initialize the cache with existing values if you plan to overwrite the values (unless you want to save the old values for some reason, such as for change management).
The only case for which you can justify a call to GetInfo in Listing 2 is if you want to verify the new values set at callout B. To do so, call GetInfo after calling SetInfo to refresh the values in the cache. If you call GetInfo before SetInfo (and after the Put statements), GetInfo overwrites the cached values (which you used Put and PutEx to modify) with the original or old values in the directory, and your changes are lost.
Although using GetInfo is a valid way to refresh the cache, the method isn't the most efficient one. For example, I update three properties at callout B in Listing 2. Calling GetInfo after callout B retrieves all values for the more than 50 properties set in callout A in Listing 2. Using GetInfoEx instead of GetInfo would be a more efficient approach and use of your network.
GetInfoEx. GetInfoEx also initializes the local property cache by retrieving an object's properties from AD and loading the property values into the cache. However, GetInfoEx loads only those properties listed in the property array, which you provide as the first parameter to GetInfoEx. GetInfoEx is therefore a more efficient method of refreshing updated values. However, as is the case with GetInfo, be sure to call SetInfo before you call GetInfoEx with the name of an updated property.
GetInfoEx is also a more efficient method than GetInfo for checking an existing value. For example, if you want to verify the value of one property in AD, use GetInfoEx. Rather than explicitly calling GetInfo or using Get to implicitly call GetInfo (which I discuss later in the article), pass GetInfoEx an array that contains one element that identifies the property you want to check.
Another situation that calls for GetInfoEx results from ADSI not loading operational attributes (e.g., canonicalName) into the cache. You must use GetInfoEx to explicitly request an operational attribute before you read it, as the code in Figure 3, page 180, shows. Otherwise, you'll receive the error message The Active Directory property cannot be found in the cache.
Get and GetEx. Get and GetEx provide similar functions in that both read specified values from the local property cache. Both methods also invoke an implicit GetInfo call when the property cache isn't initialized. You should therefore use GetInfoEx to minimize network traffic if you're interested only in retrieving a subset of values. After the property cache is initialized either explicitly or implicitly, you can't use Get or GetEx to refresh the cache.
Get returns single-value properties as scalar variants and multivalue properties as variant arrays. GetEx returns all property values as variant arrays, so it returns a single-value property in a single-element array.
Using Get is slightly more efficient than using GetEx if you know whether the target property is a single-value or multivalue property. However, using Get can be tricky when you're not certain about the value because you'll need to check Get's return type to ensure you dereference the data correctly. Otherwise, your script will encounter a runtime error if you handle a scalar as an array, or vice versa. To complicate matters further, Get returns a multivalue attribute that contains a single value as a scalar variant and not as a single-element array. Given Get's caveats, GetEx can be slightly easier to use because you can use the same approach to handle all property data. You don't need to check the return type because GetEx returns all data in an array.
The code in Listing 2 doesn't call Get or GetEx because these methods aren't necessary to the script's purpose and because the script doesn't produce console or file output. If I want to echo the property values, I need to include the appropriate output statement followed by the same object.propertymethod syntax that I used to set the property values at callout A in Listing 2. The code that Figure 3 shows provides an example of the object.propertymethod syntax that you use in conjunction with the Wscript .Echo statement.
Put and PutEx. You use the Put method to write single-value properties to the property cache and PutEx to append, modify, replace, and clear multivalue properties in the property cache. Put and PutEx write or modify values in the property cache only. You must call SetInfo to write the changes in the cache to AD. Although you can use Put to modify multivalue properties, the method doesn't give you any control over the properties' existing data. Put will blindly replace all existing multivalue data in the same way that it replaces a single-value property. You use PutEx in situations in which you need to preserve existing data in a multivalue property.
As Table 2 shows, PutEx uses a control code to communicate the type of update to perform. ADS_PROPERTY_ APPEND appends a new value while preserving existing values. ADS_PROPERTY_ DELETE deletes a specified value and leaves other values intact. Similarly to Put, ADS_PROPERTY_UPDATE replaces all existing values with new values, and ADS_PROPERTY_CLEAR removes all values to clear a property.
Callout B in Listing 2 demonstrates the use of Put and PutEx. In the case of PutEx, I append a new value to the otherHomePhone attribute. (Note that you have no control over the order of values in multivalue properties.)
SetInfo. SetInfo writes new or modified property values from the cache to AD. SetInfo is always an explicit call; when the property cache isn't initialized, SetInfo is never implicitly invoked the same way that Get or GetEx invokes GetInfo. Any property value changes that you use Put or PutEx to make are lost if you invoke GetInfo or possibly GetInfoEx before calling SetInfo. You can use one SetInfo call to batch multiple changes, but if you do so, you need to understand that SetInfo is an all-or-nothing operation. That is, SetInfo writes all changes leading up to the SetInfo call to the directory, or makes no changes if an error occurs (e.g., you encounter a constraint violation, you run into a security concern).
The object.propertymethod syntax convention. The object.propertymethod syntax is a convention that automation clients can use instead of Get and Put. The syntax lets Visual Basic (VB) and VBScript programmers use the familiar dotMethodOrPropertyName notation. The syntax has two parts separated by a dot (.). The name to the left of the dot is the object reference; the name to the right of the dot is the attribute's Lightweight Directory Access Protocol (LDAP) display name to retrieve or write in the cache. Using the object.propertymethod syntax on an assignment operator's right side or in an expression results in a Get. Using the syntax on an assignment operator's left side results in a Put. The object.propertymethod syntax's behavior is identical to that of Get and Put (i.e., Get's return types and Put's limitation in updating multivalue properties).
I use the object.propertymethod syntax throughout callout A in Listing 2 to set the initial values for both single- and multivalue properties. In callout B in Listing 2, I switch to using Put and PutEx because I update the multivalue property named otherHomePhone.
The IADsContainer Interface
IADsContainer is the interface for managing the lifecycle of AD objects. Container objects represent the hierarchy and organization of the DIT, and every container object in AD implements IADsContainer. IADsContainer exposes the four properties that Table 3 lists and the five methods that Table 4, page 182, lists.
Of the four IADsContainer properties, two are available to the LDAP provider. However, you can use VBScript to directly call only one property: Filter. As the name implies, the Filter property lets you apply a filter to a container to facilitate enumerating specific object types. For example, if you bind to an OU and you want to enumerate only the group objects that reside in the OU, you apply a Group Filter to the OU, as Figure 4 shows.
You can add other object types to the array to filter multiple object types. For example, Array("Group", "Computer") enumerates all objects of type Group and Computer. You can also reapply a filter with other object types to produce an entirely different enumeration. However, if you use the filter property to filter User objects, both User and Computer objects appear in the enumeration because Computer is a child to User in the AD class hierarchy. You can work around this caveat in a couple of different ways. For example, you can filter for Computer objects first to create an in-memory list of only the Computer objects. You next create a second in-memory list for the User filter, then compare the two lists and remove the items in the computer list from the user list. Another solution involves replacing the filter entirely with an ADO query.
The other IADsContainer property applicable to the LDAP provider is NewEnum. However, you don't call NewEnum directly; using a VBScript For Each statement to enumerate a container implicitly calls NewEnum, as Figure 4 shows.
Of the five IADsContainer methods, Create, Delete, GetObject, and MoveHere are available to the LDAP provider. Collectively, the four methods provide the primary mechanisms for the life- cycle management of AD objects.
Create. You use the Create method to create every new AD object (e.g., User, OU, Group, Computer, Site). As the code at the beginning of callout A in Listing 2 shows, you bind to the appropriate container and call Create to create a new object. Some common mistakes to avoid when you use Create are failing to set the new object's mandatory properties before calling SetInfo or forgetting the "key=" as part of the object's RDN. You can use a script similar to the one in Listing 1 to determine an object's mandatory (mustContain) and optional (mayContain) properties.
The system assigns most mandatory properties (e.g., objectClass, objectCategory, objectSid, ntSecurityDescriptor, instanceType). Of the seven mandatory user properties that Figure 1 shows, you need to be concerned with only cn and sAMAccountName. The cn property is set as part of the call to the Create method, and you use Put to set sAMAccountName. Typically, you shouldn't initialize mandatory properties that the system handles for you.
Delete. The Delete method is straightforward, as callout D in Listing 2 shows. First you bind to the parent container of the object you want to delete. Next, call the container's Delete method and identify the target child object to delete. As is the case with the Create method, include the appropriate "key=" as part of the target object's RDN.
Deleting an object is a permanent action. Also, Delete can't delete containers that hold objects. You must first recursively delete all child objects, then recursively delete nested container objects if you want to use the Delete method to remove an entire branch. If you're certain you want to sever an entire branch, use the DeleteObject method that the IADsDeleteOps utility interface provides to delete a container and all the container's children.
GetObject. The IADsContainer GetObject method is similar to VBScript's GetObject function in that IADsContainer GetObject creates a reference to an ADSI automation object. The difference between the two functions is that the IADsContainer GetObject method lets you bind only to child objects in an ADSI container. This distinction explains why the method signatures (i.e., parameter lists) of the two identically named methods are different. You provide an ADsPath to VBScript's GetObject function, whereas you provide the class name and target object's RDN in the current container when you use the IADsContainer GetObject method. The latter method provides an easy way to enumerate a container and easily bind to each child object in the body of the enumeration.
MoveHere. You use MoveHere to move an object from one container to another, such as for a cross-domain move or to rename an object. Of the four methods, users most often make mistakes with MoveHere. The most common mistake is failing to understand that MoveHere is the mechanism you use to rename AD objects. You can't change an object's mandatory cn or equivalent property (e.g., ou in the case of an OU) to rename an AD object in the same manner in which you change other attributes. You must use MoveHere. Another common mistake is trying to construct the ADsPath for the object to move or rename. You should try to use the IADs::ADs Path property to accomplish this task, or use variables, as you see at callout C in Listing 2.
When you use MoveHere, you always bind to the target container, which might or might not be the object's current parent container. When you move an object, you bind to the target container; when you rename an object, you bind to the object's parent container. If you move and rename an object, you bind to the target container. After you obtain the target or parent container interface, you can call MoveHere. MoveHere's first parameter— the ADsPath of the object to move or rename—is always the same regardless of the type of operation. The second parameter is one of two possible values. If you're renaming the object, the second parameter is the object's new RDN. If you're not renaming the object, the second parameter is the object's current RDN.
More Interfaces in ADSI
Although I've covered the 3 core ADSI interfaces in these two ADSI articles, you'lI certainly use others of the more than 50 remaining interfaces to perform your administrative tasks. Here are some examples of the interfaces you might use:
- You use the SetPassword and ChangePassword methods that the IADsUser interface exposes to manage users' passwords, as callout A in Listing 2 shows.
- You use the Group method that the IADsUser interface exposes to enumerate the groups to which a user belongs.
- You use the Members, IsMember, Add, and Remove methods that the IADsGroup interface exposes to manage group membership.
- You use the IADsSecurityDescriptor, IADsAccessControlList, and IADs AccessControlEntry security interfaces to manage security for AD objects.
To determine whether you need to use an interface other than IADs or IADsContainer, follow three easy steps:
- Identify the object type you're working with (e.g., Computer, Group, User).
- Reference the ADSI documentation to see whether an interface exists for the specific object type you're working with. The interface names begin with IADs followed by the name of the object type (e.g.,. IADsComputer, IADsGroup, IADsOU, IADsUser). You can find the complete list of interfaces in the Microsoft Developer Network (MSDN) Online Library.
- If an interface in ADSI exists, review the properties and methods that the interface exposes to determine whether a suitable helper method or property exists.
IADs and IADsContainer are applicable to every ADSI script you create. When you understand these two interfaces, and the IADsOpenDSObject interface, you're well on your way to becoming your organization's ADSI scripting guru.