Editor's note: This article is the fourth part of a 12-part series on Active Directory Service Interfaces (ADSI). The series started in the January 1999 issue. Refer to previous installments for definitions and background information.
Last month, I discussed how to read from and write to the property cache. This month, I'll explain how to use ADSI to manipulate the contents of the cache.
There will be times when you need to write a script that queries all the values that have been set in the underlying directory for a particular object. For example, suppose you're one of several systems administrators who work with your company's Windows 2000 (Win2K) Active Directory (AD) implementation. You need to write a script that queries all the property values that the administrators have set for a particular user.
Discovering the set property values for an object can be a long, tedious job. Fortunately, ADSI provides a quick method. If someone has set a value for a property, it must be in that object's property cache. So all you need to do is walk through the property cache (i.e., go through the cache item by item), displaying and optionally modifying each item as you go.
In this article, I'll describe the property cache mechanics and show you how to write scripts that use several ADSI methods and properties to add individual values, add a set of values, walk through the property cache, and write modifications to the cache and to the directory. Although these examples access the Lightweight Directory Access Protocol (LDAP) name-space, you can just as easily substitute the WinNT namespace in any of the scripts and run them against Windows NT servers. An example of such a script is available on the Win32 Scripting Journal Web site.
Property Cache Mechanics
Every object has properties. When you perform an explicit IADs GetInfo call (or an implicit IADs GetInfo call using IADs Get) on an object that you previously bound to, the OS loads all the properties for that specific object into that object's property cache. In other words, each object that you bind to has a separate property cache.
Consider the property cache as a simple list of properties. The PropertyList object represents this list. You can use several IADsPropertyList methods to navigate through the list and access items. For example, you can navigate the list and access each item, every nth item, or one particular item based on its name.
Each item in the property list is a property entry. The PropertyEntry object represents an entry. You use the IADsPropertyEntry interface to access property entries.
A property entry can have one or more property values. The PropertyValue object represents a value. To access values in a property entry, you use the IADsPropertyValue interface.
To summize, you use IADsPropertyList to navigate through and access property entries in the property list. When you want to manipulate a property, you use IADsPropertyEntry. To access the values of that property entry, you use IADsPropertyValue.
Adding Individual Values
To show you how to add an individual value, I'll expand on one of the examples from Part 3 of this series: the pager property of the User object. The pager property is an array of text strings representing multiple pager numbers.
Consider that any property represents data. Data can take several forms, including a string, an integer, or a Boolean value. In the cache, each property has two attributes: one attribute specifies the type of data that property represents and the other attribute specifies the value of that data type. So, for example, each pager property has two attributes: a Unicode string (the type of data) and the pager number (the value of that Unicode string). The User object's lastLogon property, which specifies the time the user last logged on, has the two attributes of LargeInteger (type of data) and a date and time stamp (the value of that LargeInteger).
The pager and lastLogon properties are instances of the PropertyValue object, so you manipulate them with the method and property methods of the IADsPropertyValue interface. For example, you use the ADsType property method to set the PropertyValue's type of data. Table 1 shows some of the corresponding constant names and values that you can set for the ADsType property. (A complete list of constants for the ADsType property is available on the Win32 Scripting Journal Web site.)
Suppose you want to add a PropertyValue object with the value of "Hi There!" The two attributes are a case-sensitive string (i.e., the type of data, or ADsType property) and "Hi There!" (i.e., the value of that case-sensitive string, or the CaseExactString property). The constant for the ADsType of a case-sensitive string is ADSTYPE_CASE_EXACT_STRING, which has a numeric value of 2.
Listing 1 on page 8 shows how you create this new PropertyValue object. You begin by setting the ADSTYPE_CASE_EXACT_STRING constant to its numeric value (i.e., 2) and declaring the adsPropValue variable. If you use Windows Scripting Host (WSH) with Visual Basic Script (VBScript), you must either define the constants, as Listing 1 does, or use the values directly. You use the constant names only if you're using Visual Basic (VB).
Next, you use VBScript's CreateObject method to create an instance of the PropertyValue object and set it to the adsPropValue variable. (If you're using the beta version of VBScript 5.0, here's a word of caution: The beta version includes the New operator for creating an object. However, when I was writing this article, the New operator didn't work for PropertyEntry and PropertyValue objects.) You then assign the two attributes to the PropertyValue object. You use adsPropValue's ADsType property method to assign the property's data type to the ADSTYPE_CASE_EXACT_STRING constant. You use adsPropValue's CaseExactString property method to assign the property's value to "Hi There!"
Adding Sets of Values
As I mentioned previously, some properties hold one value (e.g., lastLogon property); others hold multiple values in an array (e.g., pager property). The PropertyEntry object holds the entire set of values for a property, be it one value or many values.
However, the PropertyEntry object does more than store values. This object's property methods dictate how you can manipulate those values. The PropertyEntry object has four property methods: Name, Value, ADsType, and ControlCode. The Name property method sets the name of the property that you want to manipulate (e.g., pager). The Values property method sets an array containing those values you want to manipulate (e.g., the pager numbers). The ADsType property method determines the data type of those values (e.g., Unicode string). The ControlCode property method tells the cache whether to overwrite, update, or add to the property's existing values. You use the constants in Table 2 with the ControlCode property. These constants are the same as the constants for the IADs interface's PutEx method I described last month. Because ControlCode constants work the same way as the PutEx method constants, I won't go through them again here.
Listing 2 shows how to create a PropertyEntry object from one property value. The first part of Listing 2 is similar to Listing 1. You begin by setting the constants to their numeric values and declaring the variables. Next, you create an instance of the PropertyValue object and set it to the adsPropValue variable. You then use the ADs-Type property method to assign the property's data type to the ADSTYPE_CASE_IGNORE_STRING constant and the CaseIgnoreString property method to assign the property's value to "0123-456-7890".
You begin the second part of Listing 2 by creating an instance of the PropertyEntry object and setting it to the adsPropEntry variable. Then, you set all four PropertyEntry properties. For the Values property, you must use the Array( ) function to force the values into an array, even if you set only one value. For the ControlCode property, you're replacing the existing values with the ones you're passing in.
Walking Through the Property Cache
For any object, the property cache consists of PropertyEntry objects that correspond to each property. When you use the IADs interface's Get method, it reads the cache's PropertyEntry for that particular property.
The PropertyList object represents the entire set of properties for an object. You use the methods and property methods of the IADsPropertyList interface to manipulate the PropertyList object. The code in Listing 3 uses several of those methods and property methods to demonstrate three ways of walking through the property cache.
Listing 3 begins by using VBScript's Option Explicit statement (which requires you to declare all variables before using them) and the On Error Resume Next statement (which enables an error-handling routine). Then, after declaring the variables, you use VBScript's GetObject method to bind to the group whose property cache you want to look at. In this case, you want to view the properties for the Managers Group object and you bind that group to the adsGroup variable. Next, you explicitly use the GetInfo method to load the property cache for this group. (You won't be using the Get method, so the system won't implicitly use the GetInfo method to load the cache.)
Each object in adsGroup has a PropertyList object, so you use the IADsPropertyList interface's PropertyCount property method to count each PropertyList object. You store the count for later use by setting it to the intPropCount variable. You also use WSH's Echo method to print the count in a message box.
You now know how many properties that adsGroup has, but you need to find out the values of those properties. You can use one of three approaches to walk through the property cache to get this information. Listing 3 shows you how to use all three approaches.
Approach 1: Using the IADsPropertyList's PropertyCount property method. You begin this approach by setting the txtStr variable to a zero-length string (""). You'll be concatenating the string's value later as part of a loop, so you want to make sure that the initial value is a zero-length string.
You then walk through the property list by counting the items in the 0 To (intPropCount-1) index. You need to specify this index, because the property list index starts at 0 rather than 1. So, for example, a property list with 15 items has an index ranging from 0 to 14.
For each item in the index, you concatenate (&) two property methods to retrieve the property's name and ADsType. The script processes concatenated statements from left to right, so it first uses adsGroup.Item(CInt(intIndex)) to retrieve a property entry, to which it applies the Name property method to get the property's name. (For an important note about using the adsGroup Item method, see the sidebar "Beware of the ADSI 2.5 Beta 1 Bug.") The script then uses adsGroup.Item(CInt(intIndex)) to retrieve the same property entry, to which it applies the ADsType property method to get the property's data type. Forcing the script to process adsGroup.Item(CInt(intIndex)) twice is inefficient. I processed it twice only to illustrate how to walk through the property list. Approach 2 illustrates a more efficient approach. In this approach, you put the item in a variable the first time you call the IADsPropertyList's Item method and then reference that variable in subsequent queries.
The concatenated code includes more than just the two property methods. The code also concatenates a tab (vbTab) between the two property methods and a carriage-return line-feed (vbCrLf), or hard return, after the second property method. But even more important, the code first concatenates the existing txtStr variable onto the front (i.e., txtStr = txtStr & property method 1 & property method 2), which means that, in the output, you're appending these property values to the existing txtStr string. As a result, WSH displays all the property values in one message box if you use WSH's wscript.exe scripting engine to run the script. (If you're using WSH's cscript.exe scripting engine, using this append technique makes no difference. For more information about the two WSH scripting engines, see Michael Otey, "An Introduction to WSH," December 1998.) If you don't concatenate the txtStr variable (i.e., txtStr = property method 1 & property method 2), WSH displays a separate message box for each property.
When the script finishes looping through the property list index, it prints the appended txtStr string in the message box. Aproaches 2 and 3 also use the append technique to display all their output in one message box.
Approach 2: Using the IADsPropertyList's Next method. You start this approach by resetting the txtStr variable to a zero-length string to ensure that no values from the previous approach are left in the string. You then call the Next method to retrieve a copy of the first property entry and set the result to the adsPropEntry variable. Because you called the Next method, you can use a While loop to iterate through the cache until you encounter a Null value, which specifies that you're at the end of the list.
Providing that the first property entry isn't a Null entry, you enter the While loop, which the code While (Not (IsNull(adsPropEntry)) specifies. The And Err.Number = 0 code designates a test to see whether an error has occurred. A value of 0 specifies no error; any other value specifies an error. If you retrieve a valid entry (i.e., not a null entry) and an error hasn't occurred (i.e., the error number is equal to 0), you enter the loop. Within the loop, you append the property name and data type to the txtStr string in a similar manner to before. To move to the next property entry in the property cache, you again call the Next method. As long as this value isn't null and isn't generating an error code, this process continues until you hit a Null entry, which means you're at the end of the list. The Wend keyword signifies the end of the While loop. Finally, you print the results.
Approach 3: Using the IADsPropertyList's Next and Skip methods. This approach's code is identical to the code you use in Approach 2, except for the addition of two lines. The code at callout A calls the Reset property method, which sets the property list pointer to the first property entry in the cache. If you don't use the Reset property method, the pointer will be at the end of the cache, which would generate a Null entry. The adsGroup.Skip(2) code at callout B tells the Next property method to skip the next two property entries. In other words, the Next property method is retrieving every third property, so this approach returns only property entries 1, 4, 7, 10, and so on.
Writing the Modifications
Now that you can walk through the cache, I'll show you how to write the modifications to the cache and the directory. Listing 4 illustrates these procedures. This script is an amalgam of the code in Listings 1, 2, and 3. As such, it shows you how to assemble the pieces of code into a usable script.
The script begins with Option Explicit and On Error Resume Next, after which it sets the constants, declares the variables, and binds the adsUser variable to the AlistairGLN user object. The script then divides into sections A, B, C, and D.
Section A determines the User object's property count and lists each property's name and data type. The code in this section is from Listing 3.
Section B creates a property entry and writes it to the cache. Except for the last line, Section B's code is from Listing 2. The last line uses the IADsPropertyList's PutPropertyItem method to write the new property entry for adsUser to the cache. However, you need to use the SetInfo method to write this entry to the directory.
Section C contains new code. The first line uses the SetInfo method to write the cache to the directory. The second line uses the explicit GetInfo method to read it back into the cache. Although the second line might not seem necessary, it is. If you don't use an explicit GetInfo call, you'll be accessing the same cache that you accessed before you added the new property entry. The explicit GetInfo call retrieves any new properties that anyone else has updated since your last implicit or explicit GetInfo call.
Section D recalculates the property count and reenumerates each property's name and data type so that you can see the modifications. If you see the property count increase by one after you write the cache to the directory, the script has successfully executed. The code in this section is from Listing 3.
The Final Example
My final example script is too long to include in this article, but you can download it from the Win32 Scripting Journal Web site. This script walks through the property cache for an object and prints the name, data type, and values of each entry. Some of the properties are not printable strings, so printing them in a text format makes little sense. Thus, this script prints only the text strings. The downloadable script illustrates how you can use the WinNT namespace rather than the LDAP namespace in a script and how you can run the script against NT servers rather than Win2K servers.
This article concludes the discussion of the property cache and its ADSI interfaces. Next month, I'll take a first look at how you can rapidly search a namespace and retrieve a result set that you can manipulate.