Chapter 5. ADSI Scripting Primer

Administering a directory service often involves numerous repetitive tasks such as creating, deleting, and modifying users, groups, organizational units, computers, and other directory resources. Performing these steps manually by using graphical user interface (GUI) tools is time-consuming, tedious, and error prone. A key to reducing time consumption, tedium, and errors when administering a directory is automating repetitive tasks by using scripts.

Active Directory Service Interfaces (ADSI) is the technology that allows you to create custom scripts to administer directories. ADSI-enabled scripts are capable of performing a wide range of administrative tasks involving network directories such as the Active Directory® directory service.

In This Chapter

ADSI Overview

Obtaining the skills necessary to successfully script directory administration tasks is not difficult. In fact, of all the required scripting skills, scripting with Active Directory Service Interfaces (ADSI) is perhaps the easiest to master. This is largely a result of the consistent and uniform approach ADSI brings to directory services management.

An Introductory Example

Consider the following scenario: It is Friday morning, and you plan to have a great weekend followed by a weeklong vacation. You then receive an urgent e-mail message from your boss telling you that a group of consultants will be working in the lab starting Monday morning. Their task is to load-test their application by logging on to this application using 1,000 different user accounts. The application is tightly integrated with Active Directory.

Your task is to set up an Active Directory domain on a computer running Microsoft® Windows® 2000 and create 1,000 user accounts in the Users container of the new domain. Installing Microsoft® Windows® 2000 Advanced Server and Active Directory is simple because you already have automated installation procedures in place.

But how are you going to create 1,000 user accounts and still get all of your daily work done before your planned hiatus? This is one of the many times that ADSI scripting can help you accomplish a potentially tedious and lengthy task. The script in Listing 5.1 creates 1,000 user accounts named UserNo1 – UserNo1000.

Example 5.1. Creating 1,000 Active Directory User Accounts

 1 Set objRootDSE = GetObject("LDAP://rootDSE")
 2 Set objContainer = GetObject("LDAP://cn=Users," & _
 3     objRootDSE.Get("defaultNamingContext"))
 4
 5 For i = 1 To 1000
 6     Set objLeaf = objContainer.Create("User", "cn=UserNo" & i)
 7     objLeaf.Put "sAMAccountName", "UserNo" & i
 8     objLeaf.SetInfo
 9 Next
10 Wscript.Echo "1000 Users created."

Caution

Caution

Do not run the script in Listing 5.1 in a production domain. By default, the script creates 1,000 user accounts in the current logon domain.

It might take this script up to five minutes to run if a slow domain controller is servicing the request. Even this amount of delay is minuscule compared with how long it would take you to create 1,000 user accounts manually.

Directory Service Management

The script in Listing 5.1 is powerful but represents only a single task, creating user accounts. Using ADSI scripts, you can complete countless Active Directory administration tasks. Simply put, Active Directory administration involves managing the life cycle of directory objects from initial creation (as demonstrated in Listing 5.1) to deletion. Active Directory includes objects such as user accounts, groups, computers, and sites. The four common task categories and some example tasks involved in the life cycle of objects are:

  1. Create. Creating user accounts, groups, organizational units (OUs), computer accounts, sites, subnets, published printers, and shared folders.

  2. Modify. Adding a telephone number to a user account, deleting a member from a group, resetting a password, disabling a computer account, and delegating control of an OU or a site to a user or group.

  3. Read. Reading the full name of a user account, reading a list of group members or a list of users in an OU, and reading operating system information from computer account objects.

  4. Delete. Deleting objects that are no longer in use, such as user accounts, groups, and OUs.

What makes using ADSI scripts a powerful and efficient way to manage Active Directory is the consistent approach ADSI provides for performing similar tasks on different types of objects. This consistency carries over from one major task category to the next. For example, you use the same basic approach to create a user, group, OU, or almost any object stored in Active Directory.

The same is true for modifying and reading Active Directory objects: You use the same basic steps to modify and read objects without regard to the target object’s type. Finally, you use the same steps to delete objects, again regardless of the target object’s type.

ADSI Scripting Fundamentals

Examining script examples from each of the major task categories — create, modify, read, and delete — will give you a better understanding of ADSI scripting. Throughout this section, you will observe the relative ease with which a variety of tasks are carried out and the consistent approach used to perform the same task on different object types.

Primary ADSI Scripting Tasks

The primary scripting tasks — create, modify, read, and delete — are simple to complete with scripts. Simplicity might suggest that these tasks are limited in scope, but in reality they play a significant role in managing almost any directory service. Thus, they are the four most useful tasks in directory service management.

Creating Directory Service Objects

Listing 5.1 showed how to create 1,000 user accounts. To simplify matters, the following section takes a more modest approach by creating one OU, one user account, and one group.

Creating Active Directory objects involves four basic steps:

  1. Connect to the Active Directory container that will store the new object.

  2. Create the object.

  3. Set the object’s mandatory attributes, if necessary.

  4. Commit the new object to Active Directory.

The goal of the three scripts in this section is to create an OU named HR (Human Resources department), a user account named MyerKen in the HR OU, and a group named Atl-Users, also in the HR OU.

Creating an OU

The script in Listing 5.2 creates an OU named HR in the na.fabrikam.com domain. All mandatory attributes of an OU are automatically assigned a value by Active Directory. Therefore, the step that sets mandatory attributes does not appear in Listing 5.2.

To carry out this task, the script performs the following steps:

  1. Connect to the na.fabrikam.com domain container.

  2. Create an OU object named HR.

  3. Commit the new OU to Active Directory.

Example 5.2. Creating an OU

1 Set objDomain = GetObject("LDAP://dc=NA,dc=fabrikam,dc=com")
2 Set objOU = objDomain.Create("organizationalUnit", "ou=HR")
3 objOU.SetInfo

Creating a User Account

The script in Listing 5.3 creates a user account named MyerKen in the OU named HR. The HR OU is located in the na.fabrkam.com domain. To carry out this task, the script performs the following steps:

  1. Connect to the HR OU container in the na.fabrikam.com domain.

    HR is the OU that was created by running the script appearing in Listing 5.2.

  2. Create a user account named MyerKen.

    Using an uppercase letter for the first letter of the last and first name is not necessary. However, the case is preserved when the object is saved to Active Directory. Therefore, users will be able to distinguish the last name from the first name when searching Active Directory.

  3. Set the sAMAccountName mandatory attribute to the value myerken.

    There is no need to capitalize the first letter of the last and first name for this attribute’s value because, typically, users do not perform user account searches on the sAMAccountName attribute.

  4. Commit the new user account to Active Directory.

Example 5.3. Creating a User Account

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 Set objUser = objOU.Create("user", "cn=MyerKen")
3 objUser.Put "sAMAccountName", "myerken"
4 objUser.SetInfo

Creating a Group

The script in Listing 5.4 creates a global group named Atl-Users in the OU named HR, located in the na.fabrikam.com domain. To carry out this task, the script performs the following steps:

  1. Connect to the HR OU container in the na.fabrikam.com domain.

  2. Create a group named Atl-Users.

    By default, the script creates a global group.

  3. Set the sAMAccountName mandatory attribute to a value of Atl-Users.

    Like creating a user account, creating a security group requires a single mandatory attribute, sAMAccountName.

  4. Commit the new group account to Active Directory.

Example 5.4. Creating a Group

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 Set objGroup = objOU.Create("group", "cn=Atl-Users")
3 objGroup.Put "sAMAccountName", "Atl-Users"
4 objGroup.SetInfo

Important observations about the scripts in this section are:

  • They perform the same basic steps: They connect to an Active Directory container, create an object, set the object’s mandatory attributes (if necessary), and commit the object to Active Directory.

  • They use the same method (Create) without regard to the class of the object being created.

  • The script parameters are the only parts of the scripts that are different. Each script contains the class name (organizationalUnit, user, and group) identifying the type of object to create and the object’s corresponding attributes (the new object’s name and the user’s and group’s mandatory sAMAccountName attribute).

The steps for creating an OU (Listing 5.2), a user account (Listing 5.3), and a group (Listing 5.4) are strikingly similar, as the three preceding code listings demonstrate. This similarity extends to creating all types of directory objects. This consistency will become even clearer by examining each code line in each listing.

To create an object, the script first connects to a container, a process called binding. Binding occurs on the first line of each listing. In Listing 5.2, the script binds to the domain to create an OU. When creating an object in a domain, think of the domain as simply a container that can hold objects, just as an OU is a container that can hold objects. In Listing 5.3 and Listing 5.4, both scripts bind to an OU to create objects within it. The code in Listing 5.3 creates a user account object, and the code in Listing 5.4 creates a group object.

After binding to a container, the script performs the task of creating an object. To create an object, you must specify two parameters, the object’s class and name. In Listing 5.2, the script creates an OU by specifying the organizationalUnit class and the name, ou=HR. In Listing 5.3, the script creates a user account by specifying the user class and the name, cn=MyerKen. In Listing 5.4, the script creates a group by specifying the group class and the name, cn=Atl-Users. You will see these parameter pairs on line 3 of each listing. For information about how to determine an object’s class and name, see “ADSI Interfaces” later in this chapter.

Before committing an object to the directory, you must first set any mandatory attributes defined for an object. There are no mandatory attributes that the script needs to set for creating an OU. Therefore, this step does not occur in Listing 5.2. However, line 4 in both Listing 5.3 and Listing 5.4 sets a mandatory attribute (sAMAccountName) for a user account and a group object. The script assigns the mandatory attribute to the object by specifying the mandatory attribute’s name and its value. In Listing 5.3, the value is myerken; and in Listing 5.4, the value is Atl-Users. For information about how to determine the mandatory attributes of an object, see “Active Directory Architecture” later in this chapter.

The last step in creating an object is committing (saving) the object to the directory. This final step is the last script line in each listing. Modify, read, and delete tasks also exhibit similar uniformity, as the next sections demonstrate.

Modifying Directory Service Objects

Modifying an object is equivalent to writing an attribute to an existing object in Active Directory. If an attribute contains a value, modifying it will clear the existing value and replace it with a different value.

Typically, the type of modification you make to an object will depend on the type of object you want to modify and various characteristics of the attribute — for example, whether the attribute holds a single value or multiple values. For simplicity, however, the following task descriptions illustrate how to write a single value to the same attribute in three different objects.

Modifying attributes of Active Directory objects involves three basic steps:

  1. Connect to the Active Directory object you want to modify.

  2. Modify one or more of the object’s attributes.

  3. Commit the change to Active Directory.

The goal of the three scripts in this section is to write an attribute to each of the objects created in “Creating Directory Service Objects” earlier in this chapter. The objects include the HR OU, the MyerKen user account, and the Atl-Users global group. The description attribute is contained in all three of these objects, so it is used as the attribute to modify.

Modifying an Attribute of an OU

The script in Listing 5.5 modifies the description attribute of the OU named HR in the na.fabrikam.com domain. The description attribute is assigned the value Human Resources. To carry out this task, the script performs the following steps:

  1. Connect to the HR OU object in the na.fabrikam.com domain.

    In contrast with the create task, the HR OU is referred to as an object rather than a container because the task completed in this script is to write an attribute of an object.

  2. Modify the object’s attributes by assigning the description attribute the value Human Resources.

  3. Commit the change to the OU in Active Directory.

Example 5.5. Writing the description Attribute to an OU

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 objOU.Put "description", "Human Resources"
3 objOU.SetInfo

Modifying an Attribute of a User Account

The script in Listing 5.6 modifies the description attribute of the user account named MyerKen in the HR OU of the na.fabrikam.com domain. The description attribute is assigned the value HR employee. To carry out this task, the script performs the following steps:

  1. Connect to the MyerKen user account object in the HR OU of the na.fabrikam.com domain.

  2. Modify the object’s attributes by assigning the description attribute the value HR employee.

  3. Commit the change to the user account in Active Directory.

Example 5.6. Writing the description Attribute to a User Account

1 Set objUser = _
2     GetObject("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
3 objUser.Put "description", "HR employee"
4 objUser.SetInfo

Modifying an Attribute of a Group

The script in Listing 5.7 modifies the description attribute of the group account named Atl-Users in the HR OU of the na.fabrikam.com domain. The description attribute is assigned the value of Atlanta users. To carry out this task, the script performs the following steps:

  1. Connect to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  2. Modify the object’s attributes by assigning the description attribute the value Atlanta users.

  3. Commit the change to the group in Active Directory.

Example 5.7. Writing the description Attribute to a Group

1 Set objOU = GetObject _
2     ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
3 objOU.Put "description", "Atlanta users"
4 objOU.SetInfo

Important observations about the scripts in this section are:

  • They perform the same basic steps: They connect to an Active Directory object, modify an attribute of the object, and write the change to the corresponding Active Directory object.

  • They use the same method (Put) without regard to the class of object being modified.

Reading Attributes of Directory Service Objects

The preceding sections described how to create an OU, a user account, and a group, and set the description attribute on each of these objects. The next common task is to read an attribute of each object.

Reading an Active Directory object’s attributes involves two simple steps:

  1. Connect to the Active Directory object you want to read.

  2. Read one or more of the object’s attributes.

The goal of the three scripts in this section will be to read the description attribute of the HR OU, the MyerKen user account, and the Atl-Users group and display their values on the screen.

Important

Important

Command window output generated by the CScript script host follows each script example that echoes information to the screen. If you run these scripts, be sure you use CScript because some of the scripts contain a significant amount of output. This makes WScript an inappropriate scripting host for running these scripts.

Reading an Attribute of an OU

The script in Listing 5.8 reads and displays the description attribute of the OU named HR in the na.fabrikam.com domain. To carry out this task, the script performs the following steps:

  1. Connect to the HR OU object in the na.fabrikam.com domain.

  2. Read the object’s description attribute.

Example 5.8. Reading the description Attribute of an OU

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 Wscript.Echo objOU.Get("description")

When this script runs in the na.fabrikam.com domain, it echoes the description of the HR OU to the command window, as shown:

Human Resources

Reading an Attribute of a User Account

The script in Listing 5.9 reads and displays the description attribute of the user account named MyerKen, located in the HR OU of the na.fabrikam.com domain.

  1. Connect to the MyerKen user account object in the HR OU of the na.fabrikam.com domain.

  2. Read the object’s description attribute.

Example 5.9. Reading the description Attribute of a User Account

1 Set objUser = _
2     GetObject("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
3 Wscript.Echo objUser.Get("description")

When this script runs in the na.fabrikam.com domain, it echoes the description of the user account to the command window, as shown:

HR employee

Reading an Attribute of a Group

The script in Listing 5.10 reads and displays the description attribute of a global group named Atl-Users, located in the HR OU of the na.fabrikam.com domain.

  1. Connect to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  2. Read the object’s description attribute.

Example 5.10. Reading the description Attribute of a Group

1 Set objGroup = _
2     GetObject("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
3 Wscript.Echo objGroup.Get("description")

When this script runs in the na.fabrikam.com domain, it echoes the description of the group to the command window, as shown:

Atlanta users

Important observations about the scripts in this section:

  • They perform the same basic steps: They connect to an Active Directory object and read an attribute of the object.

  • They use the same method (Get) without regard to the class of object being read.

As demonstrated in this section, the process for reading attributes is uniform from one object to the next. In fact, within a particular task, the steps you follow from one object to the next are consistent. This consistency empowers you to write scripts that can read thousands of attributes from the many objects stored in Active Directory.

Deleting Directory Service Objects

In the life cycle of an object, the final management task is deletion. All of the preceding code examples started by performing a task to create or act on the OU named HR. This task, however, starts in reverse order by demonstrating how to delete a group, then a user account, and then an OU. This reverse approach is necessary because, when you use the Delete method in an ADSI script, you cannot delete an OU without first removing the objects (called leaf objects) within it.

If you use the Delete method to delete an OU before all leaf objects are removed, Windows Script Host (WSH) displays a message similar to the following:

C:DeleteOu.vbs(2, 1) (null): The directory service can perform the requested
operation only on a leaf object.

It might not be entirely clear why the error message reads, “The directory service can perform the requested operation only on a leaf object.” It is true that an OU is a container object. However, when an OU is empty, the Delete method can delete an OU because, from the script’s perspective, an empty OU is considered a leaf object.

This error message is avoided in the following examples because the first script removes the Atl-Users group and the second script removes the MyerKen user account before the third script deletes the HR OU.

Deleting Active Directory objects involves two simple steps:

  1. Connect to the Active Directory container where the object is stored.

  2. Delete the object.

Deleting a Group

The script in Listing 5.11 deletes the Atl-Users group from the HR OU in the na.fabrikam.com domain.

  1. Connect to the HR OU container in the na.fabrikam.com domain.

    The OU is referred to as a container rather than an object because the task completed in this script is to delete an object within a container.

  2. Delete the Atl-Users group from the HR OU in Active Directory.

Example 5.11. Deleting a Group

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 objOU.Delete "group", "cn=Atl-Users"

Deleting a User Account

The script in Listing 5.12 deletes the MyerKen user account from the HR OU in the na.fabrikam.com domain.

  1. Connect to the HR OU container in the na.fabrikam.com domain.

  2. Delete the MyerKen user account from the HR OU in Active Directory.

Example 5.12. Deleting a User Account

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 objOU.Delete "user", "cn=MyerKen"

Deleting an OU

The script in Listing 5.13 deletes the HR OU from the na.fabrikam.com domain.

  1. Connect to the na.fabrikam.com domain container.

  2. Delete the HR OU from the na.fabrikam.com domain in Active Directory.

Example 5.13. Deleting an OU

1 Set objDomain = GetObject("LDAP://dc=NA,dc=fabrikam,dc=com")
2 objDomain.Delete "organizationalUnit", "ou=HR"

Important observations about the scripts in this section are:

  • They perform the same two steps: They connect to an Active Directory container and delete an object in the container.

  • They use the same method (Delete) without regard to the class of object being deleted.

Comparing the Primary Scripting Tasks

You have now seen the four primary categories of directory administration tasks (creating, reading, modifying, and deleting). The script examples demonstrate how simply and consistently you can complete these tasks using ADSI scripts. Figure 5.1 summarizes each major task category and the set of ADSI scripting steps involved in completing each task.

Comparison of Steps in Completing Primary ADSI Scripting Tasks

Figure 5.1. Comparison of Steps in Completing Primary ADSI Scripting Tasks

Figure 5.1 and the twelve scripts preceding it illustrate the following rules about ADSI scripting:

  1. Regardless of the task at hand, step 1 is connecting (binding) to an object or container.

  2. The Create method creates an Active Directory object.

  3. The Delete method deletes an Active Directory object.

  4. The Put method modifies or writes an Active Directory object’s attributes.

  5. The Get method reads an Active Directory object’s attributes.

  6. The SetInfo method commits new and modified objects to Active Directory.

  7. The parameters used with Create, Delete, Put, and Get are always the same.

Building ADSI Scripts

The following sections examine the main steps of the preceding script examples so that you have a better understanding of how to construct your own ADSI scripts.

Step 1: Establishing a Connection

Figure 5.1 and all of the preceding listings demonstrated that the first step you must take to complete any scripted directory management task is connecting to an object. This initial step is referred to as binding to the directory. Binding to the directory prepares the script to operate on an object.

When creating or deleting an object, you must bind to the container object where a leaf object will be created or deleted. When reading or modifying an object, you must bind to the object that you want to read or modify.

Because the first part of this chapter focuses on Active Directory, the examples shown here demonstrate how an ADSI script binds to Active Directory.

Choosing a Directory Service Object for a Binding Operation

To determine which directory service object to specify in a binding operation, consider the task you want to complete (create, delete, read, or modify), the type of object (leaf or container), and, if the object is a container, whether it is empty. Figure 5.2 shows a data flow diagram to help you determine which directory service object to use for a binding operation.

How To Determine Which Directory Service Object to Use for a Binding Operation

Figure 5.2. How To Determine Which Directory Service Object to Use for a Binding Operation

The data flow diagram does not show the following two tasks that can also be scripted:

  • Deleting a container that is not empty.

    Using the DeleteObject method, you can quickly and irreversibly delete a container that is not empty. This is potentially dangerous, so exercise caution before performing this type of delete operation.

  • Moving objects from a container before deleting it.

    You might need to delete a container but preserve its contents by moving them elsewhere.

For information about how to script object moves, see “Moving and Renaming Objects” later in this chapter.

Performing a Binding Operation

To bind to an Active Directory object, you use the VBScript GetObject function, the prefix LDAP: followed by two slashes, and the path to a directory object. The binding statements in four of the previous listings are:

  • Set objDomain = GetObject("LDAP://dc=NA,dc=fabrikam,dc=com")

    Binds to the na.fabrikam.com domain.

  • Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")

    Binds to the HR OU in the na.fabrikam.com domain.

  • Set objUser = _

         GetObject("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
    

    Binds to the MyerKen user account in the HR OU of the na.fabrikam.com domain.

  • Set objGroup = _

         GetObject ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
    

    Binds to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

ADsPath

In ADSI terminology, the LDAP: prefix combined with the path to a directory object is called an ADsPath. The binding code examples appearing in the preceding section show ADsPath strings enclosed in parentheses.

Specifying the provider

The first part of the ADsPath identifies an ADSI provider. ADSI scripts use providers to communicate with different types of directory services. For example, ADSI scripts use the ADSI Lightweight Directory Access Protocol (LDAP) provider (Adsldp.dll) to communicate with Active Directory and other LDAP version 2.0–compliant and version 3.0–compliant directory services. To communicate with the local Security Accounts Manager (SAM) database, ADSI scripts use the ADSI WinNT provider (Adsnt.dll). The LDAP: prefix at the beginning of the ADsPath identifies the ADSI LDAP provider as the appropriate provider for completing the binding operation.

Note

Note

ADSI provider identifiers are case sensitive.

The VBScript GetObject function uses the provider identifier to locate and load the appropriate ADSI provider dynamic-link library into memory. Once the provider is loaded into memory, the second part of the ADsPath, the path to the directory object, tells the provider which object to bind to in the directory.

Specifying the directory object’s path

The second part of the ADsPath is the path to the target object in the directory. ADSI supports several object path formats; the path format used in all of the preceding scripting examples is referred to as an object’s distinguished name (DN). This is just one of a number of identifiers that you can use to build an object’s ADsPath. For more information about ADSI identifiers and how to use them to refer to objects in Active Directory, see “Root Directory Service Entry” later in this chapter.

Every object stored in Active Directory has a distinguished name. Think of an object’s distinguished name as the LDAP version of a file’s fully qualified path. Every file has a fully qualified path that consists of a device name, followed by zero or more folder names, followed by the file name. Likewise, objects are located within Active Directory according to a hierarchical path. The full path to the object is defined by the object’s distinguished name.

Similar to a file path that contains multiple folder names separated by backslashes, an object’s distinguished name contains a series of attribute=value pairs separated by commas. Each attribute=value pair in an object’s distinguished name is analogous to a single folder in a file’s fully qualified path.

The attribute part of each attribute=value pair identifies an attribute type, which is defined in RFC 2247 and RFC 2253 and is based on the class of object. For example, the attribute type for a user account (User class) is CN, and the attribute type for an organizational unit (organizationalUnit class) is OU.

Note

Note

With the exception of the Domain Controllers container, the attribute type of the default containers directly below an Active Directory domain, such as the Computers, Users, and Built-in containers, is CN rather than OU.

The value part of each attribute=value pair is the name you provide when you create the object. Consider the MyerKen user account located in the HR OU of the na.fabrikam.com domain. Table 5.1 shows the object class from which each component of the DN is derived, along with its attribute type and value.

Table 5.1. Object Class, Naming Attributes, and Values

Object Class

Attribute Types

Value

User

CN

MyerKen

organizationalUnit

OU

HR

domain

DC

NA

domain

DC

fabrikam

domain

DC

com

The entire string of attribute=value pairs represents the path from an object to the root of the directory tree. Following each attribute=value pair from left to right moves you up through the directory hierarchy, as shown in Figure 5.3.

Example Directory Hierarchy and the DN of a User Account Object

Figure 5.3. Example Directory Hierarchy and the DN of a User Account Object

The MyerKen user account is located in the HR OU in the NA child domain. The parent domain of NA is fabrikam.com. Fabrikam.com is the root domain of the Active Directory hierarchy. Thus, the binding string (ADsPath) to this object is:

"LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com"

Note

Note

The binding string used in Listing 5.1 is a little different from the other listings because it employs a special ADSI feature called rootDSE. Ultimately, though, the binding string in Listing 5.1. also resolves to a distinguished name. For information about the rootDSE feature, see “Root Directory Service Entry” later in this chapter.

Creating an Object Reference to the Directory Object

The last part of the binding step to explore is the code on the left side of the assignment operator (equal sign), the VBScript Set keyword followed by a variable name that you provide. For example, the variable names objDomain, objOU, objUser, and objGroup appear in the earlier listings.

In contrast with simple string and numeric assignment statements, you must use the VBScript Set statement to associate an object reference with (or assign it to) a variable. The Set statement appears in the binding step because the GetObject function requires this in order to return a reference to an object.

In the case of ADSI, the object reference returned by GetObject is a virtual representation of the Active Directory object defined by the ADsPath in the binding step. The Set statement assigns the object reference to the variable following the Set keyword. For example, the objUser variable appearing in earlier listings is a virtual representation of the MyerKen user account object. Think of the variable as the script’s pointer, or reference, to the virtual representation of the Active Directory object. The script then uses the variable to manage the referenced object.

Now that you have determined the object to bind to and have completed step 1 (binding to the object) you can perform a task against the object.

Step 2: Performing a Task

Creating and deleting objects by using a script are straightforward. After the script binds to a container object, you can either create or delete objects in the container. Reading and writing objects from a script are equally straightforward. After the script binds to an object, you can perform either a read or a write operation on the object.

When you call the Create, Put (modify), or Get (read) method to perform a task on a directory object, these operations do not occur against Active Directory. Instead, they are performed locally in a special area of memory called the local property cache. The local property cache contains one or more attributes of an Active Directory object. In contrast, the Delete method acts upon the object in Active Directory. A small number of other methods also operate in this way.

Any changes you make are initially made only on the locally cached objects. To apply the changes to the actual objects in Active Directory, you need to call the SetInfo method.

Task Code

If you review all of the listings shown in each task section, you will notice that the task code of each script contains the name of the object variable that you initialized in the binding step, a dot, and the name of the task, as shown in the following list:

  • objVariableName.Create

    To create an object in the container specified by objVariableName

  • objVariableName.Get

    To read an attribute of the object specified by objVariableName

  • objVariableName.Put

    To modify or write an attribute of the object specified by objVariableName

  • objVariableName.Delete

    To delete an object in the container specified by objVariableName

The Create and Delete names are easy to remember because they are synonymous with the task. Use Get to read an attribute, and use Put to modify or write an attribute. In ADSI scripting, these tasks are called methods. Methods perform a task against an object.

Method Parameters

Each method — Create, Get, Put, and Delete — uses syntax specific to the task at hand. The task of creating an object requires you to define the type of object you want to create and give the object a name. On the other hand, the task of reading an attribute requires you to name the attribute you want to read. The required items that a method expects are called the method’s parameters.

Create parameters

The Create method requires two parameters, the class name and the relative distinguished name of the object you want to create. A relative distinguished name is the part of an object’s name that identifies an object as unique within the container where it is located. A relative distinguished name consists of a naming attribute and a value to be assigned to the object.

Consider the following examples from previous listings:

  • Set objOU = objDomain.Create("organizationalUnit", "ou=HR")

    Creates an OU

    The class name is organizationalUnit, the naming attribute is ou, and the value assigned to the object is HR. Thus, the relative distinguished name is ou=HR.

  • Set objUser = objOU.Create("user", "cn=MyerKen")

    Creates a user account

    The class name is user, the naming attribute is cn, and the value assigned to the object is MyerKen. Thus, the relative distinguished name is cn=MyerKen.

  • Set objGroup = objOU.Create("group", "cn=Atl-Users")

    Creates a group

    The class name is group, the naming attribute is cn, and the value assigned to the object is Atl-Users. Thus, the relative distinguished name is cn=Atl-Users.

Like the binding string, the Create method requires the VBScript Set statement and a variable to serve as a reference to the object created by the method. The object is created in local memory.

Important

Important

If you write a script to create an object or modify an attribute and the changes requested in the script are not reflected in Active Directory, verify that you included the SetInfo method call in your script.

Just as binding to an object or a container in Active Directory creates an object reference (virtual representation of an object) in local memory, the Create method also creates a virtual representation of an object in local memory. The difference is that the object did not come from Active Directory. Instead, it was created locally by the script, separate from a binding string. The Create method provides an object reference to the local object. You use this object reference to modify the object and ultimately to commit the object to Active Directory.

Figure 5.4 shows the pieces of the three create methods in the previous listings:

Comparison of Three Create Method Calls

Figure 5.4. Comparison of Three Create Method Calls

Modify parameters

The Put method requires two parameters: the name of the attribute (its lDAPDisplayName) and the value you will assign the attribute.

ADSI expects a certain type of attribute name, called the lDAPDisplayName, for completing read and write operations. Attributes are assigned a number of names, but the lDAPDisplayName is common to most ADSI scripts.

Consider the following examples from previous listings:

  • objOU.Put "description", "Human Resources"

    Writes an attribute of an OU

    The lDAPDisplayName of the attribute to modify is description, and the value assigned to the attribute is Human Resources.

  • objUser.Put "description", "HR employee"

    Writes an attribute of a user account

    The lDAPDisplayName of the attribute to modify is description, and the value assigned to the attribute is HR employee.

  • objOU.Put "description", "Atlanta users"

    Writes an attribute of a group

    The lDAPDisplayName of the attribute to modify is description, and the value assigned to the attribute is Atlanta users.

The write operation also appears in the Create task scripts when an object being created contains one or more mandatory attributes that must be assigned values before the object is committed to the directory. Objects cannot be created without configuring those mandatory attributes to which values are not automatically assigned by Active Directory.

The following examples from previous listings illustrate writing a manatory attribute:

  • objUser.Put "sAMAccountName", "myerken"

    Writes a mandatory attribute (sAMAccountName) of a user account

  • objUser.Put "sAMAccountName", "atl-users"

    Writes a mandatory attribute (sAMAccountName) of a group

Mandatory attributes are defined in Active Directory. For information about how to determine the mandatory attributes of an object, see “Active Directory Architecture” later in this chapter.

Read parameters

The Get method (read task) requires a single parameter, the lDAPDisplayName of the attribute to read. In line 2 of Listing 5.8 and line 3 of Listing 5.9 and Listing 5.10, the description attribute was read.

The goal of Listing 5.8 through Listing 5.10 was to read and display the value of an attribute. Therefore, VBScript’s Wscript.Echo statement appears with the call to the Get method to display information on the screen, as shown in the following examples from previous listings:

  • Wscript.Echo objOU.Get("description")

    Reads and displays the description attribute of an OU

  • Wscript.Echo objUser.Get("description")

    Reads and displays the description attribute of a user account

  • Wscript.Echo objGroup.Get("description")

    Reads and displays the description attribute of a group

Delete parameters

Like the Create method, the Delete method requires two parameters: the type of object you want to delete and the relative distinguished name of the object.

These parameters are illustrated in the following examples from previous listings:

  • objOU.Delete "group", "cn=Atl-Users"

    Deletes a group

    The class name is group, and the relative distinguished name of the object to delete is cn=Atl-Users.

  • objOU.Delete "user", "cn=MyerKen"

    Deletes a user account

    The class name is user, and the relative distinguished name of the object to delete is cn=MyerKen.

  • objDomain.Delete "organizationalUnit", "ou=HR"

    Deletes an OU

    The class name is organizationalUnit, and the relative distinguished name of the object to delete is ou=HR.

Step 3: Committing to Active Directory

Anytime you make a change, whether by creating an object or by writing a value to an attribute, you must save the changes to the directory by calling the SetInfo method. An ADSI script will run properly even if you do not commit changes, but if you do not commit changes, anything that the script did in the local property cache is lost after the script ends. For more information about the local property cache, see “Step 2: Performing a Task” in the preceding section.

The act of saving an object to the directory is called committing. Committing an object to Active Directory is analogous to saving a document that you created in a word processing application. The act of saving the document writes the data in memory to a location on your hard disk, just as the act of calling the SetInfo method writes the data in memory (the local property cache) to Active Directory.

The code for committing an object to the directory includes the variable name of the object, a dot, and the SetInfo method call. Previously, you learned that Create, Get, Put, and Delete are method calls to complete a task. SetInfo is simply another method call whose sole task is to save objects or attributes to a directory service. Notice that this method call is the last line in all script examples in the previous Create and Modify task sections.

The code listings in “Reading Attributes of Directory Service Objects” earlier in this chapter do not contain the SetInfo method because these tasks simply bind to an object in Active Directory and read an attribute of an object. The object is not modified, so there is no need to save any changes to Active Directory. This is analogous to reading a word processing document without making any changes to it. While you can save the document to the disk, there is no reason to do so.

The code listings in “Deleting Directory Service Objects” earlier in this chapter do not contain the SetInfo method because the Delete method deletes objects from Active Directory immediately.

Performing Multiple Scripting Tasks

Each of the previous code examples demonstrated one of the primary ADSI scripting task categories by completing a single task — creating one object, deleting one object, writing one attribute, and reading one attribute. It is more common, however, to perform more than a single task in an ADSI script.

Large scripts start from a discrete set of tasks and tie them together to meet some larger administrative goal. Think about each script task as a building block. Each building block is useful in itself, but properly cementing the blocks together creates a larger, more functional structure, just as tying scripting tasks together makes a more robust and useful script.

When you are presented with a large, sophisticated script, you might be able to run it right away. But if you try to use it as a learning tool, it will be difficult to know where to start unless the script is broken down into its component parts. Therefore, consider documenting your scripts carefully so that other administrators can understand the purpose of your scripts and troubleshoot them when necessary.

Many production scripts include a significant amount of code that has nothing directly to do with ADSI —one reason why you might have found other scripts hard to follow. The ADSI scripts included with the Microsoft® Windows® 2000 Server Resource Kit highlight this point. For example, the Resource Kit’s ListDCs.vbs ADSI script is 454 lines long. Of the 454 lines, more than 400 lines have nothing to with ADSI. Why are they there? They are there because ListDCs.vbs is designed to be a general-purpose utility much like a command-line executable. As such, the script includes routines to parse command line arguments, format output, and handle errors. Although the routines are important to ListDCs.vbs, they are not part of ADSI technology. Admittedly, there are times when it is necessary to use some non-ADSI code in an ADSI script. The script code in this chapter, however, avoids non-ADSI scripting code as much as possible so that you can clearly see what ADSI scripting is.

To successfully create a script that writes and reads multiple attributes of an Active Directory object, you combine tasks described earlier in this chapter:

  1. Bind to the object. This task requires you to know the path to the object in order to create script code that binds to the object.

  2. Write or modify attributes. This task requires you to know certain characteristics of the attributes you want to write or modify. For example, you must know each attribute’s lDAPDisplayName. To complete this task, you must save changes to Active Directory.

  3. Read attributes. Reading attributes requires knowledge of the same information needed to write or modify attributes.

Complete the procedures in this section to perform write and read operations against an Active Directory user account object.

Preliminary Steps

  1. Start Notepad, and save a file to the C:scripts folder named ModifyUser.vbs.

    As you read this section, copy and paste the appropriate code lines into ModifyUser.vbs.

  2. Verify that you have previously created the MyerKen user account object in Active Directory. If not, create the user account in an OU or the Users container of the Active Directory domain to which you are connected.

Binding to a User Account Object

Complete one of the following steps to add the appropriate binding code line into ModifyUser.vbs:

  • If the account object is located in the HR OU of the na.fabrikam.com domain, start with the following code statement:

    Set objUser = _
        GetObject("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
    

    If you have created an OU of a different name and you do not have a domain named na.fabrikam.com, use the code in this step but change the DN to the Active Directory container where for the test account named MyerKen. For example, if your domain is named contoso.com and the OU that contains the MyerKen user account is named Test, the code statement is:

    Set objUser = _
        GetObject("LDAP://cn=MyerKen,ou=Test,dc=contoso,dc=com")
    
  • If you have created a test user account named MyerKen in the Users container in an Active Directory domain to which you are currently connected, start with the following code statement:

    Set objRootDSE = GetObject("LDAP://rootDSE")
    Set objUser = GetObject("LDAP://cn=MyerKen,cn=Users," & _
        objRootDSE.Get("defaultNamingContext"))
    

    This code is similar to the code in Listing 5.1 that is used to bind to Active Directory.

Writing or Modifying Attributes

To complete this procedure, you need to determine the lDAPDisplayNames of the attributes you want to write to the user account object. Figure 5.5 shows the General Properties page of the MyerKen user account and the corresponding lDAPDisplayName of each attribute that appears as a label on the page.

General Properties Page of the MyerKen User Account Object

Figure 5.5. General Properties Page of the MyerKen User Account Object

Notice that there is no attribute with an lDAPDisplayName of First name or Last name. Thus the labels on the property pages of an Active Directory object are an unreliable information source for determining lDAPDisplayNames.

There are other ways you can refer to an Active Directory object’s attributes in ADSI scripts, but the most consistent approach is by using their lDAPDisplayName. This is also the only way that enables you to write an attribute using the Put method.

To keep this procedure as straightforward as possible, the attributes that can contain more than one value (called multivalued attributes) will not be modified in the script. These are the otherTelephone and url attributes. For information about writing and reading multivalued attributes, see “Administering Multivalued Attributes” later in this chapter.

  1. To create code that writes or modifies attributes of a user account object, place the following code below the lines of code that bind to the user account object:

    objUser.Put "givenName", "Ken"
    objUser.Put "initials", "E."
    objUser.Put "sn", "Myer"
    objUser.Put "displayName", "Myer, Ken"
    objUser.Put "description", "HR employee"
    objUser.Put "physicalDeliveryOfficeName", "Room 4358"
    objUser.Put "telephoneNumber", "(425) 707-9790"
    objUser.Put "mail", "[email protected]"
    objUser.Put "wWWHomePage", "http://www.fabrikam.com"
    
  2. To create code that saves the modifications back to the corresponding object in Active Directory, place the following code statement below the lines of code in the previous step:

    objUser.SetInfo
    

Reading and Displaying the Modified Attributes

After the script modifies the user account object in Active Directory, you can either review the settings on the General Properties page of the MyerKen user account or add to the ADSI script so that it reads and displays the modifications.

  • To create code that reads the attributes modified in the Writing or Modifying Attributes procedure, copy and paste the following code into ModifyUser.vbs below the line of code containing the SetInfo method call:

    strGivenName = objUser.Get("givenName")
    strInitials = objUser.Get("initials")
    strSn = objUser.Get("sn")
    strDisplayName = objUser.Get("displayName")
    strPhysicalDeliveryOfficeName = _
        objUser.Get("physicalDeliveryOfficeName")
    strTelephoneNumber = objUser.Get("telephoneNumber")
    strMail = objUser.Get("mail")
    strWwwHomePage = objUser.Get("wWWHomePage")
    
  • To create code that displays the modified attributes, copy and paste the following code into ModifyUser.vbs below the lines of code in the preceding step:

Wscript.Echo "givenName: " & strGivenName
Wscript.Echo "initials: " & strInitials
Wscript.Echo "sn: " & strSn
Wscript.Echo "displayName: " & strDisplayName
Wscript.Echo "physicalDeliveryOfficeName: " & _
    strPhysicalDeliveryOfficeName
Wscript.Echo "telephoneNumber: " & strTelephoneNumber
Wscript.Echo "mail: " & strMail

Advanced ADSI Scripting Operations

There are a number of Active Directory administration tasks that go beyond performing basic create, modify, read, and delete operations. Some of the other administrative tasks you can perform with ADSI scripts are:

  • Administering multivalued attributes.

  • Copying, moving, and renaming objects.

  • Data caching.

  • Searching.

  • Enumerating Active Directory objects in containers.

  • Using the Root Directory Service Entry.

Administering Multivalued Attributes

The scripts earlier in this chapter, including the procedures in “Performing Multiple Scripting Tasks,” demonstrated how to read and write single-valued attributes. However, single-valued attributes cannot hold multiple records that are required for storing certain types of information. Multivalued attributes accommodate list type information. The attribute that contains a list of bridgehead servers configured in a domain, the attribute that contains a list of members in a group, and the attribute that contains a list of secondary telephone numbers for a user account are all examples of multivalued attributes.

Note

Note

For information about how to determine whether an attribute is single-valued or multivalued, see “Active Directory Architecture” later in this chapter.

Modifying and reading a multivalued attribute is similar to modifying and reading a single-valued attribute: the script binds to an object, uses a method to perform the modify or read task on an attribute, and, for the modify task, uses the SetInfo method to commit the change to Active Directory.

Modifying Multivalued Attributes

When an attribute can contain multiple values or entries, there are a number of possible modifications:

  • Clear all entries. Clearing all entries removes the attribute from the target object. From the perspective of an ADSI script, when the attribute contains no values it is no longer a part of the object.

  • Update all entries. Updating all entries removes any existing entries and then writes one or more new entries to the attribute.

  • Append an entry. Appending an entry adds one or more values to the attribute while preserving any existing entries.

  • Delete an entry. Deleting an entry removes one or more values from the attribute while preserving any other existing entries.

Regardless of the type of multivalued attribute modification you want to make, you can use the PutEx method to make the modification. When you call the PutEx method in a script, the first parameter (also known as an argument) of the method designates the type of update you want to make, the second argument designates the attribute to be modified, and the third argument designates the values to set. Table 5.2 shows the arguments of the PutEx method.

Table 5.2. PutEx Arguments

Argument

Type

Required

Default

Description

Control Code

Integer (long)

Yes

None

The value 1 clears all entries, the value 2 updates all entries, the value 3 appends one or more entries, and the value 4 deletes one or more entries.

Attribute

String

Yes

None

The lDAPDisplayName of the attribute to be modified.

Value

Variant (array)

Yes

None

To update, append, or delete entries, surround each value with quotes and, if specifying multiple values, delimit each value with a comma.

To clear an attribute, set this parameter to 0.

Tip

Tip

To make your ADSI scripts easier to read, use constants to designate the values of the PutEx control code parameter. The constants are:

  • ADS_PROPERTY_CLEAR = 1

  • ADS_PROPERTY_UPDATE = 2

  • ADS_PROPERTY_APPEND = 3

  • ADS_PROPERTY_DELETE = 4

One or more of these constants appears in all scripts in this chapter that use the PutEx method.

Modifying multivalued attributes of Active Directory objects involves three basic steps. Note that defining a constant for the control code parameter is optional, so it does not appear in the following steps.

  1. Bind to the Active Directory object you want to modify.

  2. Perform clear, update, add, or delete operations on one or more of an object’s multivalued attributes.

  3. Commit the change to Active Directory.

The goal of the first four scripts that appear in this section is to manage group membership by modifying the member multivalued attribute of the Atl-Users global group. The attribute will be modified by updating all members, adding a member, deleting a member, and clearing all members, in that order. The fifth script in this section shows how to update multivalued attributes of a user account object. The purpose of this script is to demonstrate how administering multivalued attributes is similar from one object to the next.

Restrictions in performing modifications by using the PutEx method

PutEx does not allow you to add duplicate entries to a multivalued attribute. For example, you cannot add duplicate telephone numbers to the otherTelephone attribute of a user account object or add duplicate user accounts to the member attribute of a group object. PutEx does not report an error if a script attempts to add a duplicate entry. However, when the SetInfo method is called, the script will fail and report that the object already exists.

PutEx will report an error if you attempt any attribute modification and specify a nonexistent object for an object-dependent entry. That is, if the entry a script attempts to add specifies an Active Directory object that does not exist, the script will end and report that there is no such object on the server. For example, if a script attempts to add the distinguished name of a user account to a Group but the user account object does not exist, the script will fail.

PutEx also does not require an entry to be present to attempt a delete operation. If a script specifies a nonexistent value to delete, PutEx will run normally but will not report an error. However, when the SetInfo method is called, the script will fail and report that the server is unwilling to process the request.

In the following listings, all PutEx operations are committed by a single SetInfo method call. However, whenever you perform more than one operation on the same multivalued attribute, commit the change for each operation before continuing to the next operation. Consider the following code example:

objUser.PutEx ADS_PROPERTY_DELETE, "otherTelephone", Array("707-9790")
objUser.PutEx ADS_PROPERTY_APPEND, "otherTelephone", Array("707-9799")
objUser.SetInfo

The number 707-9799 is added as an entry to the otherTelephone attribute when SetInfo is called, but the number 707-9790 is not deleted. To commit both changes to the directory, use this code instead:

objUser.PutEx ADS_PROPERTY_DELETE, "otherTelephone", Array("707-9790")
objUser.SetInfo
objUser.PutEx ADS_PROPERTY_APPEND, "otherTelephone", Array("707-9799")
objUser.SetInfo

Another important nuance of using PutEx is that the order of the entries stored in multivalued attributes is not guaranteed. Therefore, whenever you write scripts that operate on multivalued attributes, do not depend on a specific ordering of entries.

Clearing multivalued and single-valued attributes with the PutEx method

Using the PutEx method to clear attributes applies to both single and multivalued attributes. It is important for single-valued attributes because the Put method cannot completely clear an attribute. For example, you cannot specify NULL or “” for an attribute’s value when calling Put. In addition, although the following code will work to update the telephoneNumber attribute, the resulting telephoneNumber attribute is not empty. Instead, it contains a single space.

objUser.Put "telephoneNumber", " "

Therefore, PutEx is the only method capable of completely clearing one or more entries from an attribute.

Updating a Multivalued Attribute of a Group

The script in Listing 5.14 modifies the member attribute of the group named Atl-Users. The member attribute is updated so that it contains two members. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_UPDATE constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in lines 5–9).

    This control code replaces any existing entries in a multivalued attribute.

  2. Bind to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  3. Update the object’s member attribute by assigning the distinguished names of the MyerKen and LewJudy user accounts to it. Because PutEx is designed to work with multiple attributes, the two user accounts must be passed to the method in an array.

    Notice that the MyerKen user account is located in the HR OU and the LewJudy user account is located in the Sales OU, both in the na.fabrikam.com domain.

  4. Commit the change to the group in Active Directory.

    Because an update operation is specified in the script, any existing members of the Atl-Users group are removed.

Example 5.14. Updating the member Attribute of a Group

1 Const ADS_PROPERTY_UPDATE = 2
2 Set objGroup = GetObject _
3     ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
4
5 objGroup.PutEx ADS_PROPERTY_UPDATE, _
6     "member", Array("cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com", _
7         "cn=LewJudy,ou=Sales,dc=NA,dc=fabrikam,dc=com")
8
9 objGroup.SetInfo

Appending an Entry to a Multivalued Attribute of a Group

The script in Listing 5.15 appends an entry to the member attribute of the group named Atl-Users. Assuming that the script in Listing 5.14 has been run, the script in Listing 5.15 appends an entry to the member attribute so that it contains a third member. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_APPEND constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in lines 5–8).

    This control code adds one or more entries to a multivalued attribute.

  2. Bind to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  3. Append an entry to the object’s member attribute by specifying the ADS_PROPERTY_APPEND constant, the member attribute, and the distinguished name of the YoungRob user account object.

    Notice that the YoungRob user account is located in the R&D OU in the na.fabrikam.com domain.

  4. Commit the change to the group in Active Directory.

    Because an append operation is specified in the script, any existing members of the Atl-Users group are preserved.

Example 5.15. Appending an Entry to the member Attribute of a Group

1 Const ADS_PROPERTY_APPEND = 3
2 Set objGroup = GetObject _
3    ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
4
5 objGroup.PutEx ADS_PROPERTY_APPEND, _
6     "member", Array("cn=YoungRob,ou=R&D,dc=NA,dc=fabrikam,dc=com")
7
8 objGroup.SetInfo

Deleting an Entry from a Multivalued Attribute of a Group

The script in Listing 5.16 deletes an entry from the member attribute of the group named Atl-Users. Assuming that the scripts in Listing 5.14 and Listing 5.15 are run, the script in Listing 5.16 deletes an entry from the member attribute so that only two members remain. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_DELETE constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in lines 5–8).

    This control code deletes one or more entries from a multivalued attribute.

  2. Bind to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  3. Delete an entry from the object’s member attribute by specifying the ADS_PROPERTY_DELETE constant, the member attribute, and the distinguished name of the MyerKen user account object.

  4. Commit the change to the group in Active Directory.

    Because a delete operation is specified in the script, any existing members of the Atl-Users group are preserved except for the MyerKen user account.

Example 5.16. Deleting an Entry from the member Attribute of a Group

1 Const ADS_PROPERTY_DELETE = 4
2 Set objGroup = GetObject _
3    ("LDAP://cn=Atl-Users,OU=HR,dc=NA,dc=fabrikam,dc=com")
4
5 objGroup.PutEx ADS_PROPERTY_DELETE, _
6     "member", Array("cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
7
8 objGroup.SetInfo

Clearing a Multivalued Attribute of a Group

The script in Listing 5.17 clears the member attribute of the group named Atl-Users. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_CLEAR constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in line 5).

    This control code clears a multivalued attribute so that, from the script’s perspective, the attribute is empty and thus is no longer associated with the object.

  2. Bind to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  3. Clear the object’s member attribute so that it is empty.

    Notice that the last parameter is set to 0. This value is necessary and must be specified in order to successfully clear an attribute.

  4. Commit the change to the group in Active Directory.

Example 5.17. Clearing the member Attribute of a Group

1 Const ADS_PROPERTY_CLEAR = 1
2 Set objGroup = GetObject _
3    ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
4
5 objGroup.PutEx ADS_PROPERTY_CLEAR,"member", 0
6
7 objGroup.SetInfo

Updating Multivalued Attributes of a User Account

Regardless of the Active Directory object, the way that you modify the multivalued attributes of an object is the same. To demonstrate this uniformity, the script in Listing 5.18 updates the url and otherTelephone multivalued attributes of the MyerKen user account. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_UPDATE constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in lines 5–10 and lines 12 and 13).

  2. Bind to the MyerKen user account in the HR OU of the na.fabrikam.com domain.

  3. Update the object’s url and otherTelephone attributes by assigning values to them.

    Notice that the values for the url attribute are in the form of Web addresses and that the values for the otherTelephone attribute are in the form of telephone numbers. However, unlike the member attribute of a group object, these attributes are not limited to specific string formats. In other words, you can insert values that do not comply with any defined format for a url or a telephone number.

  4. Commit the change to the group in Active Directory.

    Because an update operation is specified in the script, any existing values for these two attributes are removed.

Example 5.18. Updating the url and otherTelephone Attributes of a User Account

 1 Const ADS_PROPERTY_UPDATE = 2
 2 Set objGroup = GetObject _
 3    ("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
 4
 5 objGroup.PutEx ADS_PROPERTY_UPDATE, _
 6     "url", Array("http://www.microsoft.com", _
 7          "http://www.fabrikam.com/na","http://www.contoso.com")
 8
 9 objGroup.PutEx ADS_PROPERTY_UPDATE, _
10    "otherTelephone", Array("707-9790", "707-9791", "707-9792")
11
12 objGroup.SetInfo

Important observations about the scripts in this section are:

  • They perform the same basic steps: They bind to an Active Directory object, modify a multivalued attribute of the object, and write the change to the corresponding Active Directory object.

  • They use the same method (PutEx) without regard to the class of object being modified.

    The PutEx method is similar to the Put method except that it requires a control code parameter to specify the type of modify operation.

Reading Multivalued Attributes

Because a multivalued attribute of an object can contain more than one value, you use the GetEx method to retrieve all values of the attribute. The information retrieved from GetEx is returned in the form of a variant array. An array is a data structure containing one or more values, and a variant is a data structure containing any data type, such as a string or an integer. Thus a variant array is a data structure containing one or more values of any type.

Important

Important

The Get method can also return entries in a multivalued attribute, but as a general rule, use Get for single-valued attributes only. For more information, see the section “Data Caching.”

To display the values in an array, you must add script code that is able to iterate through the array of values. Use the VBScript For Each statement to accomplish this task.

Even with the additional scripting code (that is, the For Each statement) that you must add to read a multivalued attribute, reading this type of attribute involves the same two steps outlined in “Reading Attributes of Directory Service Objects” earlier in this chapter:

  1. Connect to the Active Directory object you want to read.

  2. Read one or more of the object’s attributes.

    The difference in this step is that you use the GetEx method instead of the Get method to complete the read operation and you use a For Each statement to display each value contained in the attribute.

The goal of the scripts in this section is to read and display the member attribute of the Atl-Users group and the url and otherTelephone attributes of the MyerKen user account. These attributes were modified in “Modifying Multivalued Attributes” earlier in this chapter.

Reading a Multivalued Attribute of a Group

The script in Listing 5.19 reads and displays the entries in the member attribute of the Atl-Users group. If you ran the scripts in the preceding section, the member attribute is now empty. Therefore, rerun the script in Listing 5.14 before running this script.

  1. Connect to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  2. Use a For Each statement to read and display the object’s member attribute.

Example 5.19. Reading the member Attribute of an OU

1 Set objGroup = GetObject _
2     ("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
3
4 For Each Member in objGroup.GetEx("member")
5     Wscript.Echo Member
6 Next

When this script runs in the na.fabrikam.com domain, it echoes the members of the Atl-Users group to the command window, as shown:

CN=LewJudy,OU=Sales,dc=NA,DC=fabrikam,DC=com
CN=MyerKen,OU=HR,dc=NA,DC=fabrikam,DC=com

Reading Multivalued Attributes of a User Account

The script in Listing 5.20 reads and displays the entries in the url and otherTelephone attributes of the MyerKen user account.

  1. Connect to the MyerKen user account in the HR OU of the na.fabrikam.com domain.

  2. Use a For Each statement to read and display the object’s url attribute.

  3. Use another For Each statement to read and display the object’s otherTelephone attribute.

Example 5.20. Reading the url and otherTelephone Attributes of a User Account

 1 Set objGroup = GetObject _
 2     ("LDAP://cn=MyerKen,ou=HR,dc=NA,dc=fabrikam,dc=com")
 3
 4 For Each url in objGroup.GetEx("url")
 5     Wscript.Echo url
 6 Next
 7
 8 For Each otherTelephone in objGroup.GetEx("otherTelephone")
 9     Wscript.Echo otherTelephone
10 Next

When this script runs in the na.fabrikam.com domain, it echoes the url and otherTelephone attributes of the MyerKen user account to the command window, as shown:

http://www.contoso.com
http://www.fabrikam.com/na
http://www.microsoft.com
707-9792
707-9791
707-9790

Important observations about the scripts in this section are:

  • They perform the same basic steps: they bind to an Active Directory object and use a For Each statement to read the entries contained in multivalued attributes.

  • They use the same method (GetEx) without regard to the class of object being read.

    The GetEx method is similar to the Get method except that you must use a For Each statement to read and display an attribute’s values.

Data Caching

Thus far, little has been said about how ADSI scripts use the local property cache to complete read operations. You know from the preceding sections that you use either the Get or the GetEx method to perform read operations. Behind the scenes, both of these methods make implicit calls to the GetInfo method to retrieve attributes from Active Directory and load them into the local property cache. You can make an explicit call to GetInfo or use another method, GetInfoEx, to take control of the data loaded into the local property cache. For more information about the local property cache, see “Step 2: Performing a Task” earlier in this chapter.

Understanding how these four method calls interact with the local property cache will help you avoid accidentally overwriting values, and it can also help you make scripts operate more efficiently.

How the Get Method Retrieves Attributes

The Get method retrieves the value or values of attributes from the local property cache as either a string or a variant array, depending on the type of attribute and its contents:

  • Single-valued attributes. Get returns a string value.

  • Multivalued attributes containing a single entry. Get returns a string value.

  • Multivalued attributes containing more than one entry. Get returns a variant array of values.

Important

Important

An attribute that does not contain a value is not considered by ADSI to be associated with an Active Directory object. Therefore, empty attributes are never downloaded to the local property cache.

All of the preceding examples suggest that you should always use Get for single-valued attributes and GetEx for multivalued attributes. Following this rule will simplify the scripting process because then you will not have to determine whether the values are returned as a string or a variant array. However, if you do know whether your target attribute is single-valued or multivalued and you know how many values an attribute contains, using Get is slightly more efficient than GetEx.

If the attribute is not already loaded into the local property cache, the Get method implicitly calls the GetInfo method. As a result, GetInfo loads most attributes into the local property cache, with the exception of operational attributes and attributes that have already been downloaded to the cache.

Operational attributes are attributes whose values are not stored in a directory service but are calculated by the domain controller when they are requested. For example, canonicalName is an operational attribute. Operational attributes are synonymous with constructed attributes.

If you use the Get method again in a script, the specific attribute requested will be retrieved from the property cache without any additional calls to the GetInfo method. Therefore, attribute values that already exist in the local property cache will not be overwritten by an implicit call to the GetInfo method.

How the GetEx Method Retrieves Attributes

The GetEx method always returns the value or values of the specified attribute from the property cache in a variant array. Thus, you must always use a statement such as For Each to read even a single-valued attribute. However, if you do not know whether an attribute is single-valued or multivalued, GetEx can be easier to use than Get because all returned data is packaged in a variant array. This means you can handle all attribute data using the same approach.

If the attribute is not already loaded into the local property cache, the GetEx method implicitly calls the GetInfo method to load all attributes of an object. Attribute values that already exist in the local property cache will not be overwritten by the implicit call to the GetInfo method.

Making Explicit Calls by Using the GetInfo Method

You can explicitly call the GetInfo method to load or refresh the local property cache. When called explicitly, this method retrieves all attributes (except for operational attributes) associated with an object from Active Directory and loads them into the local property cache. Call the GetInfo method if you need to be sure that the attribute values in the local property cache are the same as the attribute values of the corresponding Active Directory object.

The syntax for the GetInfo method is:

object. GetInfo

The script in Listing 5.21 downloads most attributes of a group object named Atl-Users.

  1. Bind to the Atl-Users group in the HR OU of the na.fabrikam.com domain.

  2. Use the GetInfo method to explicitly download most of the group’s attributes into the local property cache.

Example 5.21. Making an Explicit Call to GetInfo

1 Set objGroup = _
2    GetObject("LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com")
3 objGroup.GetInfo

Exercise caution when explicitly calling the GetInfo method because any modification made to attributes in the local property cache are lost if the SetInfo method is not called before the local property cache is refreshed with GetInfo. For example, line 2 of the following code writes the value Human Resources to the description attribute in the local property cache. Line 3 then commits the value to the description attribute of the HR OU in Active Directory.

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 objOU.Put "description", "Human Resources"
3 objOU.SetInfo

However, you might add the GetInfo method in the following way:

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2 objOU.Put "description", "Human Resources"
3 objOU.GetInfo
4 objOU.SetInfo

Line 2 still writes the value Human Resources to the description attribute, but then line 3 refreshes the local property cache, thereby overwriting the value Human Resources in the cache. In contrast, if GetInfo is called implicitly (using Get or GetEx), attribute values already in the local property cache are not overwritten. The only exception to this is if you use Get or GetEx to download the specific attribute that you are modifying in the previous step. This, of course, defeats the purpose of modifying the attribute.

Making Explicit Calls by Using the GetInfoEx Method

Rather than download most attributes of an object, you can selectively load or refresh the local property cache with a specific attribute or set of attributes by using the GetInfoEx method. In addition, you must use this method to download operational attributes.

Using GetInfoEx is an ideal approach to working with attributes if you want to minimize the load placed on the domain controller servicing the request and the minimize network traffic that results from downloading attributes.

The syntax for this method is:

object. GetInfoEx Attributes, 0

The Attributes parameter of GetInfoEx is an array containing the lDAPDisplayName of each attribute. The second parameter (0) is reserved and must be specified when calling the GetInfoEx method.

You can initialize a variable with an array containing the lDAPDisplayName of one or more attributes and then use that variable in the GetInfoEx call. The script in Listing 5.22 downloads two attributes, description and dnsHostName, of a computer object named SEA-SQL-01.

  1. Bind to the SEA-SQL-01 computer in the Computers container of the na.fabrikam.com domain.

  2. Initialize a variable named arrAttributes with an array containing the lDAPDisplayName of two attributes, description and dnsHostName (line 3).

  3. Use the GetInfoEx method to explicitly download attributes specified in the arrAttributes variable into the local property cache (line 4).

    The attributes downloaded are the two specified in the arrAttributes variable in line 3.

Example 5.22. Making an Explicit Call to GetInfoEx

1 Set objComputer = GetObject _
2    ("LDAP://cn=SEA-SQL-01,cn=Computers,dc=NA,dc=fabrikam,dc=com")
3 arrAttributes = Array("description", "dnsHostName")
4 objComputer.GetInfoEx arrAttributes, 0

As with the GetInfo method, you must exercise caution when combining the GetInfoEx method with a script that modifies the attributes that GetInfoEx downloads. Modifications to attributes are lost if the SetInfo method is not called before the local property cache is refreshed with GetInfoEx.

For example, lines 3 and 4 of the following code write values to the description and dnsHostName attributes in the local property cache. However, the values are overwritten when these attributes are refreshed using the GetInfoEx method in line 7. When the SetInfo method is called in line 8, the values of the two attributes downloaded by GetInfoEx are written back to Active Directory instead of the values assigned in lines 3 and 4.

1 Set objComputer = GetObject _
2    ("LDAP://cn=SEA-SQL-01,cn=Computers,dc=NA,dc=fabrikam,dc=com")
3 objComputer.Put "description", "SQL Computer 1"
4 objComputer.Put "dnsHostName", "sea-sql-01.na.fabrikam.com"
5
6 arrAttributes = Array("description", "dnsHostName")
7 objComputer.GetInfoEx arrAttributes, 0
8 objComputer.SetInfo

By moving line 8 (the SetInfo method call) up to line 5, the two attributes written to the local property cache by the Put method calls are committed to Active Directory. When GetInfoEx selectively refreshes the cache in line 7, the values match those previously written to the cache by the Put method calls.

Copying, Moving, and Renaming Objects

In the management life cycle of Active Directory objects, common tasks not part of the four primary administrative tasks (create, delete, read, and modify) include copying, moving, and renaming. For example, you can copy objects to speed object creation, move objects if you need to delete a container but preserve its contents, and rename objects to comply with new company naming standards.

Copying Objects

To copy an object, you need a source object from which to make a copy. You must also determine which attributes of the source object you want to duplicate in the target object (the copy).

To copy an object, you simply combine some of the primary tasks covered earlier in this chapter:

  1. Complete the create task to create the target object.

    For task details, see “Creating Directory Service Objects” earlier in this chapter.

  2. Complete the read task to obtain attributes from the source object that should be copied to the target object.

    For task details, see “Reading Attributes of Directory Service Objects” and “Reading Multivalued Attributes” earlier in this chapter.

  3. Complete the modify task to write selected attributes to the target object.

    For task details, see “Modifying Directory Service Objects” and “Modifying Multivalued Attributes” earlier in this chapter.

The goal of the scripts in this section is to demonstrate how to copy objects. The first example copies a source computer object to a target computer object. This example demonstrates that ADSI scripts work with other Active Directory objects besides users, groups, and OUs. The second example copies a source user account to a target user account. This example demonstrates how to copy both single-valued and multivalued attributes.

Copying a Computer Account

The script in Listing 5.23 copies selected attributes from a computer account named SEA-PM-01 to a new computer account named SEA-SQL-01. The account to be used as a template (SEAPM- 01) is located in the Computers container of the na.fabrikam.com domain. When the new computer account (SEA-SQL-01) is created, it will also be located in the Computers container.

  1. Bind to the Computers container in the na.fabrikam.com domain.

    Notice that the Computers container has the attribute type CN. Except for the Domain Controllers container, all built-in containers directly below an Active Directory domain have the attribute type CN rather than OU.

  2. Create a computer object named sea-sql-01.

  3. Set the sAMAccountName mandatory attribute to the value sea-sql-01.

  4. Commit the new computer object to Active Directory.

    This step completes the task of creating the new computer object.

  5. Bind to the SEA-PM-01 computer object in the Computers container of the na.fabrikam.com domain.

    This computer object serves as the template object from which attributes are derived for the copy operation.

  6. Create an array named arrAttributes that contains the lDAPDisplayNames of the optional attributes that will be applied to the new computer object.

  7. Use a For Each statement to loop through the lDAPDisplayNames of the attributes specified in the array.

    1. Read each attribute from the computer object serving as the template.

      This step completes the task of reading the template computer object.

    2. Write the attributes to the new computer object.

  8. Commit the change to the new computer object in Active Directory.

    This step completes the task of modifying the new computer object. Notice that it was not necessary to bind again to the new computer object. The binding operation completed in line 2 provided the necessary connection to the new computer object.

Example 5.23. Copying a Computer Object

 1 Set objCompt = GetObject("LDAP://cn=Computers,dc=NA,dc=fabrikam,dc=com")
 2 Set objComptCopy = objCompt.Create("computer", "cn=SEA-SQL-01")
 3 objComptCopy.Put "sAMAccountName", "sea-sql-01"
 4 objComptCopy.SetInfo
 5
 6 Set objComptTemplate = _
 7     GetObject("LDAP://cn=SEA-PM-01,cn=Computers,dc=NA,dc=fabrikam,dc=com")
 8 arrAttributes = Array("description", "location")
 9
10 For Each strAttrib in arrAttributes
11     strValue = objComptTemplate.Get(strAttrib)
12     objComptCopy.Put strAttrib, strValue
13 Next
14
15 objComptCopy.SetInfo

Listing 5.23 demonstrated how to copy two attributes from one object to another. However, many more attributes can be copied. The purpose of the preceding listing was simply to show how you can perform a copy operation by writing attributes from one object to another. In fact, you can copy both single-valued and multivalued attributes from one object to another, as the next listing demonstrates.

Copying a User Account

The script in Listing 5.24 copies selected single-valued and multivalued attributes from a user account named HuffArlene to a new user account named BarrAdam. The user account to be used as a template (HuffArlene) is located in the HR OU of the na.fabrikam.com domain. When the new user account (BarrAdam) is created, it will also be located in the HR OU.

  1. Set the ADS_PROPERTY_UPDATE constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in line 21).

  2. Bind to the HR OU in the na.fabrikam.com domain.

  3. Create a user account named BarrAdam.

  4. Set the sAMAccountName mandatory attribute to the value barradam.

  5. Commit the new user account to Active Directory.

  6. Bind to the HuffArlene user account in the HR OU of the na.fabrikam.com domain.

  7. Create an array named arrSVAttributes that contains the lDAPDisplayNames of the optional single-valued attributes that will be applied to the new user account.

  8. Create another array named arrMVAttributes that contains the lDAPDisplayNames of the optional multivalued attributes that will be applied to the new user account.

  9. Use a For Each statement to iterate through the lDAPDisplayNames of the attributes specified in the arrSVAttributes array.

    Within the loop, use the Get method to read each attribute from the template user account and use the Put method to write it to the new user account.

  10. Use another For Each statement to iterate through the lDAPDisplayNames of the attributes specified in the arrMVAttributes array.

    Within the loop, use the GetEx method to read each attribute from the template user account and use the PutEx method to write it to the new user account.

  11. Commit the changes to the new user account in Active Directory.

Example 5.24. Copying a User Account

 1 Const ADS_PROPERTY_UPDATE = 2
 2 Set objOU = GetObject("LDAP://OU=HR,dc=NA,dc=fabrikam,dc=com")
 3 Set objUserCopy = objOU.Create("user", "cn=BarrAdam")
 4 objUserCopy.Put "sAMAccountName", "barradam"
 5 objUserCopy.SetInfo
 6
 7 Set objUserTemplate = _
 8     GetObject("LDAP://cn=HuffArlene,ou=HR,dc=NA,dc=fabrikam,dc=com")
 9
10 arrSVAttributes = Array("description", "department", _
11    "company", "wWWHomePage")
12 arrMVAttributes = Array("url", "otherTelephone")
13
14 For Each strAttrib in arrSVAttributes
15     strValue = objUserTemplate.Get(strAttrib)
16     objUserCopy.Put strAttrib, strValue
17 Next
18
19 For Each strAttrib in arrMVAttributes
20     arrValue = objUserTemplate.GetEx(strAttrib)
21     objUserCopy.PutEx ADS_PROPERTY_UPDATE, strAttrib, arrValue
22 Next
23
24 objUserCopy.SetInfo

Important observations about the scripts in this section are:

  • Copying an Active Directory object involves the following three tasks: Create an object, read attributes from a different object, and write the attributes to the new object.

  • No method is available to the LDAP provider for directly performing the copy task.

Moving and Renaming Objects

The move and rename tasks use the same method, MoveHere, to complete their respective operations. Unlike the copy task, the move and rename tasks are relatively simple to complete with an ADSI script. The MoveHere method supports the following Active Directory move and rename operations:

  • Moving an object to a different container within the same domain

  • Renaming an object within the same container

  • Renaming and moving an object to a different container within the same domain

  • Moving an object to another domain

  • Renaming an object while moving it to another domain

You cannot use the MoveHere method to move an object to another forest.

General Conditions for Cross-Domain Moves

There are some object-specific restrictions to making cross-domain moves. However, moving an object to other domains within the same forest is possible when the following general conditions are met:

  • The destination domain is running in native mode.

  • Both the destination and the source domain use Kerberos authentication.

  • The move operation must be completed from the source domain to the destination, or target, domain. If you attempt to move an object while logged on to the destination domain, the following message will appear:

    (null): Inappropriate authentication
    
  • To move an object from one domain to another, you must have permission to remove the object from the source domain and add the object to the target domain.

  • Only leaf objects can be moved. Therefore, if the object is a container, make sure it is empty before attempting to move it to another domain.

  • Security principals, such as users, groups, and computers cannot be moved to another domain if they are members of one or more global groups. They must first be removed from all global groups.

When you call the MoveHere method in a script, the first parameter of the method designates the ADsPath of the move or rename operation. The DN in the ADsPath designates where the object currently resides. The second argument designates the relative distinguished name of the object to be moved or the new name of the object to be renamed. If your intention is simply to move and not rename the object, the second parameter can be specified as vbNullString. Table 5.3 shows the arguments of the MoveHere method.

Table 5.3. MoveHere Arguments

Argument

Type

Required

Default

Description

ADsPath

string

Yes

None

The name of the provider and the DN of the source object to move or rename.

RelativeDistinguishedName

string

Yes

None

The cn=name attribute of the user account object to be moved or the new name of the user account to be renamed. If you are not renaming the account, you can specify vbNullString instead.

Regardless of the move or rename task you want to complete, both tasks involve two simple steps:

  1. Bind to the Active Directory container that is the target of the object move or rename operation.

  2. Move or rename the object.

    Like the Delete method, the MoveHere method acts immediately upon Active Directory. Therefore, there is no need to call the SetInfo method to complete a rename or move operation.

The goal of the three scripts in this section is to rename a published printer, move a group from one OU to another within the same domain, and move an OU from one domain to another. These three objects and tasks provide a good sampling of what can be done with the MoveHere method. For more information about how to complete object-specific move operations, see the ADSI task-based chapters in this book.

Renaming a Published Printer

The script in Listing 5.25 renames the Printer1 printer to HRPrn1. After the rename operation, the printer remains in the HR OU of the na.fabrikam.com domain.

  1. Bind to the HR OU container in the na.fabrikam.com domain.

    This binding operation designates the HR OU as the target container of the rename operation.

  2. Rename the Printer1 printer to HRPrn1.

    The first parameter of the MoveHere method specifies the ADsPath of the object to be renamed, the LDAP provider and the DN of Printer1. The second parameter specifies the new name for the printer.

Example 5.25. Renaming a Published Printer

1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
2
3 objOU.MoveHere _
4    "LDAP://cn=Printer1,ou=HR,dc=NA,dc=fabrikam,dc=com", "cn=HRPrn1"

Moving a Group from One Container to Another

The script in Listing 5.26 moves the Atl-Users group from the HR OU to the Users container of the na.fabrikam.com domain. Note that the Atl-Users group is not modified in any way. All members of the group remain, and the group is not renamed.

  1. Bind to the Users container in the na.fabrikam.com domain.

    This binding operation designates the Users container as the target container of the move operation.

  2. Move the Atl-Users group from the HR OU to the Users container within the same domain.

    The first parameter of the MoveHere method specifies the ADsPath of the object to be moved, the LDAP provider and the DN of the Atl-Users group. The second parameter specifies vbNullString because the group is not renamed during the move operation. Alternatively, you can specify the relative distinguished name (cn=Atl-Users) for the second parameter.

Example 5.26. Moving a Group Within the Same Domain

1 Set objOU = GetObject("LDAP://cn=Users,dc=NA,dc=fabrikam,dc=com")
2
3 objOU.MoveHere "LDAP://cn=Atl-Users,ou=HR,dc=NA,dc=fabrikam,dc=com", _
4    vbNullString

Tip

Tip

It is easy to get confused about whether you specify the target or source container in the binding step. To remember that it is the target container that you specify in the binding step, ask yourself, “Where do I want the object to go?” After you have answered that question, specify that location in the binding step.

Moving an OU from One Domain to Another

The script in Listing 5.27 moves the Management OU from the fabrikam.com domain to the na.fabrikam.com domain.

  1. Bind to the na.fabrikam.com domain.

    This binding operation designates the na.fabrikam.com domain as the target container of the move operation.

  2. Move the Management OU from the fabrikam.com domain to the na.fabrikam.com domain.

Example 5.27. Performing a Cross-Domain Move of an OU

1 Set objDomain = GetObject("LDAP://dc=NA,dc=fabrikam,dc=com")
2
3 objDomain.MoveHere "LDAP://ou=Management,dc=fabrikam,dc=com", _
4    vbNullString

While the script in Listing 5.27 might appear useful, it is of limited use in its current form. A cross-domain move works only for leaf objects. Therefore, the OU must be empty before it can be moved. However, if you have assigned a number of attributes to the OU, it might make sense to move all of the objects in the container to the other domain, then move the OU, and finally move all of the objects back into the OU. For information about moving specific types of objects, such as user accounts, see the corresponding ADSI task-based chapter.

Important observations about the scripts in this section are:

  • They perform the same basic steps: They bind to a target container and then call the MoveHere method to move or rename an object.

  • They use the same method (MoveHere) without regard to the class of object or the operation (move or rename).

Searching

Although searching Active Directory is a useful administrative task in itself, it becomes even more powerful when the results of a search are combined with other administrative tasks. For example, you can search for all the user accounts in a domain and then use the results of the search to modify attributes that should be the same for all users, or verify that an Active Directory object does not already exist before attempting to create it.

The query technology available to VBScript is ActiveX® Data Objects (ADO). ADO uses the ADSI OLE DB provider to read information from Active Directory. OLE DB is a set of interfaces that provide access to all types of databases, including the Active Directory database. The information returned to ADO by the ADSI OLE DB provider is read-only. Thus, you cannot use ADO to modify the result set returned by a query. However, as the preceding paragraph explains, after the result set is returned, you can use ADSI methods to perform administrative tasks that go beyond returning a result set.

Returning a result set typically involves three ADO objects:

  • Connection. This object provides a link between the ADSI script and Active Directory by loading the ADSI OLE DB provider.

  • Command. This object enables the script to initiate a query against Active Directory and control various aspects of the search, such as the sort order.

  • RecordSet. This object receives the query results from the Command object.

Searching Active Directory

The goal of performing a search is to return a result set containing zero or more records. A result set containing no records is useful if you are verifying that an object is not present before attempting to create it.

Searching for Active Directory objects involves the following steps:

  1. Create an ADO Connection object and use the Open method of the object to access Active Directory with the ADSI OLE DB provider.

  2. Create an ADO Command object, and assign the Command object’s ActiveConnection property to the Connection object.

    This step is necessary because the Command object holds both the connection and the query string to run against Active Directory.

  3. Assign a search request (query string) to the CommandText property of the Command object.

    You can use either SQL syntax or LDAP search dialect for the query string. All of the examples in this chapter use LDAP search dialect. For information about the SQL dialect for performing an Active Directory search, see the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources.

  4. Using the Execute method of the Command object, run the query and store the results in a RecordSet object.

  5. Using properties of the RecordSet object, read information in the result set.

  6. Using the Connection object Close method, close the Connection.

    This final step is optional, but it is good practice to remove objects from memory when a script has finished using them. In much larger scripts that take time to complete, removing unused objects from memory saves resources.

The goal of the scripts in this section is to demonstrate how to construct Active Directory searches by using ADO and the ADSI OLE DB provider. In each case, the script includes the six steps just described for searching Active Directory.

Creating a Simple Search Script

The script in Listing 5.28 returns a result set containing the name of all objects in the na.fabrikam.com domain.

To carry out this task, the script performs the following steps:

  1. Create an ADO Connection object to access Active Directory by using the ADSI OLE DB provider.

    Line 1 creates a Connection object in memory, and line 2 opens the Connection object using the ADSI OLE DB provider. Notice that the name of the ADSI OLE DB provider that you use in all ADSI search scripts is ADsDSOObject. This is called the ProgID (Program ID) of the provider.

  2. Create an ADO Command object in memory, and assign the Command object’s ActiveConnection property to the Connection object (lines 4 and 5).

  3. Assign the query string to the CommandText property of the Command object.

    Line 8, which continues line 7, specifies the search base, the attribute to return, and the search scope.

    • The search base, surrounded by angle brackets (< >), specifies the start of the search — the ADsPath to the na.fabrikam.com domain.

    • The attribute to return, which appears after two semicolons, specifies the lDAPDisplayName of each attribute that the query should return. In this case, a single attribute, name, is specified. If more than one attribute is specified, separate each attribute with a comma.

    • The search scope, appearing at the end of the query string, specifies where to perform the query — subtree, which performs the search of the entire hierarchy, starting from the search base na.fabrikam.com.

  4. Run the query by calling the Execute method of the Command object and assigning the return value to the RecordSet object (line 9).

    The query string returns records containing a single field, the name field.

  5. Use a While Wend statement to display each record in objRecordSet. Use the MoveNext method of the RecordSet object to move to the next record.

  6. Close the Connection object.

Example 5.28. Searching for the Names of All Objects in the Domain

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://dc=NA,dc=fabrikam,dc=com>;;name;subtree"
 9 Set objRecordSet = objCommand.Execute
10
11 While Not objRecordSet.EOF
12     Wscript.Echo objRecordSet.Fields("name")
13     objRecordSet.MoveNext
14 Wend
15
16 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name of each object in the domain to the command window, as shown in the following abbreviated result set:

na
Builtin
Administrators
Users
...
S-1-5-11
Program Data
Microsoft

The information returned by running the script in Listing 5.28 is of limited use for the following reasons:

  • The names of all objects in the na.fabrikam.com domain are returned.

  • There is no indication of where in the domain hierarchy the objects exist.

  • There is no indication of the type of objects in the result set.

  • The result set might be too large or too small, depending on the requirements of your search.

To make the result set more useful, you can change the script by:

Modifying the query string. In the query string, you can specify the attributes to return and the part of the directory that is searched (the search base). You can also implement filters and specify the scope of the search.

Specifying additional search options by using the Command object. Using the Command object lets you control many aspects of the search, such as the sort order of the query, the size limit of the result set, how long the script waits for results, and other options.

Note

Note

For a complete list of search options and other properties supported by ADO and available from the ADSI OLE DB Provider, see the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources.

The goal of the next eight scripts in this section is to show examples of how you can change the query string, specify additional Command object properties, and modify the script language in other ways to fine-tune the results of a search.

Scripting the Attributes to Be Returned by the Search

Listing 5.28 demonstrated how to return a single value, that of the name attribute. Returning additional attributes can make the result set more useful. For example, by adding the distinguishedName attribute to the result set, you can determine where objects are located in Active Directory.

The script in Listing 5.29 returns a result set containing both the name and the distinguishedName of all objects in the na.fabrikam.com domain. The steps in this listing are exactly the same as those in the preceding listing; therefore, the steps are not repeated here.

To expand the result set, the following modifications were made to the script:

  1. Include the distinguishedName attribute in the query string (line 8).

  2. Echo the value of the distinguishedName attribute to the command window (lines 14 and 15).

Example 5.29. Searching for the Names and DNs of All Objects in the Domain

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://dc=NA,dc=fabrikam,dc=com>;;distinguishedName,name;subtree"
 9
10 Set objRecordSet = objCommand.Execute
11
12 While Not objRecordSet.EOF
13     Wscript.Echo objRecordSet.Fields("Name")
14     Wscript.Echo "[" & _
15         objRecordSet.Fields("distinguishedName") & "]"
16     objRecordSet.MoveNext
17 Wend
18
19 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name and DN of each object in the domain to the command window, as shown in the following abbreviated result set:

na
[dc=NA,DC=fabrikam,DC=com]
Builtin
[CN=Builtin,dc=NA,DC=fabrikam,DC=com]
Administrators
[CN=Administrators,CN=Builtin,dc=NA,DC=fabrikam,DC=com]
Users
[CN=Users,CN=Builtin,dc=NA,DC=fabrikam,DC=com]
...
S-1-5-11
[CN=S-1-5-11,CN=ForeignSecurityPrincipals,dc=NA,DC=fabrikam,DC=com]
Program Data
[CN=Program Data,dc=NA,DC=fabrikam,DC=com]
Microsoft
[CN=Microsoft,CN=Program Data,dc=NA,DC=fabrikam,DC=com]

Limiting a Search to a Specific Type of Object

Suppose you are interested in a result set containing only a certain type of object, such as a computer, printer, user account, or group. To limit a search to a specific type of object, you can specify either the objectClass or objectCategory search filter property in the query string.

Each Active Directory object contains a single-valued objectCategory attribute. This attribute is the DN of the class from which the object is derived. For example, the objectCategory of a group object is cn=Group,cn=Schema,cn=Configuration,dc=fabrikam,dc=com. To return only group objects in the result set, you include the filter property (objectCategory=Group) in the filter portion of the search string.

Each Active Directory object also contains a multivalued objectClass attribute. This attribute contains an ordered list of the entire class hierarchy from which the Active Directory object is derived. The objectClass filter property lets you limit the query to objects matching any one of the values stored in the objectClass multivalued attribute. For example, to return all user and computer objects, you can specify (objectClass=user). This returns both user and computer objects because in the Active Directory hierarchy, the computer class is a child class of the user class.

Because the objectCategory attribute contains a single value, it is better suited for performing searches. Therefore, whenever possible, use the objectCategory filter property instead of objectClass for performing searches containing potentially large result sets.

Note

Note

For information about Active Directory classes and the class hierarchy, see “Active Directory Architecture” later in this chapter.

Listing 5.30 demonstrates how to return a result set containing a certain category of object — the Computer category. The script returns both the name and the distinguishedName of computer objects in the na.fabrikam.com domain. The steps in this listing are exactly the same as those in the preceding listings in this section; therefore, the steps are not repeated here.

To limit the result set to computer objects in the domain, the following modification was made to the script:

  • Include the objectCategory filter in the query string, and set its value to return all computer objects (line 8).

Example 5.30. Searching for the Names and DNs of Computer Objects in the Domain

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://dc=NA,dc=fabrikam,dc=com>;(objectCategory=computer)" & _
 9        ";distinguishedName,name;subtree"
10
11 Set objRecordSet = objCommand.Execute
12
13 While Not objRecordSet.EOF
14     Wscript.Echo objRecordSet.Fields("Name")
15     Wscript.Echo "[" & _
16         objRecordSet.Fields("distinguishedName") & "]"
17     objRecordSet.MoveNext
18 Wend
19
20 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name and DN of each computer object in the domain to the command window, as shown in the following abbreviated result set:

SEA-DC-02
[CN=SEA-DC-02,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
SEA-DC-03
[CN=SEA-DC-02,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
...
SEA-PM-01
[CN=SEA-PM-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
SEA-SQL-01
[CN=SEA-SQL-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]

Filter properties can be combined, and wildcards are supported. For details about the search filter syntax, see the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources and perform a search on the phrase, “Search Filter Syntax.” For object-specific search examples, see the ADSI task-based chapters in this book.

Specifying the Global Catalog in the Search Base

Suppose you want to return the names and DNs of all computers in the forest. To accomplish this task, you can query the Global Catalog server in the root domain because the name and distinguishedName attributes are two attributes that are located in the Global Catalog server by default. Global catalog servers contain a partial attribute set of every object in the domain. To query the Global Catalog in the forest, change the LDAP moniker in the search base portion of the query string to GC and change the DN to the root domain. Therefore, the ADsPath changes from

<LDAP://dc=NA,dc=fabrikam,dc=com>

to

<GC://dc=NA,dc=fabrikam,dc=com>

Listing 5.31 demonstrates how to return a result set containing information about all computer objects in the forest.

To expand the result set to all computer objects in the forest, the following modification was made to the script:

  • Use the GC moniker in the search base (ADsPath) of the query string, and change the DN to the root domain, fabrikam.com (line 8).

Example 5.31. Searching for the Names and DNs of All Computer Objects in the Forest

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<GC://dc=fabrikam,dc=com>;(objectCategory=computer)" & _
 9      ";distinguishedName,name;subtree"
10
11 Set objRecordSet = objCommand.Execute
12
13 While Not objRecordSet.EOF
14     Wscript.Echo objRecordSet.Fields("Name")
15     Wscript.Echo "[" & _
16         objRecordSet.Fields("distinguishedName") & "]"
17     objRecordSet.MoveNext
18 Wend
19
20 objConnection.Close

When this script runs in the fabrikam.com root domain, it echoes the name and DN of each computer object in the forest to the command window, as shown in the following abbreviated result set:

SEA-DC-01
[CN=SEA-DC-01,OU=Domain Controllers,DC=fabrikam,DC=com]
SEA-DC-04
[CN=SEA-DC-04,OU=Domain Controllers,DC=fabrikam,DC=com]
SEA-DC-02
[CN=SEA-DC-02,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
SEA-DC-03
[CN=SEA-DC-03,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
...
SEA-PM-01
[CN=SEA-PM-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
SEA-SQL-01
[CN=SEA-SQL-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]

Using Referral Chasing to Expand the Result Set

If you want to return a complete result set containing one or more attributes that are not in the Global Catalog, you must use referral chasing. When a domain controller in a parent domain, such as the root domain, builds its result set, it passes a list of child domains back to the client computer running the script. The client computer then contacts each of the child domains so that they can build their result sets to satisfy the query. This process is called referral chasing.

Referral chasing increases network traffic and processing load on the domain controllers servicing the request. Network traffic is increased because, in contrast with a Global Catalog search, the client must contact a domain controller in each child domain and each domain controller servicing the request must respond with a result set.

You can explicitly enable referral chasing by setting the Chase Referrals property of the Command object to ADS_CHASE_REFERRALS_SUBORDINATE (a constant with the value &h20). Listing 5.32 demonstrates how to return a result set that uses referral chasing to retrieve attributes from a domain and its child domains. With the exception of four minor additions, the steps in this listing are similar to the previous listings in this section.

To support referral chasing, the following modifications were made to the script:

  1. Set the ADS_CHASE_REFERRALS_SUBORDINATE constant for referral chasing (line 1).

  2. Enable referral chasing by setting the Chase Referrals property of the ADO Command object to ADS_CHASE_REFERRALS_SUBORDINATE. This instructs the server to send a list of referrals back to the client so that child domains can also process the result set (lines 8 and 9).

  3. Include the isCriticalSystemObject attribute in the query string to demonstrate retrieving a value not contained in the Global Catalog (line 13).

  4. Echo the value of isCriticalSystemObject to the command window (lines 19 and 20).

Example 5.32. Using Referral Chasing to Perform a Search

 1 ADS_CHASE_REFERRALS_SUBORDINATE = &h20
 2 Set objConnection = CreateObject("ADODB.Connection")
 3 objConnection.Open "Provider=ADsDSOObject;"
 4
 5 Set objCommand = CreateObject("ADODB.Command")
 6 objCommand.ActiveConnection = objConnection
 7
 8 objCommand.Properties("Chase Referrals") = _
 9    ADS_CHASE_REFERRALS_SUBORDINATE
10
11 objCommand.CommandText = _
12    "<LDAP://dc=fabrikam,dc=com>;(objectCategory=computer);" & _
13        "distinguishedName,name,isCriticalSystemObject;subtree"
14
15 Set objRecordSet = objCommand.Execute
16
17 While Not objRecordSet.EOF
18     Wscript.Echo objRecordSet.Fields("Name")
19     Wscript.Echo "isCriticalSystemObject: " & _
20         objRecordSet.Fields("isCriticalSystemObject")
21     Wscript.Echo "[" & _
22         objRecordSet.Fields("distinguishedName") & "]"
23     objRecordSet.MoveNext
24 Wend
25
26 objConnection.Close

When this script runs in the fabrikam.com root domain, it echoes the name, the DN, and the Boolean value of isCriticalSystemObject of each computer object in the forest to the command window, as shown in the following abbreviated result set:

SEA-DC-01
isCriticalSystemObject: True
[CN=SEA-DC-01,OU=Domain Controllers,DC=fabrikam,DC=com]
SEA-DC-04
isCriticalSystemObject: True
[CN=SEA-DC-04,OU=Domain Controllers,DC=fabrikam,DC=com]
SEA-DC-02
isCriticalSystemObject: True
[CN=SEA-DC-02,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
SEA-DC-03
isCriticalSystemObject: True
[CN=SEA-DC-03,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
...
SEA-PM-01
isCriticalSystemObject: False
[CN=SEA-PM-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
SEA-SQL-01
isCriticalSystemObject: False
[CN=SEA-SQL-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]

Controlling the Scope of a Search

Suppose you are interested in limiting a result to attributes of computer objects in the Computers container of a particular domain. Suppose as well that additional child containers in the Computers container contain objects. To limit a search to a specific container, such as the Computers container, you can modify the DN specified in the ADsPath (the search base) as follows:

cn=Computers,dc=NA,dc=fabrikam,dc=com

A possible query string might look like this:

"<GC://cn=Computers,dc=NA,dc=fabrikam,dc=com>" & _
    ";(objectCategory=computer)" & _
        ";distinguishedName,name;subtree"

This query string instructs the script to retrieve the DN and name of all computer objects in the Computers container of the na.fabrikam.com domain. However, because subtree is specified at the end of the query string, the result set will also include any computer objects in child containers of the Computers container. To limit the search to just the Computers container, you can specify a different search scope.

The search scope is the last part of the query string; options for search scope are base, onelevel, and subtree:

Base. Searches only the DN specified in the search base. For example, a query for the name attribute with the search base <GC://cn=Computers,dc=NA,dc=fabrikam,dc=com> and no search filter returns the name value of the container, Computers. No child objects of the Computers container are searched.

Onelevel. Searches the immediate children of the specified search base. For example, a query for the name attribute with the search base <GC://cn=Computers,dc=NA,dc=fabrikam,dc=com> and the (objectCategory=computer) search filter returns the name values of all computer objects in the Computers container. Any child containers in the Computers container are not searched.

Subtree. Searches the entire subtree, including the object specified in the search base. Examples of this search scope appear in all previous search examples. This is the default scope option: If you do not specify a scope option in the query, subtree is used.

The script in Listing 5.33 demonstrates how to return a result set containing the name and distinguishedName of all computer objects in the Computers container. Subcontainers of the Computers container are not searched.

To limit the search scope:

  • Specify the Computers container in the na.fabrikam.com domain as the search base.

  • Specify onelevel as the search scope.

Example 5.33. Limiting the Scope of a Search

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<GC://cn=Computers,dc=NA,dc=fabrikam,dc=com>" & _
 9       ";(objectCategory=computer)" & _
10           ";distinguishedName,name;onelevel"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordSet.EOF
15     Wscript.Echo objRecordSet.Fields("Name")
16     Wscript.Echo "[" & _
17         objRecordSet.Fields("distinguishedName") & "]"
18     objRecordSet.MoveNext
19 Wend
20
21 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name and the DN of computer objects in the Computers container to the command window, as shown in the following abbreviated result set:

SEA-PM-01
[CN=SEA-PM-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
SEA-SQL-01
[CN=SEA-SQL-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
...

Sorting the Results of a Search

If you want the server to sort the result set before it is sent to the client, you can include the Sort On property of the command object in the ADSI script. The Sort On property instructs the server to perform the sort operation before the data is returned.

You assign the Sort On property the lDAPDisplayName of an attribute. The attribute should contain a meaningful value for the search. For example, you can search on the name attribute to sort a result set alphanumerically or search on the whenCreated attribute to sort a result set chronologically.

Note

Note

The Sort On property cannot sort on an attribute stored as a distinguished name. If you attempt to sort on this type of attribute, an empty result set is returned.

Listing 5.34 demonstrates how to instruct the server to sort a result set containing information about all computer objects in the forest. The result set includes the name, distinguishedName, and whenCreated attributes of each computer object in the forest. The script instructs the server to sort on the whenCreated attribute. Therefore, the result set contains all computer objects in the forest, from first created to last created.

To sort the result set, the following modifications were made to the script:

  1. Assign the whenCreated attribute to the Sort On property of the Command object (line 7).

  2. Include the whenCreated attribute in the query string so that its value will be part of the result set (line 11).

    Notice that the script uses the GC moniker in the query string. This works properly because all three attributes are contained in the Global Catalog.

  3. Echo the value of the whenCreated attribute to the command window (line 19).

Example 5.34. Sorting Computer Objects in the Domain Based on Creation Date

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.Properties("Sort On") = "whenCreated"
 8
 9 objCommand.CommandText = _
10    "<GC://dc=fabrikam,dc=com>;(objectCategory=Computer);" & _
11        "distinguishedName,name,whenCreated;subtree"
12
13 Set objRecordSet = objCommand.Execute
14
15 While Not objRecordSet.EOF
16     Wscript.Echo objRecordSet.Fields("Name")
17     Wscript.Echo "[" & _
18         objRecordSet.Fields("distinguishedName") & "]"
19     Wscript.Echo objRecordSet.Fields("whenCreated") & VbCrLf
20     objRecordSet.MoveNext
21 Wend
22
23 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name, DN, and whenCreated attributes of each computer object in the domain to the command window. The result set is returned by the server in ascending order (oldest to newest object creation date) based on the whenCreated attribute, as shown in the following abbreviated result set:

SEA-DC-01
[CN=SEA-DC-01,OU=Domain Controllers,DC=fabrikam,DC=com]
8/14/2002 9:59:12 AM

SEA-DC-02
[CN=SEA-DC-02,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
8/21/2002 1:53:24 PM

SEA-DC-03
[CN=SEA-DC-03,OU=Domain Controllers,dc=NA,DC=fabrikam,DC=com]
8/27/2002 9:53:24 AM

SEA-PM-01
[CN=SEA-PM-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
8/27/2002 11:53:24 AM

...

SEA-SQL-01
[CN=SEA-SQL-01,cn=Computers,dc=NA,DC=fabrikam,DC=com]
8/27/2002 4:06:30 PM

SEA-DC-04
[CN=SEA-DC-04,OU=Domain Controllers,DC=fabrikam,DC=com]
9/03/2002 2:00:03 PM

Retrieving Multivalued Attributes from a Search

A number of Active Directory objects contain multivalued attributes. The entries in multivalued attributes can also be part of a result set. For example, you might want to return a list of group members (both user accounts and groups) from the member attribute of groups.

The script in Listing 5.35 returns a result set containing the name and distinguishedName single-valued attributes and the member multivalued attribute for each group in the na.fabrikam.com domain.

To include a multivalued attribute in the result set, the following modifications were made to the script:

  1. Include the (objectCategory=Group) filter in the query string to limit the result set to group objects (line 8). Also, include the member multivalued attribute in the query string so that its value is part of the result set (line 9).

    For this script to return group memberships, a global catalog server must be configured in the na.fabrikam.com domain. This is required because the script uses the GC moniker and the distinguished name of the na.fabrikam.com domain for the search base.

    If no global catalog server is in the na.fabrikam.com domain, you must use the LDAP moniker instead.

  2. Initialize a variable named arrMembers with the contents of the member attribute contained in the fields property of the RecordSet object (line 19).

  3. Using the VBScript IsArray function, test whether the member attribute is an array (line 22).

    This result is false if the member attribute of the group is empty and true if one or more members are listed in the group.

    • If IsArray is true, use a For Each statement to echo each member of the group to the command window (lines 23–25).

    • If IsArray is false, echo the word None to the command window (line 27).

Example 5.35. Searching for the Names and DNs of All Objects in the Domain

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<GC://dc=NA,dc=fabrikam,dc=com>;(objectCategory=Group);" & _
 9        "distinguishedName,name,member;subtree"
10
11 Set objRecordSet = objCommand.Execute
12
13 While Not objRecordSet.EOF
14     Wscript.Echo objRecordSet.Fields("Name")
15     Wscript.Echo "[" & _
16         objRecordSet.Fields("distinguishedName") & "]"
17
18     Wscript.Echo "Group Member(s):"
19     arrMembers = objRecordSet.Fields("member")
21
22     If IsArray(objRecordSet.Fields("member")) Then
23         For Each strMember in arrMembers
24             Wscript.Echo vbTab & strMember
25         Next
26     Else
27         Wscript.Echo vbTab & "None"
28     End If
29     Wscript.Echo VbCrLf
30     objRecordSet.MoveNext
31 Wend
32
33 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes the name, DN, and members of each group object in the domain to the command window, as shown in the following abbreviated result set:

Administrators
[CN=Administrators,CN=Builtin,DC=na,DC=fabrikam,DC=com]
Group Member(s):
   CN=Enterprise Admins,CN=Users,DC=fabrikam,DC=com
   CN=Domain Admins,CN=Users,DC=na,DC=fabrikam,DC=com
   CN=Administrator,CN=Users,DC=na,DC=fabrikam,DC=com

Users
[CN=Users,CN=Builtin,DC=na,DC=fabrikam,DC=com]
Group Member(s):
   CN=Domain Users,CN=Users,DC=na,DC=fabrikam,DC=com
   CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=na,DC=fabrikam,DC=com
   CN=S-1-5-4,CN=ForeignSecurityPrincipals,DC=na,DC=fabrikam,DC=com

...

Pre-Windows 2000 Compatible Access
[CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=na,DC=fabrikam,DC=com]
Group Member(s):
   None

Atl-Users
[CN=Atl-Users,CN=Users,DC=na,DC=fabrikam,DC=com]
Group Member(s):
   CN=LewJudy,OU=Sales,DC=na,DC=fabrikam,DC=com
   CN=HuffArlene,OU=HR,DC=na,DC=fabrikam,DC=com
   CN=MyerKen,OU=HR,DC=na,DC=fabrikam,DC=com

Using Range Limits When Retrieving Multivalued Attributes

If a multivalued attribute contains many records, you can request a group of records in the attribute (rather than retrieve them all at once) by specifying Range Limits. This more evenly distributes the processing load placed on a domain controller servicing the request and, as a result, can improve the performance of the search operation. Also, if a multivalued attribute contains more than 1,000 entries, you must specify range limits, or the result set might not be complete.

The script in Listing 5.36 returns a result set containing the first 1,000 entries in the member multivalued attribute for the Atl-Users group in the na.fabrikam.com domain.

Note

Note

This script will fail if there are fewer than 1,000 members in the Atl-Users group. There must always be more members than the upper limit of the range (in this case, 999).

To specify range limits in a result set, the following modifications are made to the script:

  1. Include the Range keyword in the query string to limit the result set to a certain number of entries contained in the member attribute (line 9). The range 0–999 returns the first 1,000 entries in the member attribute of the Atl-Users group.

  2. Echo the phrase, “First 1000 Members:” to the screen (line 13).

    This phrase will appear ahead of the entries that are returned from the member attribute of the Atl-Users group.

  3. Initialize a variable named arrMembers with the contents of the member attribute contained in the fields property of the RecordSet object. Within the fields property, specify a range limit that instructs the script to return the first 1,000 records (line 14).

    You must specify the Range Limit in the Fields property of the RecordSet object exactly as it was specified in the query string.

Example 5.36. Searching for Group Membership Using Range Limits

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://cn=Atl-Users,cn=Users,dc=NA,dc=fabrikam,dc=com>;;" & _
 9        "member;Range=0-999;base"
10
11 Set objRecordSet = objCommand.Execute
12
13 Wscript.Echo "First 1000 Member(s):"
14 arrMembers = objRecordSet.Fields("member;Range=0-999")
15
16 For Each strMember in arrMembers
17     Wscript.Echo vbTab & strMember
18 Next
19
21 objConnection.Close

When this script runs in the na.fabrikam.com domain, it echoes a list containing the first 1,000 members in the Atl-Users Group to the command window, as shown in the following abbreviated result set:

First 1000 Member(s):
        CN=UserNo988,CN=Users,DC=na,DC=fabrikam,DC=com
        CN=UserNo987,CN=Users,DC=na,DC=fabrikam,DC=com
        CN=UserNo986,CN=Users,DC=na,DC=fabrikam,DC=com
              ...
        CN=HuffArlene,OU=HR,DC=na,DC=fabrikam,DC=com
        CN=MyerKen,OU=HR,DC=na,DC=fabrikam,DC=com

Important observations about the scripts in this section are:

  • They perform the same basic steps:

    1. Create an ADO Connection object using the ADSI OLE DB provider.

    2. Create an ADO Command object, and associate it with the Connection object.

    3. Use the Command object to build a query string.

    4. Create a RecordSet object to run the query and store the result set.

    5. Use the RecordSet object to read each record in the RecordSet.

    6. Close the connection.

  • With the exception of Listing 5.36, inside the While Wend statement the scripts echo the records in the result set to the command window. This step is not necessary in Listing 5.36 because the example script returns a single record containing the entries in the member attribute of the Atl-Users group.

Displaying a result set is the most rudimentary task that can be completed with a search operation. For other tasks that can be completed as a result of a search operation, see “Performing an Administrative Task Using a Result Set” later in this chapter.

Optimizing Search Performance

Optimizing a search operation requires knowledge of Active Directory to construct efficient query strings and an understanding of performance-related properties of the Command object. A search operation is also affected by how efficiently you use objects in a script.

Consolidating Query Strings

When writing a script that performs many search operations, consider consolidating the search operations. For example, write a query that returns a result set containing multiple attributes rather than create two separate queries that return attributes from the same object. Consolidating search operations reduces the load placed on the domain controller or domain controllers servicing the search request.

Limiting the Result Set

Narrow the scope of your search operation as much as possible. For example, if you want a result set containing all objects in an OU but you are not concerned about objects outside the OU, specify a search base that starts in the container you want to search. Also, if you are interested only in the objects within the OU but not child containers of the OU, limit the scope of the search to onelevel.

Use search filters to further narrow the search. For example, specify (objectCategory=class type), where class type is the type of object you want in the result set. Also, use objectCategory rather than objectClass because objectCategory is single-valued and ideal for servicing search requests. Unlike the objectClass attribute, objectCategory is replicated to the Global Catalog and indexed.

Specify filters, such as (cn=SEA*), so that the result set is limited to objects beginning with the letters SEA. However, if you do use the * wildcard, use it only at the end of the string. Specifying the wildcard at the beginning or in the middle of a value requires more processing on the domain controller servicing the request.

Combine filters to further refine the search. For example, the following filter limits a search to all computer objects starting with SEA:

(&(objectCategory=computer)(cn=SEA*))

You can also limit the number of entries returned by a multivalued attribute containing many entries by specifying a range limit. For an example of how to implement range limits in a search operation, see Listing 5.36 in “Searching Active Directory” earlier in this chapter.

Specifying Additional Command Object Properties

Certain Command object properties control various aspects of the search operation. These properties are especially useful for handling large result sets. Table 5.4 shows some of the options of the Command object that control a search operation.

Table 5.4. Options for Improving Performance for Large Result Sets

Option

Description

Default

Syntax

Page Size (paging)

Instructs the domain controller to process a certain number of records and return them to the client before continuing the search.

Disabled

objCommand.Properties _
    ("Page Size")= Records

where Records is the number of records the domain controller should return before continuing the search.

Size Limit

Specifies the size of the result set. If the server reaches the size specified by the Size Limit property, the result set is returned to the client and the search operation is considered complete.

1,000 records

objCommand.Properties _
    ("Size Limit")= Records

where Records is the number of records the domain controller should return before completing the search.

The default size limit of a search is 1,000 records.

Time Limit

Specifies the time that the domain controller will search before returning a result set. If the server reaches the time limit, the search is ended.

None

objCommand.Properties _
    ("Time Limit")= Time

where Time is the maximum amount of time (in seconds) that the domain controller should perform a search operation.

Timeout

Specifies the amount of time the client waits for a result set before terminating the search request.

None

objCommand.Properties _
    ("Timeout")= Time

where Timeout is the maximum amount of time (in seconds) that the client waits before terminating a search request.

Cache Results (caching)

Specifies whether the result set should be cached to the client. For very large result sets, disabling caching will reduce memory consumption on the client.

True

objCommand.Properties _
    ("Cache Results"=
Boolean

If set to False, caching is disabled. If set to True, caching is enabled.

Asynchronous

Specifies whether the server should send a result set a record at a time (asynchronously) or wait until the search operation completes (synchronously).

False

objCommand.Properties _
    ("Asynchronous"= Boolean

If set to True, results are sent asynchronously. If set to False, results are sent synchronously.

See the ADSI task-based chapters in this book for script examples that use filter combinations, range limits, and properties of the Command object to complete object-specific search operations.

Using the Global Catalog to Perform Search Operations

When all attributes in a search operation are contained in the Global Catalog, use the GC prefix in the search base rather than LDAP. This is especially important when you want to return a result set from more than one domain. If you use the Global Catalog, a single domain controller can service the request. If you do not use the Global Catalog, you must enable referral chasing to get a complete result set from multiple domains. Referral chasing is not efficient and should be avoided whenever possible.

If you need to sort a result set, sort on attributes that are both indexed and in the Global Catalog. For information about how to determine which attributes are in the Global Catalog and are indexed, see “Active Directory Architecture” later in this chapter.

Minimize Object Creation (Instantiation)

Create a Connection object only once, and reuse it in the same script. Do not clear the Connection object from memory until you have completed all operations that use it. This rule also applies to other types of objects. For example, if you bind to an object in Active Directory, do not bind again to the object in the same script. Instead, reuse the object that is already in memory.

Performing an Administrative Task Using a Result Set

The ADSI OLE DB provider gains read-only access to Active Directory. Therefore, you cannot use ADO to modify Active Directory directly. However, you can use the result set returned by a search operation to perform administrative tasks using a combination of ADO and ADSI methods. For example, you can:

  • Search for the sAMAccountName attribute of an object in a domain and, if the result set is empty, use the Create method to create the object.

    For an example of how to complete this task, see “Active Directory Users” in this book.

  • Search for all computer objects using the objectCategory attribute and then use the Put method to modify an attribute of each object.

  • Search for all objects whose description attribute designates that the object is owned by a specific department and then use the MoveHere method to consolidate all objects in a container.

The goal of the two scripts in this section is to demonstrate how to use a result set returned by a search operation to perform an administrative task.

Modifying an Attribute in Multiple Objects

The script in Listing 5.37 modifies the location attribute to Atlanta, Georgia, for all computers in a domain whose name begins with ATL. The steps to complete this task are a combination of the steps described in “Searching” and “Modifying Directory Service Objects” earlier in this chapter; therefore, the steps are summarized here.

  1. Using ADO, query Active Directory for all computer objects starting with the name ATL.

    • In line 9, two filters are combined, (objectCategory=Computer) and (cn=ATL*). The second filter uses the asterisk wildcard to find all computers whose name starts with ATL.

    • In line 10, ADsPath is the attribute returned for each computer in the result set.

  2. Use a While Wend statement and the MoveNext method to read each record in the result set.

    1. For each record in the result set, bind to the corresponding object in Active Directory, write the location attribute to the object, and then commit the object to Active Directory (lines 15–18).

    2. Show the number of records modified by echoing the value of the RecordSet object’s RecordCount property to the command window (lines 21 and 22).

Example 5.37. Modifying Multiple Computer Objects Using the Result Set Returned by a Search

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://dc=NA,dc=fabrikam,dc=com>;" & _
 9        "(&(objectCategory=Computer)(cn=ATL*));" & _
10             "ADsPath;subtree"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordSet.EOF
15     strADsPath = objRecordSet.Fields("ADsPath")
16     Set objComputer = GetObject(strADsPath)
17     objComputer.Put "location", "Atlanta, Georgia"
18     objComputer.SetInfo
19     objRecordSet.MoveNext
20 Wend
21 Wscript.Echo objRecordSet.RecordCount & " computers objects modified."
22
23 objConnection.Close

Moving Objects Containing a Certain Value for an Attribute

The script in Listing 5.38 moves user account objects to the HR OU if their department attribute is set to Human Resources. The steps to complete this task are a combination of the steps described in “Searching” and “Moving and Renaming Objects” earlier in this chapter; therefore, the steps are summarized here.

  1. Using ADO, query Active Directory for all user account objects with a department attribute value of Human Resources.

    • On lines 9 and 10, three filters are combined: (objectCategory=person), (objectClass=user), and (department=Human Resources). Take note of how ampersands and parentheses are used to combine the filters. The script uses both objectCategory and objectClass so that all user account types that are security principals are returned by the query. For more information about why this filter combination is necessary, see “Active Directory Users” in this book.

    • On line 10, return the ADsPath, distinguishedName, and name attributes of each user account that matches the filter properties and scope. All of these attributes are used later in the script.

  2. Bind to the target OU of the move operation (line 14).

    Note that this binding operation could have been completed inside the While Wend statement that starts on line 16. However, it is more efficient to perform a binding operation once and reuse it as many times as necessary in the script.

  3. Use a While Wend statement to read each record in the result set (line 16).

    1. Initialize the strADsPath variable with the field containing the ADsPath.

    2. Initialize two variables, strDNRecord and strDNCompare. The strDNRecord contains the value of the distinguishedName attribute returned by the query. The strDNCompare attribute contains a distinguishedName that is constructed from the name field returned by the query and the path to the HR OU. Use the strDNCompare variable to determine whether the user account specified by strDNRecord is currently located in the HR OU.

    3. If the user account is not already in the HR OU, (that is, strDNRecord is not equal to strDNCompare), use the MoveHere method to move the object into the that OU. Then echo the distinguishedName of the user account before it was moved and state that it was moved. Otherwise, echo the distinguishedName of the user account in the HR OU and state that it was not moved.

Example 5.38. Moving Multiple User Accounts Using the Result Set Returned by a Search

 1 Set objConnection = CreateObject("ADODB.Connection")
 2 objConnection.Open "Provider=ADsDSOObject;"
 3
 4 Set objCommand = CreateObject("ADODB.Command")
 5 objCommand.ActiveConnection = objConnection
 6
 7 objCommand.CommandText = _
 8    "<LDAP://dc=NA,dc=fabrikam,dc=com>;" & _
 9        "(&(&(objectCategory=person)(objectClass=user)" & _
10            "(department=Human Resources)));" & _
11                "ADsPath,distinguishedName,name;subtree"
12
13 Set objRecordSet = objCommand.Execute
14
15 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
16
17 While Not objRecordSet.EOF
18     strADsPath = objRecordSet.Fields("ADsPath")
19
20     strDNRecord=LCase(objRecordSet.Fields("distinguishedName"))
21     strDNCompare=LCase("cn=" & objRecordSet.Fields("name") & _
22         ",ou=HR,dc=NA,dc=fabrikam,dc=com")
23
24     If strDNRecord <> strDNCompare Then
25         objOU.MoveHere strADsPath, vbNullString
26         Wscript.Echo objRecordSet.Fields("distinguishedName") & " Moved."
27     Else
28         Wscript.Echo objRecordSet.Fields("distinguishedName") & " Not Moved."
29     End If
30     objRecordSet.MoveNext
31 Wend
32
33 objConnection.Close

Important observations about the scripts in this section are:

  • Both scripts perform the same basic steps: They use ADO to create a Connection, a Command, and a RecordSet object, and then they read each record in the RecordSet object.

  • Using the information in the result set, both scripts perform an administrative task.

Enumerating Active Directory Objects in Containers

Using ADO and the ADSI OLE DB provider is not the only way to return results from a container. You can also enumerate the contents of a container, such as a domain or an OU. Enumeration is the process of returning a list of objects in a container.

Enumeration in ADSI is not as sophisticated as in ADO and is not as efficient for retrieving information about a large number of objects. However, container enumeration does provide limited capabilities, and writing scripts that enumerate containers is simpler than writing scripts that perform a search operation against Active Directory.

Scripting Container Enumeration

Enumerating the contents of an Active Directory container typically involves three basic steps:

  1. Bind to an Active Directory container to be enumerated.

    Typically, the container is an OU.

  2. Limit the result set with the Filter property.

    This step is optional but should be used if you need to limit the type of objects enumerated in a container of many objects.

  3. Create a loop to perform an administrative task against the objects in the container.

Enumerating the Contents of a Container

The script in Listing 5.39 enumerates the Configuration container and echoes the names of the child containers to the command window. The Configuration container is located in the fabrikam.com root domain. The script involves the following steps:

  1. Bind to the Configuration container in the root domain.

  2. Use a For Each statement to echo the names of each child container in the Configuration container.

Example 5.39. Enumerating the Configuration Container for Names of Objects Within It

1 Set objConfiguration = GetObject _
2     ("LDAP://cn=Configuration,dc=fabrikam,dc=com")
3
4 For Each objContainer in objConfiguration
5     Wscript.Echo objContainer.Name
6 Next

When the script runs in the fabrikam.com forest, it echoes the name of each child container of the Configuration container to the command window, as shown:

CN=DisplaySpecifiers
CN=Extended-Rights
CN=ForestUpdates
CN=LostAndFoundConfig
CN=Partitions
CN=Physical Locations
CN=Services
CN=Sites
CN=WellKnown Security Principals

The script works from any domain in the forest.

Enumerating a Container to Perform an Administrative Task

The script in Listing 5.40 enumerates the Partitions container, which is located in the Configuration container of the fabrikam.com root domain. During enumeration, two entries in the upnSuffixes multivalued attribute are updated and the script echoes all values in the attribute to the command window.

This script demonstrates container enumeration combined with writing and reading a multivalued attribute. For more information about multivalued attributes, see “Administering Multivalued Attributes” earlier in this chapter.

  1. Set the ADS_PROPERTY_APPEND constant equal to the control code parameter used by the PutEx method to indicate this mode of modification (used in lines 5–7).

  2. Bind to the Partitions container in the Configuration container of the root domain.

  3. Add entries to the upnSuffixes attribute.

  4. Use a For Each statement to echo the entries in the Partitions container’s upnSuffixes attribute.

Example 5.40. Enumerating the Partitions Container to Write and Read the upnSuffixes Attribute

 1 Const ADS_PROPERTY_APPEND = 3
 2 Set objPartitions = GetObject _
 3     ("LDAP://cn=Partitions,cn=Configuration,dc=fabrikam,dc=com")
 4
 5 objPartitions.PutEx ADS_PROPERTY_APPEND, upnSuffixes", _
 6     Array("sa.fabrikam.com","corp.fabrikam.com")
 7 objPartitions.SetInfo
 8
 9 For Each Suffix in objPartitions.GetEx("upnSuffixes")
10     Wscript.Echo Suffix
11 Next

When this script runs in the fabrikam.com root domain, it inserts two entries in the upnSuffixes attribute and then echoes all entries in the upnSuffixes attribute to the command window, as shown:

corp.fabrikam.com
sa.fabrikam.com

By default, the script in Listing 5.40 works only if your user account is a member of the Domain Admins global group or the Enterprise Admins universal group in the root domain. Both of these groups are granted the right to update attributes in the partitions container.

Limiting Container Enumeration to a Specific Object Type

The script in Listing 5.41 enumerates the Users container of the na.fabrikam.com root domain and uses the Filter method to limit the enumeration to all user account objects. The script then echoes the value of the primaryGroupID attribute and all entries in the memberOf attribute to the command window. For information about the Filter method, see “ADSI Interfaces” later in this chapter.

  1. Use the On Error Resume Next statement.

    This statement can be used to catch (or suppress) any run-time error; however, you should use it only if you are testing for and addressing errors that might occur when the script runs. In this case, the script uses the On Error Resume Next statement to catch the ADSI error that is generated if an attribute cannot be found in the local property cache.

  2. Set the E_ADS_PROPERTY_NOT_FOUND constant equal to the ADSI error code generated if the memberOf attribute cannot be found in the local property cache (used in line 15).

  3. Bind to the Users container in the na.fabrikam.com domain.

  4. Set the Filter property to return user account objects (line 6).

  5. Use a For Each statement to echo information about each user account. Inside the loop, do the following:

    1. Using the Get method, echo the value of the primaryGroupID single-valued attribute to the command window (line11).

    2. Using the GetEx method, initialize the arrMemberOf variable with the entries in the memberOf multivalued attribute (line 13).

      If the memberOf attribute is present, the script does not raise the error number corresponding to E_ADS_PROPERTY_NOT_FOUND. Therefore, echo each entry in the arrMemberOf variable to the command window (lines 15–18).

      Otherwise, echo a message stating that the memberOf attribute is empty, and clear the error code (lines 19–21).

Example 5.41. Limiting Container Enumeration to User Accounts by Using the Filter Property

 1 On Error Resume Next
 2 Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D
 3 Set objOU = GetObject _
 4     ("LDAP://cn=Users,dc=NA,dc=fabrikam,dc=com")
 5
 6 ObjOU.Filter= Array("user")
 7
 8 For Each objUser in objOU
 9     Wscript.Echo objUser.cn & " is a member of: "
10     Wscript.Echo vbTab & "Primary Group ID: " & _
11         objUser.Get("primaryGroupID")
12
13     arrMemberOf = objUser.GetEx("memberOf")
14
15     If Err.Number <> E_ADS_PROPERTY_NOT_FOUND Then
16         For Each Group in arrMemberOf
17             Wscript.Echo vbTab & Group
18         Next
19     Else
20         Wscript.Echo vbTab & "memberOf attribute is not set"
21         Err.Clear
22     End If
23     Wscript.Echo VbCrLf
24  Next

When this script runs in the na.fabrikam.com root domain, it echoes the primaryGroupID attribute and the entries in the memberOf attribute of user accounts to the command window, as shown in the following abbreviated list:

Administrator is a member of:
        Primary Group ID: 513
        CN=Group Policy Creator Owners,CN=Users,DC=na,DC=fabrikam,DC=com
        CN=Domain Admins,CN=Users,DC=na,DC=fabrikam,DC=com
        CN=Print Operators,CN=Builtin,DC=na,DC=fabrikam,DC=com
        CN=Administrators,CN=Builtin,DC=na,DC=fabrikam,DC=com

FABRIKAM$ is a member of:
        Primary Group ID: 513
        memberOf attribute is not set

Guest is a member of:
        Primary Group ID: 514
        CN=Guests,CN=Builtin,DC=na,DC=fabrikam,DC=com
...

Root Directory Service Entry

An element important to building an ADsPath to an object is the Root Directory Service Entry (rootDSE). The rootDSE is defined as the root of the directory, and it is a useful feature of ADSI because it allows a script to derive important information about the root directory. In turn, this enables you to stay away from hard-coding distinguished names when constructing an ADsPath.

Up to this point, all of the script examples in this chapter use a feature of ADSI called serverless binding. Serverless binding means that the name of a domain controller is not hard-coded into the ADsPath. Instead, ADSI locates a domain controller in the default domain that can service the binding request. Typically, when you run a script, the default domain is your current logon domain and, depending on the configuration of Active Directory, a domain controller at a local site will service the request.

Serverless binding is an important step toward making ADSI scripts function efficiently within the forest. However, because all of the script examples in this chapter thus far use hard-coded distinguished names in the ADsPath, the scripts function only within the fabrikam.com forest. In every case, rootDSE can be used to make the scripts function in Active Directory forests other than fabrikam.com.

Scripting with rootDSE

Using rootDSE involves three basic steps.

  1. Bind to rootDSE.

  2. Use the Get method to read an attribute of rootDSE.

  3. Use the attribute returned by rootDSE to construct an ADsPath and bind to a container or an object in the directory.

    Steps 2 and 3 are often combined to reduce the length of the script.

The goal of the four scripts in this section is to demonstrate how to use rootDSE to bind to the current domain, the root domain, the configuration container, and the schema container. Using rootDSE to form an ADsPath to the current domain, the root domain, and the configuration container is useful for the previous script examples in this chapter. Using rootDSE to form an ADsPath to the schema container is useful for the scripts appearing in the upcoming section “Active Directory Architecture.”

Binding to the Current Domain

The script in Listing 5.42 binds to the current domain by using the defaultNamingContext attribute of rootDSE. The current domain is the domain where the client is logged on. This example is particularly useful for the numerous listings in this chapter that access the current domain. By modifying the binding string, you can also use this example for binding to a common container such as the Users container, an OU, or any leaf object in the domain. To carry out this task, the script performs the following steps:

  1. Bind to the rootDSE.

  2. Construct an ADsPath to the current domain.

  3. Use the strADsPath variable in the binding operation.

Example 5.42. Constructing an ADsPath to the Current Domain with rootDSE

1 Set objRootDSE = GetObject("LDAP://rootDSE")
2 strADsPath = "LDAP://" & objRootDSE.Get("defaultNamingContext")
3 Set objDomain = GetObject(strADsPath)

In the na.fabrikam.com domain, the strADsPath variable contains:

LDAP://DC=na,DC=fabrikam,DC=com

Binding to the Root Domain

The script in Listing 5.43 binds to the root domain. Regardless of which domain the client is logged on to, the root domain in the forest is returned.

Example 5.43. Constructing an ADsPath to the Root Domain with rootDSE

1 Set objRootDSE = GetObject("LDAP://rootDSE")
2 strADsPath = "LDAP://" & objRootDSE.Get("rootDomainNamingContext")
3 Set objRootDomain = GetObject(strADsPath)

In any domain in the fabrikam.com forest, the strADsPath variable contains:

LDAP://DC=fabrikam,DC=com

Binding to the Configuration Container

The script in Listing 5.44 binds to the configuration container in the forest.

Example 5.44. Constructing an ADsPath to the Configuration Container with rootDSE

1 Set objRootDSE = GetObject("LDAP://rootDSE")
2 strADsPath = "LDAP://" & objRootDSE.Get("configurationNamingContext")
3 Set objConfiguration = GetObject(strADsPath)

In any domain in the fabrikam.com forest, the strADsPath variable contains:

LDAP://CN=Configuration,DC=fabrikam,DC=com

Binding to the Schema Container

The script in Listing 5.45 binds to the schema container in the forest.

Example 5.45. Constructing an ADsPath to the Schema Container with rootDSE

1 Set objRootDSE = GetObject("LDAP://rootDSE")
2 strADsPath = "LDAP://" & objRootDSE.Get("schemaNamingContext")
3 Set objSchema = GetObject(strADsPath)

In any domain in the fabrikam.com forest, the strADsPath variable contains:

LDAP://CN=Schema,CN=Configuration,DC=fabrikam,DC=com

Note

Note

For a complete list of rootDSE properties, see the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources.

Active Directory Architecture

Having a general understanding of Active Directory physical architecture and logical structure will help you successfully create ADSI scripts to complete directory management tasks. For example, knowing how to determine the attributes that can be assigned to an object will aid you in writing attributes to the object. Also, knowing which attributes are stored in the Global Catalog and which are indexed will aid you in writing efficient search scripts.

Active Directory physical architecture includes the Active Directory store and the components that make the store accessible. Active Directory logical structure defines how the directory is managed using ADSI and other tools, such as various Microsoft Management Command window (MMC) snap-ins.

Physical Architecture

An important aspect of managing Active Directory with ADSI scripts is understanding the two primary parts of Active Directory, the Active Directory store and the components (DLLs) that make the directory accessible. These two parts of Active Directory work together to provide the foundation of Windows 2000 Server family distributed networks.

The Active Directory store provides secure, searchable, hierarchical storage of objects contained in a network, including users, computers, printers, and applications. The objects in the Active Directory store contain identity and location information to describe network resources. The Active Directory store is contained in a file named the Windows NT® Directory Services Directory Information Tree, Ntds.dit.

The Active Directory components that make the information in the store accessible to users and applications are the:

  • Extensible Storage Engine. Writes and reads data from the Active Directory store (Ntds.dit). The Extensible Storage Engine (ESE) performs each write operation as a discrete transaction. ESE protects Ntds.dit by using Active Directory log files to provide transaction rollback and database recovery capabilities.

  • Database Layer. Provides an object-oriented, hierarchical view of the data contained in the Active Directory store.

  • Directory System Agent. ADSI providers and other interface components use the Directory System Agent (DSA) to establish a connection with the database layer and ultimately the Active Directory store. The DSA acts as a gatekeeper by ensuring that client operations on the objects in the Active Directory store comply with the rules that define each object. For example, the DSA will not allow script operations that attempt to write a value that is too long for a field or a script operation that does not specify all mandatory attributes of an object when it is created.

    The DSA is also integral to directory replication from one domain controller to another.

  • Lightweight Directory Access Protocol. The protocol layer to LDAP-compliant directory services such as Active Directory. LDAP is the language used by the client and the server to communicate with LDAP-compliant directories. The Active Directory store is compliant with both version 2 and version 3 of the LDAP protocol (LDAP v2 and LDAP v3). The LDAP layer component in Active Directory is LDAP v3.

Logical Structure

Promoting a computer running Windows 2000 Server to a domain controller installs Active Directory on the computer. The Active Directory store is contained on each domain controller in the domain. Before the promotion process completes, you must specify the relationship of the new domain controller to the network. The domain controller can be configured to create a domain or to become part of an existing domain.

To make Active Directory scalable from a single-server network to a network containing thousands of servers, Active Directory is designed to contain many domains. All domains are interconnected to form a hierarchy. A single branch in the hierarchy is called a tree, and all the trees in the hierarchy are collectively called a forest.

Classes and Attributes

Each Active Directory forest contains a schema. The schema is the library from which all objects are created in Active Directory. You can think of the schema as a template repository whose templates are categorized as either classes or attributes. Classes are used by Active Directory to create objects in the forest. Classes contain references to the attributes in the schema; thus, the objects created from classes contain attributes. Objects created from classes are logical representations of objects in the network, such as a user or a printer. Figure 5.6 shows the relationship between attributes and classes in the schema and the objects they create (instantiate) in Active Directory.

Attribute, Class, and Instantiated Object Relationships

Figure 5.6. Attribute, Class, and Instantiated Object Relationships

The process of creating an object from a class is called instantiation, and an object created from a class is called an instance. Each class defines a type of object that can be created in the forest. Attributes contained in a class serve to describe the class. For example, the computer class contains a cn attribute whose value is Computer and an lDAPDisplayName attribute whose value is computer. Each attribute of a class has a purpose in the directory. For instance, as you have seen, the LDAP provider uses the lDAPDisplayName attribute in ADSI scripts to access objects.

Mandatory and Optional Attributes

Classes also include the systemMustContain and systemMayContain attributes. These attributes define which attributes an object of this class must have (mandatory attributes) and which additional attributes it can have (optional attributes) when it is instantiated. Optional attributes can be set at the same time as the mandatory attributes (when an object is created) or later.

Listing 5.46 shows a script that reads any mandatory and optional attributes assigned to the computer class. To carry out this task, the script performs the following steps:

  1. Use the On Error Resume Next statement.

  2. Set the E_ADS_PROPERTY_NOT_FOUND constant equal to the ADSI error code generated if either the systemMustContain or systemMayContain attribute is not found in the local property cache (used in lines 10 and 21).

  3. Use rootDSE to determine the value of the schemaNamingContext attribute.

    For more information about rootDSE, see “Root Directory Service Entry” earlier in this chapter.

  4. Bind to the computer class in the schema container using the GetObject function and the LDAP provider.

  5. Initialize the arrMandatoryAttributes variable with the systemMustContain attribute (line 9).

    • If initializing the variable returns the error code equal to E_ADS_PROPERTY_NOT_FOUND, echo to the command window that no mandatory attributes were found.

    • Otherwise, use a For Each statement to read and display the name of each mandatory attribute in the computer class.

  6. Initialize the arrOptionalAttributes variable with the systemMayContain attribute (line 20).

    • If initializing the variable returns the error code equal to E_ADS_PROPERTY_NOT_FOUND, echo to the command window that no optional attributes were found.

    • Otherwise, use a For Each statement to read and display the name of each optional attribute in the computer class.

Example 5.46. Reading the Mandatory and Optional Attributes of the Computer Class

 1 On Error Resume Next
 2 Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D
 3 strClassName = "cn=Computer"
 4
 5 Set objRootDSE = GetObject("LDAP://rootDSE")
 6 Set objSchemaClass = GetObject("LDAP://" & strClassName & "," & _
 7     objRootDSE.Get("schemaNamingContext"))
 8
 9 arrMandatoryAttributes = objSchemaClass.GetEx("systemMustContain")
10 If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
11     Wscript.Echo "No mandatory attributes"
12     Err.Clear
13 Else
14     Wscript.Echo "Mandatory (Must-Contain) attributes"
15     For Each strAttribute in arrMandatoryAttributes
16         Wscript.Echo strAttribute
17     Next
18 End If
19
20 arrOptionalAttributes = objSchemaClass.GetEx("systemMayContain")
21 If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
22     Wscript.Echo "No optional attributes"
23     Err.Clear
24 Else
25     Wscript.Echo VbCrLf & "Optional (May-Contain) attributes"
26     For Each strAttribute in arrOptionalAttributes
27         Wscript.Echo strAttribute
28     Next
29 End If

When the script runs, it echoes a message indicating that there are no mandatory attributes and then it echoes a list showing the entries in the systemMayContain attribute of the computer class, as shown in the following abbreviated list:

No mandatory attributes

Optional (May-Contain) attributes
volumeCount
siteGUID
rIDSetReferences
...
dNSHostName
defaultLocalPolicyObject
cn
catalogs

When you create a computer object you must specify a value for the sAMAccountName attribute because it is a mandatory attribute of the computer class. However, running the preceding script suggests that the computer class does not contain any mandatory attributes. The script is not incorrect. The computer class, like many other classes, inherits attributes from other classes by means of direct inheritance or through the Active Directory class hierarchy. The sAMAccountName attribute is mandatory, but is not defined in the computer class.

Class Inheritance and Categorization

Not only is the Active Directory hierarchical in structure (domains are interconnected by trees and trees are interconnected to form a forest), but so are the classes in the schema. Inheritance from one class to another means that some attributes defined for a class are inherited, whereas others are applied directly to the class. Ultimately, the object that is instantiated from the class can contain all of the attributes of the class.

Understanding the differences between class categories will help you determine how objects are used in the forest. In the case of the computer object, the object inherits attributes from the user class. Moving up the class hierarchy, attributes are inherited from the organizationalPerson, person, and top classes. Notice that the user class contains attributes directly applied by the mailRecipient and securityPrincipal classes. The securityPrincipal class contains the sAMAccountName attribute, which is mandatory for all classes that inherit this attribute. Thus, the computer class contains the sAMAccountName attribute through inheritance.

Other distinctions about objects can be drawn by understanding the class hierarchy and inheritance. For example, the contact class is not a securityPrincipal because the securityPrincipal auxiliary class is not applied to the contact class and the contact class does not inherit the attributes of this auxiliary class. Therefore, if you want to create a user account type that is a security principal, you know from the class relationship that a contact object is not a security principal.

Classes are categorized as abstract, structural, auxiliary, or 88. This categorization is stored as an integer in the objectClassCategory attribute of each class.

  • Abstract classes. Classes of this type provide attributes that flow through the hierarchy, but they cannot be used to instantiate an object. The objectClassCategory attribute for this type of class has the value 2.

  • Auxiliary classes. Classes of this type provide attributes that extend a structural class, but they cannot be used to form a structural class by themselves or instantiate an object. The objectClassCategory attribute for this type of class has the value 3.

  • Structural classes. Classes of this type can be instantiated into objects and can contain additional attributes that are not inherited from the other class types. The objectClassCategory attribute for this type of class has the value 1.

  • 88. Classes of this type were defined before there was a specification to classify class categories. Therefore, they are not required to be categorized as abstract, auxiliary, or structural and instead are assigned the value 88. Classes assigned this value behave like structural classes in that they can be instantiated into objects. However, you should view 88 classes as abstract classes because the objects that they create are not typically used to perform Active Directory tasks or to populate the Active Directory store. The objectClassCategory attribute for this type of class has the value 0.

Active Directory complies with the X.500 standard, and therefore must maintain these class categories as defined in the X.500 1993 specification. Any classes defined before the 1993 specification comply with the X.500 1988 specification, which does not contain a categorization requirement. Thus, the 88 category includes all classes defined before there was a categorization requirement.

The script shown in Listing 5.47 reads the objectClassCategory attribute of the classes Top, Mail-Recepient, Security-Principal, Person, Organizational-Person, Contact, User, Computer, and Organizational-Unit. To perform this task the script must:

  1. Initialize a variable with an array containing the common names of the classes.

    The common name of each class, not the value of the lDAPDisplayName attribute, is provided to the part of the GetObject function appearing on line 9. GetObject uses the common name of the attribute to build the distinguished name for binding to a class in the schema container.

  2. Use rootDSE to determine the value of the schemaNamingContext attribute.

    For more information about rootDSE, see “Root Directory Service Entry” earlier in this chapter.

  3. Use a For Each statement to bind to each class in the schema container using the GetObject function and the LDAP provider.

  4. Initialize the intClassCategory variable with the integer value of the objectClassCategory attribute.

  5. Use a Select Case statement to test the value of the intClassCategory variable and display a message indicating the class type of each class.

Example 5.47. Reading the objectClassCategory Attribute of Several Classes

 1 arrClassNames = Array _
 2   ("cn=top","cn=mail-Recipient", "cn=security-Principal", _
 3       "cn=person", "cn=Organizational-Person", _
 4          "cn=contact", "cn=user", "cn=computer", "cn=organizational-Unit")
 5
 6 Set objRootDSE = GetObject("LDAP://rootDSE")
 7 For Each ClassName in arrClassNames
 8     Set objSchemaClass = GetObject("LDAP://" & ClassName & "," & _
 9     objRootDSE.Get("schemaNamingContext"))
10
11     intClassCategory = objSchemaClass.Get("objectClassCategory")
12     WScript.STDOUT.Write ClassName & " is "
13     Select Case intClassCategory
14         Case 0
15             Wscript.Echo "88"
16         Case 1
17             Wscript.Echo "structural"
18         Case 2
19             Wscript.Echo "abstract"
20         Case 3
21             Wscript.Echo "auxiliary"
22     End Select
23 Next

When the script runs, it echoes the class categories of selected classes to the command window, as shown in the following list:

cn=top is abstract
cn=mail-Recipient is auxiliary
cn=security-Principal is auxiliary
cn=person is 88
cn=Organizational-Person is 88
cn=contact is structural
cn=user is structural
cn=computer is structural
cn=organizational-Unit is structural

The systemAuxiliaryClass attribute of each class lists any auxiliary classes applied directly to a class. The script in Listing 5.48 reads the systemAuxiliaryClass attribute of several classes:

  1. Specify the On Error Resume Next statement, and set the E_ADS_PROPERTY_NOT_FOUND constant to determine later in the script whether the systemAuxiliaryClass is empty.

  2. Initialize a variable with an array containing the common names of the classes used in the previous task.

  3. Use rootDSE to determine the value of the schemaNamingContext attribute.

  4. Use a For Each statement to bind to each class in the schema container, using the GetObject function and the LDAP provider.

  5. Initialize the arrSystemAuxiliaryClass variable with the systemAuxiliaryClass attribute (lines 15 and 16).

    • If initializing the variable returns the error code equal to E_ADS_PROPERTY_NOT_FOUND, echo to the command window that no system auxiliary classes are applied directly to the class.

    • Otherwise, use a For Each statement to read and display a list of auxiliary classes assigned directly to the class.

Example 5.48. Reading the systemAuxiliaryClass Attribute of Several Classes

 1 On Error Resume Next
 2 Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D
 3 arrClassNames = Array _
 4   ("cn=top","cn=mail-Recipient", "cn=security-Principal", _
 5      "cn=person", "cn=Organizational-Person", _
 6          "cn=contact", "cn=user", "cn=computer", "cn=organizational-Unit")
 7
 8 Set objRootDSE = GetObject("LDAP://rootDSE")
 9 For Each ClassName in arrClassNames
10     Set objSchemaClass = GetObject("LDAP://" & ClassName & "," & _
11     objRootDSE.Get("schemaNamingContext"))
12
13     arrSystemAuxiliaryClass = _
14     objSchemaClass.GetEx("systemAuxiliaryClass")
15
16     If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
17         Wscript.Echo "No auxiliary classes" & _
18             " assigned to " & ClassName
19         Err.Clear
20     Else
21         Wscript.Echo "Auxiliary classes in " & ClassName & ":"
22         For Each strAuxiliaryClass in arrSystemAuxiliaryClass
23             Wscript.Echo vbTab & strAuxiliaryClass
24         Next
25       Wscript.Echo
26     End If
27 Next

When this script runs it echoes a message indicating that the mailRecipient auxiliary class is applied to the contact and user classes and that the securityPrincipal auxiliary class is also applied to the user class, as shown in the following list:

No auxiliary classes assigned to cn=top.
No auxiliary classes assigned to cn=mail-Recipient.
No auxiliary classes assigned to cn=security-Principal.
No auxiliary classes assigned to cn=person.
No auxiliary classes assigned to cn=Organizational-Person.
Auxiliary classes in cn=contact:
        mailRecipient

Auxiliary classes in cn=user:
        securityPrincipal
        mailRecipient

No auxiliary classes assigned to cn=computer.
No auxiliary classes assigned to cn=organizational-Unit.

Understanding class inheritance will help you write search scripts that return the expected set of objects. For example, if you perform a search for all objects whose objectClass attribute contains user, you might be surprised to find that objects instantiated from the computer class are returned in the result set. The computer class inherits the attributes of the user class and all other classes above it in the hierarchy. Therefore, you have to modify the search to exclude computer objects from the result set. For more information about searching using ADSI, see “Searching Active Directory” earlier in this chapter.

You can use a script to view class relationships. The subClassOf attribute shows the parent class from which a class is derived. The steps for reading an attribute are similar to the preceding two listings, so scripting steps are not included here. Listing 5.49 shows how to determine the parent class of the Computer class.

Example 5.49. Reading the subClassOf Attribute of a Class

1 strClassName = "cn=computer"
2
3 Set objRootDSE = GetObject("LDAP://rootDSE")
4 Set objSchemaClass = GetObject("LDAP://" & strClassName & "," & _
5     objRootDSE.Get("schemaNamingContext"))
6
7 strSubClassOf = objSchemaClass.Get("subClassOf")
8 Wscript.Echo "The " & strClassName & _
9     " class is a child of the " & strSubClassOf & " class."

When this script runs, it echoes a message indicating that the user class is the parent of the computer class, as shown:

The cn=computer class is a child of the user class.

Snap-ins For Viewing and Configuring User Account Attributes, Classes, and Objects

As demonstrated in the preceding section, scripts can be used to view information about classes and attributes. You can also examine all of the classes and attributes in the schema using the MMC Active Directory Schema snap-in or the ADSI Edit snap-in. Knowing how to use these tools will make the process of writing scripts easier to achieve. The Active Directory Schema snap-in provides a graphical interface to the classes and attributes in the schema. The ADSI Edit snap-in provides a graphical interface to all objects in Active Directory, including the schema.

Active Directory Schema snap-in

The command window tree in the Active Directory Schema Snap-in provides two lists:

  • Class list, which contains all the classes in an Active Directory schema

  • Attributes list, which contains all the attributes in the schema

From the Class list, you can learn which attributes are contained in a particular class and the hierarchical relationships of the class, such as the parent class, auxiliary classes, and possible superiors of the class. The attributes of a class include information such as whether the attribute is mandatory or optional and the lDAPDisplayName of the attribute. The lDAPDisplayName is the name you will use to identify most attributes in an ADSI script.

From the Attribute list, you can learn about the characteristics of an attribute, such as whether an attribute is replicated to the Global Catalog, whether it is single or multivalued, and whether the attribute is a Unicode string (string), integer, or other data type. Knowing the characteristics of an attribute is critical to managing the objects that contain the attributes derived from the associated class.

Snap-ins For Viewing and Configuring User Account Attributes, Classes, and Objects To load the Active Directory Schema snap-in

  1. From a command prompt, type regsvr32 schmmgmt to register the snap-in.

    This step must be completed only once on each computer where the snap-in will be loaded.

  2. Start MMC by typing MMC at the command prompt or from the Run menu.

  3. From the File menu, click Add/Remove Snap-in, and then click Add.

  4. From the Add Standalone Snap-in dialog box, click Active Directory Schema, and then click Add.

ADSI Edit snap-in

The ADSI Edit snap-in lets you view information about the objects created in Active Directory and lets you view the schema. However, many find the presentation of the schema provided by the Active Directory Schema Snap-in easier to use.

After loading the ADSI Edit snap-in, you can choose where in Active Directory to connect. The default connection points are:

  • LDAP://domain_controller_name/Domain. This view provides information about the objects in the domain. Use this view to read information about attributes that are or can be assigned to Active Directory objects in the domain.

  • LDAP://domain_controller_name/Configuration. This view provides information about the objects that are used to construct Active Directory, such as the sites and services of the domain.

  • LDAP://domain_controller_name/RootDSE. This view provides information about the attributes contained in the RootDSE object. For information about how to use this object, see “Root Directory Services Entry” earlier in this chapter.

  • LDAP://domain_controller_name/Schema. This view provides information about the objects and classes in the Active Directory schema.

Note

Note

You can also establish a connection with the Global Catalog from this snap-in and view information about the attributes replicated to the Global Catalog.

Note To load the ADSI Edit snap-in and connect to the domain

  1. Install the Support tools (SupTools.msi) from the Windows 2000 Server family installation CD-ROM.

    The Support Tools are located in the SupportTools folder on the installation CD-ROM.

  2. Start MMC by typing MMC at the command prompt or from the Run menu.

  3. From the File menu, click Add/Remove Snap-in, and then click Add.

  4. From the Add Standalone Snap-in dialog box, click ADSI Edit, and then click Add.

  5. In the command window tree, right-click the ADSI Edit item, and then click Connect to.

  6. Accept the defaults appearing in the Connection Settings dialog box to connect to the domain, and then click OK.

Active Directory Replication and Indexing

Active Directory is well suited for handling a large number of read and search operations, and a significantly smaller number of changes and updates. The data in Active Directory is replicated, meaning that updates occurring to the Active Directory on one domain controller are sent to other domain controllers in the network. Because the data is replicated, Active Directory is not well suited for dynamic data that frequently changes — for example, CPU utilization and Internet stock prices.

Note

Note

However, you can retrieve frequently changing data, such as computer system performance and event logging information, by using WMI. For more information about WMI, see “WMI Scripting Primer” in this book.

A critical part of managing Active Directory with ADSI scripts is knowing where directory objects are replicated. Active Directory is divided into partitions to reduce replication. Partitions are either fully replicated (full replicas) or partially replicated (partial replicas). Full replicas are partitions that are replicated to every domain controller in the forest and can be read or written to from any domain controller. Partial replicas are read-only partitions that contain a subset of data contained in Active Directory.

Partitions Replicated on Domain Controllers

The three full replicas found on each domain controller are the:

  • Schema partition. This partition contains all of the classes and attributes defined in the forest.

  • Configuration partition. This partition contains system information that all domain controllers must store — this includes information about the various partitions, forest-wide services, sites, and well-known security principals in the forest.

  • Domain Directory partition. This partition contains all of the objects created in a particular domain and is replicated to every domain controller within a domain. Unlike the other two types of partitions, which are full replicas, this partition is different for each domain.

Note

Note

Other replicas might be present on domain controllers.

Attributes Replicated to the Global Catalog

All domain controllers designated as Global Catalog servers contain a partial replica of all other domain directory partitions in the forest. This partial replica, appropriately named the Global Catalog, contains all of the objects in the domain directory partition, but only a subset of the attributes of these objects. Knowing which attributes are contained in the Global Catalog is critical to creating scripts that perform search operations efficiently. For example, if you write a script to request an attribute that is not in the Global Catalog and you want the script to return results from more than one domain, you must use referral chasing. Referral chasing increases network congestion and can cause the script to respond slowly. For information about referral chasing, see “Searching Active Directory” earlier in this chapter.

To avoid referral chasing, read attributes that are contained in the Global Catalog. By doing so, you ensure that the script contacts a single domain controller designated as a Global Catalog server to fulfill the request of the script.

The isMemberOfPartialAttributeSet attribute contains a True or False value indicating whether an attribute is replicated to the Global Catalog. Listing 5.50 shows how to determine whether an attribute is replicated to the Global Catalog.

  1. Initialize a variable named strAttributeName with the common name of an attribute.

    In this example, the script tests the given-name attribute. To test other attributes, simply initialize the attribute with the common name of a different attribute.

  2. Use rootDSE to determine the value of the schemaNamingContext attribute.

  3. Bind to the attribute in the schema container using the GetObject function and the LDAP provider.

  4. Initialize the blnInGC variable with the Boolean value contained in the isMemberOfPartialAttributeSet attribute.

    • If blnInGC is True, echo to the command window that the attribute is replicated to the Global Catalog.

    • Otherwise, echo to the command window that the attribute is not replicated to the Global Catalog.

Example 5.50. Reading the isMemberOfPartialAttributeSet Attribute of an Attribute

 1 strAttributeName = "cn=given-name"
 2
 3 Set objRootDSE = GetObject("LDAP://rootDSE")
 4 Set objSchemaAttribute = GetObject("LDAP://" & strAttributeName & "," & _
 5     objRootDSE.Get("schemaNamingContext"))
 6
 7 blnInGC = objSchemaAttribute.Get("isMemberOfPartialAttributeSet")
 8 If blnInGC Then
 9     Wscript.Echo strAttributeName & _
10         " is replicated to the Global Catalog."
11     Else
12         Wscript.Echo "The " & strAttributeName & _
13             " is not replicated to the Global Catalog."
14 End If

When this script runs, it echoes a message indicating that the attribute is contained in the Global Catalog, as shown:

cn=given-name is replicated to the Global Catalog.

After you have determined that an attribute is in the Global Catalog, you specify GC instead of LDAP when constructing a query string. For information about creating a query string that uses the Global Catalog, see “Searching Active Directory” earlier in this chapter.

In Listing 5.50, you might have noticed that the script searches for an attribute (isMemberOfPartialAttributeSet) in an attribute (givenName or cn=given-name). This illustrates the fact that attributes can contain attributes. In fact, if you view the schema from a tool such as the ADSI Edit snap-in, you will see that attributes and classes are viewed as objects. As you know, Active Directory objects contain attributes.

Indexed Attributes

Another important aspect to consider when performing a search operation is whether the attribute to be sorted is indexed. Indexed attributes are already sorted, which reduces the processing requirements placed on a domain controller when you perform a search operation that includes sorting the result set. For information about enabling a sort operation in a search, see “Searching Active Directory” earlier in this chapter.

The searchFlags attribute contains an integer value indicating, among other things, whether an attribute is indexed. Listing 5.51 shows how to determine whether an attribute is indexed. This script is similar to Listing 5.50, so only steps that differ are shown here.

  1. Set the IS_INDEXED constant to determine later in the script whether an attribute is indexed.

  2. Use rootDSE to determine the value of the schemaNamingContext attribute.

  3. Initialize the intSearchFlags variable with the integer value contained in the searchFlags attribute (line 8).

  4. Use rootDSE to determine the value of the schemaNamingContext attribute.

  5. Use the AND operator to evaluate the value of IS_INDEXED against the first bit in the searchFlags attribute.

    • If the first bit in the searchFlags attribute is on, echo to the command window that the attribute is indexed.

    • Otherwise, echo to the command window that the attribute is not indexed.

Example 5.51. Reading the searchFlags Attribute to Determine Whether an Attribute Is Indexed

 1 Const IS_INDEXED = 1
 2 strAttributeName = "cn=given-name"
 3
 4 Set objRootDSE = GetObject("LDAP://rootDSE")
 5 Set objSchemaAttribute = GetObject("LDAP://" & strAttributeName & "," & _
 6     objRootDSE.Get("schemaNamingContext"))
 7
 8 intSearchFlags = objSchemaAttribute.Get("searchFlags")
 9 If IS_INDEXED AND intSearchFlags Then
10     Wscript.Echo strAttributeName & " is indexed."
11 Else
12     Wscript.Echo strAttributeName & " not indexed."
13 End If

When this script runs, it echoes a message indicating that the given-name attribute is indexed, as shown:

cn=given-name is indexed.

Attributes That Are Both Replicated to the Global Catalog and Indexed

Ideally, search operations should use attributes that are both replicated to the global catalog and indexed. To retrieve a result set containing all attributes that meet both of these criteria by default, it is more efficient to perform a search operation than it is to bind to each attribute individually. For more information about performing efficient search operations, see “Optimizing Search Performance” earlier in this chapter.

Caution

Caution

You can configure attributes in the schema to be replicated to the Global Catalog or to be indexed by using a tool such as the Active Directory Schema snap-in. However, you must be cautious about how you modify the schema because improper modifications can damage Active Directory or severely affect network and server performance.

Listing 5.52 shows how to perform a search operation to retrieve a result set of all attributes that are both replicated to the Global Catalog and indexed. The steps to complete this task are similar to the search tasks shown earlier in this chapter; therefore, steps are summarized.

  1. Set the IS_INDEXED constant to determine later in the script whether an attribute is indexed.

  2. Use rootDSE to determine the value of the schemaNamingContext attribute and initialize the strADsPath variable.

  3. Using ADO, query Active Directory for all AttributeSchema objects and return the lDAPDisplayName, isMemberOfPartialAttributeSet, and searchFlags attributes of the objects.

    The objectCategory=AttributeSchema returns objects in the schema that are defined as attributes.

  4. Use a While Wend statement to read each record in the result set.

  5. For each record in the result set, determine both whether the attribute is contained in the Global Catalog — isMemberOfPartialAttributeSet = True — and whether the attribute is indexed — first bit of the searchFlags attribute is on (lines 20 and 21).

    If both conditions are true, display the lDAPDisplayName of the attribute (stored in the strAttribute variable).

Example 5.52. Locating Attributes That Are in the Global Catalog and Indexed

 1 Const IS_INDEXED = 1
 2
 3 Set objConnection = CreateObject("ADODB.Connection")
 4 objConnection.Open "Provider=ADsDSOObject;"
 5
 6 Set objCommand = CreateObject("ADODB.Command")
 7 objCommand.ActiveConnection = objConnection
 8
 9 Set objRootDSE = GetObject("LDAP://rootDSE")
10 strADsPath = "<LDAP://" & objRootDSE.Get("schemaNamingContext") & ">"
11 objCommand.CommandText = strADsPath & _
12     ";(objectCategory=AttributeSchema);" & _
13       "lDAPDisplayName,isMemberOfPartialAttributeSet,searchFlags;onelevel"
14
15 Set objRecordSet = objCommand.Execute
16 Wscript.Echo "Attributes in Global Catalog and indexed: "
17 While NOT objRecordSet.EOF
18     strAttribute = objRecordSet.Fields("lDAPDisplayName")
19     If objRecordSet.Fields("isMemberOfPartialAttributeSet") AND _
20         (IS_INDEXED AND objRecordSet.Fields("searchFlags")) Then
21             Wscript.Echo strAttribute
22     End If
23     objRecordSet.MoveNext
24 Wend
25
26 objConnection.Close

When this script runs, it echoes the lDAPDisplayName of the attributes that are both contained in the Global Catalog and indexed, as shown in the following abbreviated result set:

Attributes in Global Catalog and indexed:
AltSecurityIdentities
cn
displayName
mail
...
name
sAMAccountName
sAMAccountType
servicePrincipalName
sIDHistory
sn

Operational Attributes

Not all attribute values are stored in a directory service. Instead, attribute values that are not contained in the directory can be calculated when a request for the attribute is made. This type of attribute is called operational. Note that this type of attribute is defined in the schema but it does not contain a value in the directory. Instead, the domain controller that processes a request for an operational attribute calculates the attribute’s value to answer the client request.

It is critical that you know which attributes are operational because, unlike the other attributes of an object, operational attributes are not downloaded to the local property cache unless you make an explicit call to the GetInfo or GetInfoEx method. For more information about how to use these methods, see “Data Caching” earlier in this chapter.

The script in Listing 5.53 determines which attributes in the schema are operational. It accomplishes this task by reading the systemFlags attribute of each AttributeSchema object. The steps to complete this task are similar to those for Listing 5.52; therefore, these steps are summarized.

  1. Set the ADS_SYSTEMFLAG_ATTR_IS_CONSTRUCTED constant to determine later in the script whether an attribute is operational.

  2. Use rootDSE to determine the value of the schemaNamingContext attribute and initialize the strADsPath variable.

  3. Using ADO, query Active Directory for all AttributeSchema objects and return the lDAPDisplayName and systemFlags attributes of the objects.

  4. Use a While Wend statement to read each record in the result set.

  5. For each record in the result set, determine whether the attribute is operational, indicated by whether the third bit of the searchFlags attribute is on (lines 19 and 20).

    • If the searchFlags attribute is on, display the lDAPDisplayName of the attribute (stored in the strAttribute variable).

Example 5.53. Determining Which Attributes Are Operational

 1 Const ADS_SYSTEMFLAG_ATTR_IS_CONSTRUCTED = &h4
 2
 3 Set objConnection = CreateObject("ADODB.Connection")
 4 objConnection.Open "Provider=ADsDSOObject;"
 5
 6 Set objCommand = CreateObject("ADODB.Command")
 7 objCommand.ActiveConnection = objConnection
 8
 9 Set objRootDSE = GetObject("LDAP://rootDSE")
10 strADsPath = "<LDAP://" & objRootDSE.Get("schemaNamingContext") & ">"
11 objCommand.CommandText = strADsPath & _
12     ";(objectCategory=AttributeSchema);" & _
13         "lDAPDisplayName,systemFlags;onelevel"
14
15 Set objRecordSet = objCommand.Execute
16 Wscript.Echo "Constructed Attributes: "
17 While NOT objRecordSet.EOF
18     strAttribute = objRecordSet.Fields("lDAPDisplayName")
19     If ADS_SYSTEMFLAG_ATTR_IS_CONSTRUCTED AND _
20         objRecordSet.Fields("systemFlags") Then
21             Wscript.Echo strAttribute
22             objRecordSet.MoveNext
23     End If
24 Wend
25
26 objConnection.Close

When this script runs, it echoes a list of operational attributes, as shown in the following abbreviated result set:

Constructed Attributes:
allowedAttributes
allowedAttributesEffective
allowedChildClasses
allowedChildClassesEffective
aNR
attributeTypes
canonicalName
createTimeStamp
...

ADSI Architecture

Understanding architectural aspects of Active Directory is a critical part of writing ADSI scripts that perform a wide variety of tasks efficiently. In fact, the schema of the underlying directory service ultimately defines the rules for using ADSI to programmatically administer the directory. For example, an ADSI script that creates an object in Active Directory must also write mandatory attributes defined for the object that are not automatically set by the directory.

Another critical part of writing ADSI scripts is having an understanding of the ADSI object model, the ADSI file and logical components, the various ADSI providers available for directory access, and the interfaces that make performing tasks possible.

ADSI Object Model

ADSI is a collection of dynamic-link libraries (DLLs) that are used by client applications such as Windows Script Host (WSH) and Active Directory Users and Computers to administer directory services such as Active Directory.

When you perform a binding operation in an ADSI script, the script uses the moniker in the ADsPath to load the proper ADSI provider DLL. For example, specifying the LDAP moniker loads the LDAP provider, (Adsidp.dll). The ADSI provider DLL creates or instantiates a Component Object Model (COM) object. The COM object is given the name you provide as a reference in the Set statement, for example, objUser, objGroup, or objDomain.

Each instantiated object contains interfaces. An interface is a grouping of related methods. As all of the preceding listings demonstrated, methods allow you to perform administrative tasks upon a directory. For example, you used the Create, Put, Get, and Delete methods to perform create, write, read, and delete tasks in Active Directory. All of these methods are contained in a single interface named IADs. Figure 5.7 shows the relationship between a binding operation, a provider, a COM object, its interfaces, and a directory.

Creating a COM Object Representation of a Directory Object

Figure 5.7. Creating a COM Object Representation of a Directory Object

In Figure 5.7, the LDAP provider instantiates a COM object. The provider determines that MyerKen in the HR OU of the na.fabrikam.com domain is a user account object, so it creates a COM object with the appropriate interfaces for interacting with a user account object. Only three interfaces, IADs, IADsUser, and IUnknown appear in the figure. IADs and IADsUser are the two interfaces that you are most likely to use from an ADSI script to interact with an Active Directory user account object. The IUnknown interface is attached to every COM object and is the base interface for all other interfaces.

The Set statement in Figure 5.7 provides a reference to the object and names the object objUser.

ADSI Installation Components

ADSI resides in the systemrootSystem32 folder of Microsoft® Windows NT®-based client computers. The file names are ActiveDS.dll, ActiveDS.tlb, and DLLs starting with the ads prefix, such as Adsldp.dll. An installation of ADSI also updates some DLLs, such as Wldap32.dll.

ADSI is a system component on computers running Windows 2000 and Microsoft® Windows® XP operating systems. Therefore, you do not have to install it separately from the operating system. In contrast, ADSI is not a system component on computers running Microsoft® Windows® 95, Windows® 98, Windows® Millenium Edition (Me), or Windows NT 4.0. This means you must install it to use it on these computers. For information about how you can install ADSI on client computers, see the Active Directory Service Interfaces link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources.

ADSI Layers

The ADSI architecture can be visualized as consisting of five layers, as shown in Figure 5.8. From the bottom of the figure up, the layers are:

  • Directory or Namespace

  • ADSI Provider

  • ADSI Router and Registry

  • Property Cache

  • ADSI application

ADSI Architecture

Figure 5.8. ADSI Architecture

The five layers appearing in Figure 5.8 reside in two places. The lowest layer, the directory or namespace, resides on computers running directory services accessible by ADSI, and the other three layers reside on client computers.

Directory Namespace

A directory service stores information about all resources on a network and makes the information accessible to users and applications. The directory is the physical database where information about the network is stored.

A namespace is an area where a name can be resolved. For example, the Domain Name System (DNS) namespace is a tree of domains where host names and IP addresses are contained. Name resolution allows computers to use the DNS namespace to resolve host names to IP addresses and IP addresses to host names. Similarly, a directory namespace is a defined area where a network name (directory element) can be resolved into an object regardless of where it resides within the namespace. In the case of Active Directory, the namespace is the forest and Active Directory provides name resolution services.

Any network names in Active Directory can be resolved into objects. For example, directory elements such as domains, user accounts, groups, OUs, and computers can all be resolved into objects. In turn, the object contains properties that you can manage using ADSI scripts. For example, after you have obtained a domain object using a script, you can create OUs; after you have obtained an OU object in a script, you can create user accounts, groups, and other objects.

Providers

ADSI is not part of the directory service itself, but it provides the interfaces to interact with the directory namespace. You can use ADSI to access all of the following directory namespaces:

  • LDAP directories, including Active Directory

  • Security Accounts Manager (SAM) databases, in both local workstations and Windows NT domains

  • Internet Information Services (IIS) metabase

  • Novell NetWare Directory Services (NDS)

  • NetWare Bindery

ADSI providers access the directory namespace and create virtual representations of objects that you then administer with ADSI interfaces. In the suite of ADSI DLLs, there is a subset of provider DLLs that access various directory services, as shown in Table 5.5.

Table 5.5. ADSI Providers and the Directory Services They Access

Provider

To Access

LDAP provider

LDAP Version 2– and Version 3–based directories, including Active Directory.

GC provider

The Global Catalog on Active Directory domain controllers designated as Global Catalog servers. The GC provider is similar to the LDAP provider but uses TCP port number 3268 to access the Global Catalog.

ADSI OLE DB provider

Active Directory to perform search operations.

WinNT provider

Windows NT domains and Windows NT/Windows 2000/Windows XP local account databases.

IIS provider

The Internet Information Services (IIS) metabase.

NDS provider

Novell NetWare Directory Services.

NWCOMPAT provider

The Novell Netware Bindery.

ADSI providers act as the intermediaries between a directory namespace and an ADSI application, such as an ADSI script. All of the listings in this chapter bind to Active Directory using the LDAP provider or the GC provider.

Note

Note

Provider names are case sensitive. All ADSI providers listed are uppercase except for the WinNT provider, which is a mixture of uppercase and lowercase. Also, unlike the other providers listed, the IIS provider is not categorized as a system provider.

Binding to a directory varies from one provider to the next. However, the binding syntax always begins with the name of the provider, followed by a colon, two forward slashes, and a path distinctive to the type of directory you are binding to.

As explained in “Step 1: Establishing a Connection” earlier in this chapter, the ADsPath is the name of the provider combined with the path to a directory object. The ADsPath syntax from one provider to the next is unique to avoid naming conflicts from one directory namespace to the next. The following list shows ADsPath syntax examples for many of the ADSI providers:

  • LDAP provider path to a domain controller in the na.fabrikam.com domain: LDAP://dc=NA,dc=fabrikam,dc=com. Notice that the example does not specify a server name, just a domain name. This method of binding is called serverless binding. It is a good idea to use serverless binding whenever possible so that the script is not tied to completing a bind operation to a specific domain controller.

  • GC provider path to a global catalog server in the na.fabrikam.com domain: GC://dc=NA,dc=fabrikam,dc=com.

  • WinNT provider path to a domain controller in the na.fabrikam.com domain: WinNT://NA.

  • IIS provider path to the IIS server sea-dc-01.na.fabrikam.com: IIS://sea-dc-01.na.fabrikam.com.

  • NDS provider path to a directory server in the na.fabrikam.com domain: NDS://server01/o=org01/dc=com/dc=fabrikam/dc=na.

  • NWCOMPAT provider to a bindery server: NWCOMPAT://server01.

The ADSI OLE DB provider uses the same ADsPath syntax as the LDAP and GC providers but with additional parameters to control what is returned by a search operation. For more information, see “Searching Active Directory” earlier in this chapter.

Tip

Tip

Use rootDSE to avoid hard-coding distinguished names into the ADsPath of an Active Directory bind operation. For more information about rootDSE, see “Root Directory Service Entry” earlier in this chapter.

There is a special provider called the ADSI namespace provider. You can use this provider to return a list of all providers installed on a client computer, as shown in Listing 5.54.

Example 5.54. Displaying a List of Installed ADSI Providers

1 Set objProvider = GetObject("ADs:")
2 For Each Provider In objProvider
3     Wscript.Echo Provider.Name
4 Next

When this script runs, it echoes the name of each installed provider, as shown in the following list that was generated from a Windows 2000 Professional computer:

WinNT:
NWCOMPAT:
NDS:
LDAP:
IIS:

Caution

Caution

Although the ADSI WinNT provider can be used to manage a subset of objects and attributes in Active Directory, using the WinNT provider to manage Active Directory is not recommended because it can have adverse effects on a script’s performance and resiliency.

Router

When a script calls the GetObject function, the ADSI router receives the request, identifies the provider being requested, searches the registry for information about the provider, and then loads the provider into local memory. The router identifies the provider by matching the provider name (its ProgID) with its ProgID as listed in the registry. The provider then passes the requested named element back to the ADSI router, and the router creates the ADSI object that is then passed back to the calling script.

The ADSI Router is responsible for creating many of the ADSI core objects and the corresponding interfaces used to manage elements in ADSI-supported directories. After object creation (completing the binding operation), object management, such as reading and writing object attributes, is undertaken directly between the ADSI application and the provider.

Property Cache

Immediately following the binding operation, a region of local memory is set aside to hold a virtual representation of an Active Directory object. The Active Directory object and its attributes are not downloaded to the property cache during the binding operation. This is important because the Active Directory object can contain hundreds of attributes, most of which you will not need to manage in a single script. For more information about the local property cache, see “Data Caching” earlier in this chapter.

ADSI-Enabled Applications

This is the top layer of ADSI, and is where you interact with the rest of the architecture. All of the listings in this chapter demonstrate how to use WSH and VBScript to create scripts that interact with ADSI. Many other applications depend on ADSI, such as the Active Directory Users and Computers and ADSI Edit snap-ins. These GUI-based applications can help you manage directories and gain a further understanding of how directories operate.

ADSI Interfaces

Each interface contains a set of methods you use to programmatically administer directories. Methods either perform an action or identify something about the object to which they are attached. Methods that perform an action are always referred to as methods, and methods that describe an object are often called properties.

A common convention for showing an interface and its method or property is interfacename::methodname or interfacename::propertyname. For example, the GetEx method of the IADs core interface is written as IADs::GetEx.

Whether an interface is available to administer a directory service is dependent on the provider being used. Providers do not implement all interfaces. For example, the LDAP provider does not implement the IADsADSystemInfo interface because this interface is specifically designed to retrieve information about a client computer and the currently logged-on user in an Active Directory domain. In contrast, the LDAP provider is specifically designed to interact on domain controllers with Active Directory and other LDAP-compliant directories.

Note

Note

To use the IADsADSystemInfo interface, the computer must be running Windows 2000 or an operating system in the Windows XP family and the interface must be created separately from the binding operation.

In addition, providers do not necessarily implement all methods within an interface. For example, the LDAP provider does not implement the CopyHere method of the IADsContainer interface. Instead, you must use other techniques to perform a copy operation.

For a list of the interfaces supported by each system provider, go to the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources and search for the topic “Provider Support of ADSI Interfaces.”

Categorizing Interfaces

Interface categorizations make it easier to locate the appropriate interface for a particular task. Table 5.6 shows the interface categories, a general description of their purposes, and the interfaces within each category that are implemented by the LDAP provider.

Table 5.6. Interfaces Implemented by the LDAP Provider

Categories

LDAP Provider–Specific Purpose

Interfaces

Core

General-purpose interfaces for interacting with almost any object in a directory. You are likely to use these interfaces most of the time in ADSI scripts.

IADs, IADsContainer, IADsNamespaces, IADsOpenDSObject

Schema

Interfaces for managing and extending a directory’s schema.

IADsClass, IADsProperty, IADsSyntax

Property Cache

Interfaces for interacting with the local property cache.

IADsPropertyEntry, IADsPropertyList, IADsPropertyValue, IADsPropertyValue2

Persistent Object

Interfaces for interacting with a specific type of object or a logical grouping of attributes.

IADsGroup, IADsLocality, IADsMembers, IADsO, IADsOU, IADsPrintQueue, IADsUser

Dynamic Object

Interface for interacting with a print queue published to the directory.

IADsPrintQueueOperations

Security

Interfaces that interact with DACLs assigned to directory objects.

IADsAccessControlEntry, IADsAccessControlList, IADsSecurityDescriptor

Non-Automation

Interfaces that provide low-level access to directory objects. These interfaces are not available to scripting languages such as VBScript.

IDirectoryObject, IDirectorySearch

Extension

Interface for adding methods and other functions to existing objects.

IADsExtension

Utility

Interfaces that provide helper functions. IADsDeleteOps, IADsPathname,

IADsObjectOptions

Data Type

Interface for interpreting attributes that are stored as large integer (64-bit) data types.

IADsLargeInteger

LDAP Provider Objects and Their Interfaces

Grouping interfaces by the objects that they support simplifies the process of determining the properties and methods that are implemented for an object. For a table showing the ADSI object types generated by the LDAP provider, such as generic object (GenObject) and User, and the ADSI interfaces supported by each object, see the Active Directory Programmer’s Guide link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources and search for the topic “ADSI Objects of LDAP.” For tables implemented by the other system providers, search for the following topics:

  • “ADSI Objects for WinNT”

  • “ADSI Objects for NDS”

  • “ADSI Objects of NWCOMPAT”

The core interfaces, IADs and IADsContainer, are the two interfaces you are most likely to encounter when working with COM objects instantiated by the ADSI system providers. Interfaces implemented by the LDAP or WinNT provider to perform specialized tasks, such as changing a user account password or reading an attribute stored as a large integer, appear in the ADSI task-based chapters in this book.

IADs Properties

The six read-only properties in the IADs interface let you uniquely identify every object in the directory.

  • IADs::ADsPath. Returns a string containing the fully qualified path of the object in the directory. The path uniquely identifies the object in the directory and can be used to retrieve the object as long as the object is not moved.

  • IADs::Class. Returns a string containing the name of the schema class of the object.

  • IADs::GUID. Returns a string containing the globally unique identifier (GUID) of the object.

    You can use the GUID returned by this property to bind directly to the object. If you choose to do this, however, the ADsPath property will return a GUID ADsPath.

  • IADs::Name. Returns a string containing the relative name of the object within the underlying directory service. This name distinguishes the object from its siblings.

  • IADs::Parent. Returns a string containing the ADsPath of the parent object.

    Although concatenating the Parent property to the Name property in an ADSI script might sometimes return the object’s ADsPath, it is not guaranteed to return valid results. Use the object’s ADsPath property to retrieve the actual ADsPath.

  • IADs::Schema. Returns a string containing the schema class of the object.

    You can use the string returned by this property to bind to the schema class object. After you have bound to the schema class object, you can determine the mandatory and optional attributes defined for objects based on the schema. For more information about mandatory and optional attributes, see “Classes and Attributes” earlier in this chapter.

The script in Listing 5.55 displays information about the na.fabrikam.com domain using the IADs properties collection.

Example 5.55. Displaying Information About a Domain Using IADs Properties

1 Set objDomain = GetObject("LDAP://dc=NA,dc=fabrikam,dc=com")
2 Wscript.Echo "ADsPath:" & objDomain.ADsPath
3 Wscript.Echo "Class:" & objDomain.Class
4 Wscript.Echo "GUID:" & objDomain.GUID
5 Wscript.Echo "Name:" & objDomain.Name
6 Wscript.Echo "Parent:" & objDomain.Parent
7 Wscript.Echo "Schema:" & objDomain.Schema

When this script runs, it echoes IADs properties in the domain object, as shown in the following list:

ADsPath:LDAP://dc=NA,dc=fabrikam,dc=com
Class:domainDNS
GUID:618a1da520255a41ab778f5dc1d8da4d
Name:dc=NA
Parent:LDAP://dc=fabrikam,dc=com
Schema:LDAP://schema/domainDNS

IADsContainer Property

The one read-only property in IADsContainer lets you read the object classes specified in the Filter method.

  • IADsContainer::Filter. This property returns the class of objects being filtered in a container enumeration.

    IADsContainer::Filter is also considered a method because you use it to set the Filter property on a container object to be enumerated. Therefore, to separate the property from the method, the property is referred to as get_Filter because it reads the value of the filter. The method is referred to as put_Filter because it sets or writes the value of the filter.

The other two properties of IADsContainer, IADsContainer::Count and IADsContainer::Hints, are not implemented in the LDAP provider.

The script in Listing 5.56 displays the Filter property in the container enumeration of the HR OU. Note that you must assign a value to the Filter property by using the Filter method, as shown in line 2 of the listing.

Example 5.56. Displaying the Filtered Property

1 Set objOU = GetObject("LDAP://OU=HR,dc=NA,dc=fabrikam,dc=com")
2 ObjOU.Filter= Array("Computer", "User")
3 Wscript.Echo "Object Filters:"
4 For each strObject in objOU.Filter
5     Wscript.Echo vbTab & strObject
6 Next

When this script runs, it echoes the IADsContainer::Filter property in the HR OU, as shown in the following list:

Object Filters:
       Computer
       User

For information about IADsContainer methods, see the following sections earlier in this chapter:

One method of IADsContainer that requires some explanation beyond referring to sections earlier in this chapter is:

  • IADsContainer::GetObject. This method retrieves an IADs reference to an object in a container.

After you have used the VBScript GetObject function to bind to a container, you can then use the IADs::GetObject method to retrieve an object from the container. However, for simplicity, you will usually complete a single GetObject function call to bind directly to an object in a container instead of performing the binding operation in two steps.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset