Chapter 7. Active Directory Users

Managing user data is a critical function in any organization. Using graphical user interface tools for one user or one task at a time, in all but the smallest companies, can be a costly and time-consuming exercise. Through scripting, you can manage the entire life cycle of all the individual users accounts in an enterprise efficiently, accurately, and cost effectively. To help you automate user account management tasks locally and remotely, the Microsoft® Windows® 2000 family of operating systems provide a variety of scripting tools.

In This Chapter

User Account Overview

Managing user accounts is a fundamental task common to all enterprises that deploy the Active Directory® directory service. Although you can manage Active Directory user accounts by using graphical user interface (GUI) tools such as Active Directory Users and Computers, you might find this approach less than ideal in large, distributed environments. Active Directory user accounts consist of more than 250 attributes. Management of the many attributes exposed through the standard user account properties pages can be time-consuming and error-prone when performed manually.

Suppose user accounts are created manually for each user in an organization. A request is made to create a user account for a new user named Ken Myer. When the request is received, one administrator manually creates the user account and uses the “first name, first initial of the last name” notation to name the user account KenM. Another administrator sees the same request for user account creation and uses the “first name, dot, last name” notation to name the user account Ken.Myer. While both naming conventions are appropriate, they are inconsistent and, in this example, lead to the creation of two user accounts for the same user. Using a script, you can specifically define the naming policy for user accounts. If both administrators attempt to create the Ken Myer user account, only the first administrator’s effort will succeed (the second attempt will fail because the account already exists). A single, uniformly named user account is created for the user because the naming convention for all user accounts is the same.

Errors can be introduced in a number of ways, such as by not following naming conventions or by mistyping initial passwords. Creating Active Directory user accounts that follow naming and configuration guidelines is further complicated by the hundreds of user account attributes available in each Active Directory user account. Unofrtunately, to reduce the amount of time it takes to create user accounts, system administrators commonly take shortcuts by leaving out a number of useful attributes (such as phone number or office location) when creating user accounts.

The most significant challenges in creating Active Directory user accounts are complying with naming conventions and consistently configuring user account attributes properly for all users. Scripting user account creation is an ideal way to maintain consistency while making the account creation process rapid and relatively error free.

The scripting solutions presented in this chapter, which use ADSI and Microsoft® Visual Basic® Scripting Edition (VBScript), provide an alternative to the GUI-based approach to user account management and thus reduce the amount of time and potential for error generally associated with manual entry of user data. Furthermore, as you become comfortable using the techniques that follow, you can create a wide range of highly customized script-based tools that address your organization’s specific needs.

Active Directory User Accounts

Consider the life cycle of an Active Directory user account. Common user account administrative tasks that occur over the lifetime of a user account include creating the user account, setting the user account’s initial password, enabling the user account, and configuring the user account’s attributes. Scripts can be used to automate all of these user account management tasks.

To maintain control of user accounts on an ongoing basis, you can create scripts to audit user accounts, generate reports, verify common settings, and change user account settings as necessary. Other common management tasks that can be scripted include copying, moving, and renaming user accounts.

If you need to locate multiple user accounts that reside in different organizational units or domains, you can use scripts to search for the user accounts in Active Directory. Finally, when an employee leaves the company, you can disable the employee’s user account for a fixed amount of time and delete the user account once you are certain the account is no longer needed. All of the steps in the life cycle of a user account can be automated using scripts.

Active Directory User Account Objects

Learning to automate Active Directory user operations begins with a basic understanding of each type of Active Directory object, each object’s attributes, and the tasks commonly associated with each type of object.

A user account object, like all objects in Active Directory, is derived from a class. A class can be thought of as a template that defines what an object can or must contain. Just as a word processing template defines the content of a document created from it, a class defines the content of an object created from it.

User Account Types

In most cases, a user account is created so that a person or a program, such as a service, can log on to a computer or a domain. To access resources in an Active Directory forest, each user or application must have an account in Active Directory. Domain controllers running Windows 2000 use accounts to verify that the user or application has permission to use a resource.

Active Directory defines two types of user account objects: User and Contact.

User Account

The User account is the primary Active Directory object type used to represent users. Users can be people who log on to the network or services that must log on in order to run. An Active Directory User account is a security principal that the Windows 2000 security subsystem recognizes.

When a user logs on to the domain, the domain controller verifies the user’s password by comparing it with the corresponding user account object in the Active Directory database. If the password presented matches the password stored in the corresponding Active Directory user account object, the domain controller produces an access token, which is subsequently used to verify access to computing resources throughout the forest.

Contact Account

The Contact user account object type is used to represent human users for address book, distribution list, and e-mail purposes; however, a contact account is not a security principal. A Contact account has no security context and therefore cannot be used to log on to a domain or to control access to computing resources.

Identification Attributes

All Active Directory user account object types are identified by several attributes defined in the Active Directory schema. All user account object types have names in the form of cn (common name), name, distinguishedName, and objectGUID. For example, a user account object created for a user named Ken Myer and stored in the Management organizational unit (OU) of the na.fabrikam.com domain could be assigned the following names:

  • cn and name: MyerKen

    When you create the user account, you must give the object a name. The name you provide is the relative distinguished name of the object and must be unique in the current container. The value you provide is assigned to the user’s cn (Common-Name) and name (RDN) attributes.

  • distinguishedName: cn=MyerKen,ou=Management,dc=na,dc=fabrikam,dc=com

    The value of a user account’s distinguishedName (Obj-Dist-Name) attribute is constructed from the user’s relative distinguished name and the relative distinguished name of each parent all the way to the root of the directory. Because a user account’s distinguishedName is automatically generated, you cannot explicitly set its value. However, an object’s distinguishedName changes when the object is moved or renamed. If the MyerKen account is moved to the Finance OU, the new distinguishedName for the account will be this:

    cn=MyerKen,ou=Finance,dc=na,dc=fabrikam,dc=com

  • objectGUID: 77 22 D5 1D B8 65 67 4F A9 C2 10 19 D1 D7 4E 9E (hexadecimal value)

    Active Directory assigns a unique value to the objectGUID attribute for each object when it is created. The objectGUID is a unique 128-bit structure used by the system to identify the object. The objectGUID does not change even if a user account object is moved or renamed. You will rarely, if ever, need to use the objectGUID when writing scripts to manage users and user accounts.

Security Principal Naming Attributes

Because the user account object type is a security principal, it has additional naming attributes, such as sAMAccountName, userPrincipalName, and objectSid. The security principal naming attributes are critical for the Windows 2000 security system to recognize user accounts. The MyerKen user account mentioned in the preceding example could be assigned the following security naming attributes:

  • sAMAccountName: myerken

    This mandatory attribute is used to log on to the domain from computers running versions of Windows earlier than Windows 2000 and by other computers running the LAN Manager client redirector. This value must be unique in the domain and must be no longer than 20 characters.

  • userPrincipalName (UPN): [email protected]

    This attribute can be used to log on to the domain from computers running Windows 2000 or Microsoft® Windows® XP. The UPN is assigned an e-mail style value to simplify logon to the domain. For consistency and ease of use, consider making the UPN the same as the user’s e-mail address.

  • objectSid: 01 05 00 00 00 00 00 05 15 00 00 00 83 3D 2B 46 67 FD 7C 30 F8 9F B4 74 6B 04 00 00 (hexadecimal value)

    The domain security authority, an operating system component of Windows 2000, assigns a unique value to the objectSid attribute for user account types that are security principals. When a user logs on to the domain, the security system assigns the value in the objectSid to the user’s access token. The access token identifies the user account to the Windows 2000 security infrastructure.

User account types that are security principals also contain security attributes. These attributes define account and password characteristics that help to maintain domain security. For example, the accountExpires attribute can define a date when a user account will automatically expire. This ensures that a user will not be able to access the network with this user account after the expiration date.

All user account types contain address book attributes that provide supplementary information to identify user accounts. For example, the displayName attribute contains a friendly name for each user account. This name appears in the directory to assist users in identifying other users in the network.

Creating User Accounts

Creating user accounts is a fundamental task for any organization supporting computer users. In most network environments, each employee is assigned a user account that must be used in order to access local and network computing resources.

As shown in the following code sample, it takes only a few lines of code to create a user account. To create a user account, the script needs only to contain a valid path to the Active Directory container where a user account should be created, as well as two mandatory attributes of the user class, the common name (cn attribute) and the SAM account name (sAMAccountName attribute). For example, this code creates a user account with the common name MyerKen:

Set objOU = GetObject("LDAP://ou=Management,dc=NA,dc=fabrikam,dc=com")
Set objUser = objOU.Create("User", "cn=MyerKen")
objUser.Put "sAMAccountName", "myerken"
objUser.SetInfo

The system automatically generates a GUID and a security identifier (SID) for the user account object, as well as the optional attributes listed in Table 7.1.

Table 7.1. Default Settings for User Account Object Optional Attributes

Attribute

Default Setting

pwdLastSet

User must change password at next logon

userAccountControl

Password Not Required

userAccountControl

Account Disabled

The user account is a member of the Domain Users group by default. However, after the user account is created, no password is assigned and the account is disabled.

Note

Note

There are over forty attributes that contain values when a user account is created. To see the attributes that contain values for user accounts, you can use the ADSI Edit snap-in.

Choosing a container for user account creation

User account objects are typically created in an OU that was itself created after Active Directory has been installed. You can also create user account objects in built-in containers, such as the Users container or domain containers, but this approach is not recommended. The Users container is primarily designed and used for migrating user accounts from domains other than Active Directory domains, such as Windows NT® 4.0 domains. Creating user account objects in OUs provides for a more organized, and thus easier to manage, directory structure.

If you create user accounts in a built-in container, you must use a naming attribute for the parent container of cn. Conversely, if you create a user account in an OU, you must use a naming attribute for the parent container of ou. The following examples show the difference between the distinguishedName attributes of two user accounts created in the na.fabrikam.com domain.

  • distinguishedName attribute of the AkersKim user account in the Users built-in container:

    "cn=AkersKim,cn=Users,dc=NA,dc=fabrikam,dc=com"
    
  • distinguishedName attribute of the MyerKen user account in the Management OU:

    "cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com"
    

Specifying a valid path for user account creation

When you create a user account object, you must specify both a path in Active Directory to the container where you want the object to be created and the cn for the user account object to give the user account object a unique identity in Active Directory.

The cn is the relative distinguished name of the user account object. The relative distinguished name is the unique name within the container where the object is created.

The combination of the path and the cn is the distinguishedName attribute of a user account object. The distinguishedName attribute is a unique name in Active Directory.

Verifying a unique name when creating a user account in the domain

The sAMAccountName attribute for a user account object must be unique throughout the domain. Thus, a script used to create user accounts should check that the sAMAccountName is unique before attempting to create the account. There are many ways to verify that a sAMAccountName is unique. For example, you can query Active Directory to verify the uniqueness of a sAMAccountName, or, without querying Active Directory, you can trap certain script errors that indicate an attempt to create a duplicate sAMAccountName. For information about verifying user account uniqueness, see “Searching Active Directory for User Accounts” later in this chapter.

The relative distinguished name must also be unique but only in the target container where the user account is created. For example, if a user account object with the cn MyerKen is in the Management OU, you cannot create another user account in the Management OU with the cn MyerKen. However, you can create another user account with the cn MyerKen in a different OU, proved this user has a unique sAMAccountName.

To create a user account object in Active Directory, use the Create method of the IADsContainer interface. Table 7.2 shows the arguments of this method.

Table 7.2. Arguments of the Create Method

Argument

Type

Required

Default

Description

Class

string

Yes

None

The class type. In this case, “User”.

cn

string (specified as cn = string)

Yes

None

The cn of the object to create. In this case, the object is a user account.

Scripting Steps

Listing 7.1 contains a script that creates an Active Directory user account object but does not check whether a user account name is unique before attempting to create the account. To carry out this task, the script performs the following steps:

  1. Bind to the OU (Management) by using the GetObject function and the LDAP provider.

    A reference (objOU) to the OU is created in local memory. Because an OU is a container, the objOU reference exposes the ADSI IADsContainer interface. IADsContainer is the ADSI interface that provides the Create method. As its name implies, the Create method is used to create objects in Active Directory.

  2. Create the object, and set the object’s mandatory attributes in the local property cache.

    The first mandatory attribute, the object’s relative distinguished name, is listed as the second parameter of the Create method on line 2. (The first parameter — User — specifies the type of object being created by the method.) The mandatory user attribute, sAMAccountName, is then added to the user account object on line 3.

  3. Commit the new object to Active Directory.

    The SetInfo method commits the new user account object to Active Directory.

Example 7.1. Creating a User Account in Active Directory

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

The user account MyerKen is created in the Management OU of the na.fabrikam.com domain. The name and cn attribute of the user account object is MyerKen; the sAMAccountName of the user account is MyerKen. There is no assigned password for this user account, and the account is disabled.

Configuring User Account Passwords

User account passwords provide a critical barrier against unauthorized access to computing resources. In most network environments, it is unacceptable to create user accounts without initial passwords. Doing so leaves the user account vulnerable to initial logon by an unauthorized user. After all, to log on to a domain, you only need two things: a valid user account and the password for that user account. If the account does not have a password, the unauthorized user needs only to guess the user account name to obtain network access.

Therefore, the next step in the life cycle of creating user accounts is assigning an initial password. You can assign initial passwords to user accounts immediately after creating them, and you can configure other password attributes to control whether the user will be required to change the password at initial logon.

Note

Note

By default, user accounts are disabled when they are created unless they are specifically enabled in the script. For information about enabling a user account object with a script, see “Enabling or Disabling a User Account” later in this chapter.

The unicodePwd attribute of each user account object contains the password. The password is an octet string stored in Unicode format and encoded with one-way format (OWF). The operating system can read the password, but you cannot use a script to decode the password to plain text. If a user forgets his or her password, there is no way for you to query the system and retrieve that password.

You can, however, use a script to set or change a password. The two methods in the IADsUser interface for assigning passwords to user accounts are SetPassword and ChangePassword. The SetPassword method is used to reset a forgotten password or to set a password immediately after a user account is created. The SetPassword method requires administrator credentials, and the method performs a replace operation.

The ChangePassword method enables users to change their password. Unlike SetPassword, ChangePassword requires the current password and a new value in order to assign a new password. This is because the ChangePassword method must perform both a delete and an add operation. The delete operation requires the current password, and the add operation requires the new password.

Table 7.3 and Table 7.4 in the following task sections contain the arguments that must be included when the SetPassword or ChangePassword method is called.

Table 7.3. Argument of the SetPassword Method

Argument

Type

Required

Default

Description

NewPassword

string

Yes

None

New password value

Table 7.4. Arguments of the ChangePassword Method

Argument

Type

Required

Default

Description

OldPassword

string

Yes

None

Current password value

NewPassword

string

Yes

None

New password value

A password cannot be assigned until after you use the SetInfo method to commit a user account to Active Directory. In contrast, other user account attributes, such as the streetAddress attribute, can be stored in the local property cache before the user account is committed to Active Directory.

Note

Note

In contrast with Active Directory user accounts, strict password policy configured for local user accounts on Windows 2000 nondomain controllers requires that SetPassword be called prior to SetInfo.

Regardless of which password assignment method you employ in your scripts, the password you assign must comply with password policies or the script will fail. If, for example, the Minimum password length policy setting is five characters and you attempt to assign a password that is three characters long, the following error message appears:

The password does not meet the password policy requirements. Check the minimum
password length, password complexity and password history requirements.

Setting User Account Passwords

After creating a user account, you should set a password that a user will enter to log on to the network. If the password policies in the domain dictate that a password is required, you will have to set a password because Active Directory does not automatically add a password to a user account when it is created.

Table 7.3 shows the argument of the SetPassword method.

Scripting Steps

Listing 7.2 contains a script that sets a user account password to a specified value. To carry out this task, the script performs the following steps:

  1. Bind to the user account object by using the GetObject function and the LDAP provider.

  2. Use the SetPassword method to set the user account’s password to the specified value.

Example 7.2. Setting a User Account Password

1 Set objUser = GetObject _
2    ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
3 objUser.SetPassword "i5A2sj*!"

After the user account is enabled, a user can log on as MyerKen in the na.fabrikam.com domain with the password i5A2sj*!. The password is case sensitive when the user logs on from a computer running a Windows NT–based operating system but is not case sensitive when the user logs on from computers running other Windows operating systems, such as Windows 98. For Windows NT–based computers, a password of PASSWORD is not the same as a password of password.

Tip

Tip

Listing 7.2 shows the basic script structure required to set a user account password. However, you can improve on this script by using the Arguments property of the Windows Script Host (WSH) WScript object to receive parameters, such as the user account name and the OU where the user account resides. For more information about using the Arguments property, see “WSH Primer” in this book.

Changing User Account Passwords

Password policies configured at the domain level by using Group Policy objects (GPOs) can dictate when users must change their passwords. Concern about unauthorized access and the need to configure more secure passwords are two reasons why users might need to preempt password policies and change their passwords immediately. The ChangePassword method allows you to create a script that users can employ to change their own passwords.

Table 7.4 shows the arguments of the ChangePassword method.

Scripting Steps

Listing 7.3 contains a script that changes a user account password. To carry out this task, the script performs the following steps:

  1. Bind to the user account object by using the GetObject function and the LDAP provider.

  2. Use the ChangePassword method to specify the current password and to change the password to the specified value.

    The current password is the first parameter that the ChangePassword method receives. The current password specified in the script must be the same as the password currently assigned to the user, or the script will fail.

    The second parameter represents the new password to be assigned to the user account.

Example 7.3. Changing a User Account Password

1 Set objUser = GetObject _
2     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
3 objUser.ChangePassword "i5A2sj*!", "jl3R86df"

Reading User Account Password Attributes

A number of password attributes affect how users are able to manage their passwords. Reading password attributes of user accounts is useful for identifying potential security holes. For example, a script can help you determine which users have not reset their passwords in the past 30 days.

Note

Note

You can make the regular changing of passwords a domain-wide requirement by configuring a password policy setting in a GPO linked to the domain. Domain-level password attributes apply to all user accounts in the domain.

Password attributes in each user account object appear in Table 7.5.

Table 7.5. Password Attributes in Each User Account

Attribute Name

User Account Setting

Data Type

pwdLastSet

Password Last Changed

Large Integer/Date Time

userAccountControl

Password Required

Integer: ADS_UF_PASSWD_NOTREQD flag Value: 0x0020

userAccountControl

Cannot Change Password

Integer: ADS_UF_PASSWD_CANT_CHANGE flag Value: 0x0040

userAccountControl

Password Never Expires

Integer: ADS_UF_DONT_EXPIRE_PASSWD flag Value: 0x1000

userAccountControl

Store password using reversible encryption

Integer: ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED flag Value: 0x0080

userAccountControl

Password Expired

Integer: ADS_UF_PASSWORD_EXPIRED flag Value: 0x80000

Password attributes that are part of each Active Directory user account object can be viewed and, in some cases, configured by using scripts. Table 7.5 shows password attributes contained in each Active Directory user account object.All password attributes appearing in Table 7.5 are stored in the userAccountControl attribute of a user object except for the pwdLastSet attribute. The userAccountControl attribute is a 4-byte (32-bit) data structure that contains flags for configuring other user account settings, such as the flag that controls whether a user account is enabled or disabled.

The userAccountControl is a type of integer wherein each bit in its value represents a unique setting. This type of integer is called a bit field. Because each bit in a bit field represents a different setting, simply examining the integer’s value as a whole number is of little use. You must examine the individual bit that corresponds to the setting you are interested in reading.

To help you identify which bit to check, programming libraries such as ADSI often include predefined constants that map the bits in a bit field to friendly names. The constants serve as bit masks, each of which is used to test whether certain bits are set in the bit field.

The set of constants that represent bit masks for properties of the userAccountControl attribute is included in the ADS_USER_FLAG_ENUM enumeration. An enumeration in this context is simply one or more constants grouped together according to their usage. The specific constant that represents a user account’s Password never expires option is ADS_UF_DONT_EXPIRE_PASSWD, which is defined as 0x10000, or &h10000 in VBScript.

For example, to determine whether a user account expires, you examine the state (1 or 0) of the ADS_UF_DONT_EXPIRE_PASSWD bit in the userAccountControl attribute. To accomplish this task, you must first read the userAccountControl attribute from a user account object. This attribute contains this and other settings. Then, you use the bitwise AND operator along with the setting’s bit mask to extract the corresponding bit values from the bit field.

Values of the Flags in the userAccountControl Attribute

Most of the password-related flags in the userAccountControl attribute can be displayed by reading the integer value of the attribute returned by the LDAP provider and IADs. Other password flags require alternative methods. Table 7.6 lists password flags in the userAccountControl attribute and the attributes that contain values corresponding to these flags.

Table 7.6. Flags in userAccountControl and Attributes to Read Using ADSI

Setting

Flag

Attribute to Read

Password Required

ADS_UF_PASSWD_NOTREQD

userAccountControl

Password Never Expires

ADS_UF_DONT_EXPIRE_PASSWD

userAccountControl

Store password using reversible encryption

ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED

userAccountControl

The password has expired

ADS_UF_PASSWORD_EXPIRED

userFlags

User cannot change password

ADS_UF_PASSWD_CANT_CHANGE

nTSecurityDescriptor

The pwdLastSet attribute is a large integer and does not appear in an easily readable format when IADs is used. Therefore, use the IADsUser interface (accessible from the LDAP provider) to display this value.

Displaying Password Attributes Accessible from userAccountControl

The LDAP provider can read the value of the userAccountControl attribute to determine:

  • Whether a password is required.

  • Whether the Password never expires option is enabled or disabled.

  • Whether the Store password using reversible encryption option is enabled or disabled.

Scripting Steps

Listing 7.4 contains a script that displays the state of password flags in the userAccountControl attribute and the pwdLastSet attribute of a user account. To carry out this task, the script performs the following steps:

  1. Create a Dictionary object to hold the value of the flags directly available from the userAccountControl attribute.

  2. Define the name and the value of each flag in the Dictionary object.

  3. Bind to the user account object by using the GetObject function and the LDAP provider.

  4. Create the intUAC variable, and initialize it to the integer value of the userAccountControl attribute.

  5. Create a loop, and use the bitwise AND operator to evaluate each flag value against the value of the userAccountControl attribute.

  6. Display each flag name and whether it is enabled or disabled.

Example 7.4. Displaying Password Attributes Available from the LDAP Provider and the userAccountControl Attribute

 1 Set objHash = CreateObject("Scripting.Dictionary")
 2 objHash.Add "ADS_UF_PASSWD_NOTREQD", &h00020
 3 objHash.Add "ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED", &h0080
 4 objHash.Add "ADS_UF_DONT_EXPIRE_PASSWD", &h10000
 5
 6 Set objUser = GetObject _
 7     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 8 intUAC = objUser.Get("userAccountControl")
 9
10 For Each Key In objHash.Keys
11     If objHash(Key) And intUAC Then
12         Wscript.Echo Key & " is enabled"
13     Else
14         Wscript.Echo Key & " is disabled"
15     End If
16 Next

Determining When a Password Was Last Set

To determine when a password will expire, it is important to know when the password was last set. Determining when a password was last set requires that you read the pwdLastSet attribute.

You can use the PasswordLastChanged property of the IADsUser interface to determine the last time that a user changed his or her user account password.

Scripting Steps

Listing 7.5 contains a script that checks the exact date and time when a user account password was last changed. To carry out this task, the script performs the following steps:

  1. Bind to the user account object by using the GetObject function and the LDAP provider.

  2. Create a variable, and initialize it to the value returned by the PasswordLastChanged property of the IADsUser interface.

  3. Display the date and time when the password was last set.

Example 7.5. Determining When a Password Was Last Set

1 Set objUser = GetObject _
2     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
3 dtmValue = objUser.PasswordLastChanged
4 Wscript.Echo "Password was last set: " & dtmValue

Configuring User Account Password Attributes

The topic “Reading User Account Password Attributes” demonstrated how to read the password attributes associated with a user account object. Reading these values is an excellent way to begin troubleshooting problems that might be related to a user account object’s password attributes. If the issue is password related, configuring password attributes is the next important step.

You can configure password attributes to increase network security in a number of ways — for example, by requiring users to change their passwords regularly or by enforcing the use of passwords. Configuring password attributes can also help maintain the proper operation of service accounts by keeping service account passwords from expiring.

How you configure password attributes of a user account from ADSI varies depending on the attribute:

  • Use the XOR bitwise operator to configure the flags in the userAccountControl attribute that correspond to the following settings:

    • Password required

    • Password never expires

    • Store password using reversible encryption

  • Set pwdLastSet to 0 or -1 to enable or disable the User must change password at next logon option.

Changing Flags in the userAccountControl Attribute

To enable any of the flags directly available from the userAccountControl attribute (see Table 7.6), use the XOR bitwise operator. Listing 7.6 contains a script that demonstrates how to evaluate and set a password flag in the userAccountControl attribute.

Scripting Steps

Listing 7.6 contains a script that disables the ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED flag using the XOR operator. To carry out this task, the script performs the following steps:

  1. Set a constant to the value of the ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED flag in the userAccountControl attribute.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Create a variable, and initialize it to the integer value of the userAccountControl attribute.

  4. Use the bitwise AND operator to determine whether the flag is enabled.

  5. If the flag is enabled, use the XOR bitwise operator to disable it in the userAccountControl attribute of the user account object.

  6. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.6. Disabling a Password-Related Flag in userAccountControl

 1 Const ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = &H80
 2
 3 Set objUser = GetObject _
 4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 5 intUAC = objUser.Get("userAccountControl")
 6
 7 If intUAC AND _
 8     ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED Then
 9     objUser.Put "userAccountControl", intUAC XOR _
10         ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
11     objUser.SetInfo
12 End If

Configuring a Password Change at Next Logon Requirement

For security, you might want users to change their passwords at next logon. You can accomplish this task by enabling the User must change password at next logon option. Selecting this option is important to ensure that users change their passwords to something that only they know.

The pwdLastSet attribute controls the value of the ADS_UF_PASSWORD_EXPIRED flag in the userAccountControl attribute. When set to 0, the pwdLastSet attribute enables the ADS_UF_PASSWORD_EXPIRED flag. When this flag is enabled, the current password is expired and the User must change password at next logon option is enabled.

Active Directory automatically enables this flag (expires the password) when a new user account is created but not when the SetPassword method is used to set a user’s password. Therefore, if you run an ADSI script that uses the SetPassword method, you should also enable the User must change password at next logon option from the script.

Scripting Steps

Enabling and disabling the User must change password at next logon option are done in opposite fashion.

Enabling the User must change password at next logon option

Listing 7.7 contains a script that enables the User must change password at next logon option. To carry out this task, the script performs the following steps:

  1. Bind to the user account object by using the GetObject function and the LDAP provider.

  2. Set the pwdLastSet attribute to 0 to enable the User must change password at next logon option.

  3. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.7. Enabling the User must change password at next logon Option

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

Disabling the User must change password at next logon option

To disable this option, simply change the 0 in line 3 of Listing 7.7 to -1, as shown in Listing 7.8.

Example 7.8. Disabling the User must change password at next logon Option

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

Managing User Accounts

After you create a user account, assign it a password, and configure password attributes for it, the next important step in preparing the account for user access is to enable it. By default, a user account is disabled if you create a user account and specify only the sAMAccountName mandatory attribute. A user cannot log on with the user account until you specifically enable it.

For security, you might want to disable an enabled user account if the user of the account will not be logging on to the network for an extended period of time or if, after a period of user account inactivity, you will be reassigning an existing user account to another user.

For both security and troubleshooting, it is useful to check user accounts for their enabled or disabled status. A user account that you specifically disabled should stay that way until it will be used again. Otherwise, a dormant user account that is enabled increases the vulnerability of your network to unauthorized access. Using a script, you can periodically check the status of user accounts that should be disabled. If a user is having trouble logging on to the network, you can use a script to determine whether the user account is enabled.

Using the ADS_UF_ACCOUNTDISABLE flag, you can display or configure the disabled status of a user account. This flag contains the decimal value 2 when an account is disabled and 0 when it is enabled. The ADS_UF_ACCOUNTDISABLE flag is stored in the userAccountControl attribute of each user account object.

For a list of attributes that are enabled automatically when a user account is created, see “Creating User Accounts” earlier in this chapter.

Enabling or Disabling a User Account

You can use scripts to either enable or disable a user account. This is done by toggling the value of the ADS_UF_ACCOUNTDISABLE flag in the userAccountControl attribute.

The scripts for enabling or disabling a user account are similar.

Scripting Steps

Listing 7.9 contains a script that sets the ADS_UF_ACCOUNTDISABLE flag to 0 to enable a user account. To carry out this task, the script performs the following steps:

  1. Set the ADS_UF_ACCOUNTDISABLE constant equal to the disabled flag in the userAccountControl attribute (used on line 8).

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Create a variable, and initialize it to the integer value of the userAccountControl attribute.

  4. Use the bitwise AND operator to determine whether the flag is enabled.

  5. If the flag is enabled, use the XOR bitwise operator to disable it in the userAccountControl attribute of the user account object, thereby enabling the user account.

  6. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.9. Enabling a User Account by Modifying the ADS_UF_ACCOUNTDISABLE Flag

 1 Const ADS_UF_ACCOUNTDISABLE = 2
 2
 3 Set objUser = GetObject _
 4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 5 intUAC = objUser.Get("userAccountControl")
 6
 7 If intUAC AND ADS_UF_ACCOUNTDISABLE Then
 8     objUser.Put "userAccountControl", intUAC XOR ADS_UF_ACCOUNTDISABLE
 9     objUser.SetInfo
10 End If

Listing 7.10 contains a script that disables a user account. To carry out this task, the script performs the following steps:

  1. Set the ADS_UF_ACCOUNTDISABLE constant equal to the disabled flag in the userAccountControl attribute (used on line 7).

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Create a variable, and initialize it to the integer value of the userAccountControl attribute.

  4. Use the bitwise OR operator to enable ADS_UF_ACCOUNTDISABLE in the userAccountControl attribute, thereby disabling the user account.

  5. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.10. Disabling a User Account by Modifying the ADS_UF_ACCOUNTDISABLE Flag

1 Const ADS_UF_ACCOUNTDISABLE = 2
2
3 Set objUser = GetObject _
4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
5 intUAC = objUser.Get("userAccountControl")
6
7 objUser.Put "userAccountControl", intUAC OR ADS_UF_ACCOUNTDISABLE
8 objUser.SetInfo

Determining Whether an Account Is Enabled or Disabled

If a user is having trouble logging on to the network, it might be because his or her user account has been disabled. Because of this, checking the status of the disabled flag (ADS_UF_ACCOUNTDISABLE) in the userAccountControl attribute is an important preliminary troubleshooting step. If the account is disabled, you can be reasonably sure that the user’s trouble logging on is associated with the disabled status of the user account. It is also a useful security precaution to periodically check that user accounts that should be disabled are in fact disabled.

Scripting Steps

Listing 7.11 contains a script that reads the userAccountControl attribute to determine whether a user account is enabled or disabled. To carry out this task, the script performs the following steps:

  1. Set the ADS_UF_ACCOUNTDISABLE constant equal to the disabled flag in the userAccountControl attribute.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Create a variable and initialize it to the integer value of the userAccountControl attribute.

  4. Use the bitwise AND operator to determine whether the flag is enabled.

  5. Display a message indicating whether the account is enabled or disabled.

Example 7.11. Checking the Value of the ADS_UF_ACCOUNTDISABLE Flag

 1 Const ADS_UF_ACCOUNTDISABLE = 2
 2
 3 Set objUser = GetObject _
 4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 5 intUAC = objUser.Get("userAccountControl")
 6
 7 If intUAC AND ADS_UF_ACCOUNTDISABLE Then
 8     Wscript.Echo "The account is disabled"
 9 Else
10     Wscript.Echo "The account is enabled"
11 End If

Reading and Writing User Account Attributes

The two primary interfaces for managing Active Directory user accounts are IADs and IADsUser. IADs is a core interface and can be used to manage many types of objects in Active Directory, not just user accounts. In contrast, IADsUser is a persistent interface that is specifically limited to managing user account objects. The attributes of a user account object available from IADsUser are represented as properties of the interface. For example, the pwdLastSet attribute of a user account is represented by the PasswordLastChanged property of IADsUser. IADs has a small set of methods that use the lDAPDisplayNames of attributes to manage user accounts and other types of object in Active Directory. Therefore, to retrieve the first name of a user account, IADs reads the lDAPDisplayName, givenName.

It might seem sensible to use IADsUser for all of your user account management tasks, because the property names are intuitive and easier to remember than the lDAPDisplayNames of the attributes. However, the IADsUser interface does not provide access to most of the attributes of a user account and is limited to managing user account objects. IADs, on the other hand, can read all attributes of all Active Directory object. Thus, your comfort with managing user account attributes from the IADs core interface will make it easier to understand how to manage many other types of Active Directory objects.

Managing the attributes of a user account object involves reading and writing to those attributes. The key to using the IADs core interface to manage attributes of user account objects is knowing how to find the following characteristics of each attribute:

  • lDAPDisplayName of an attribute. Use the Active Directory Schema snap-in to determine the lDAPDisplayNames of attributes to determine the names of the attributes associated with the User class. For information about installing the Active Directory Schema snap-in, see “ADSI Scripting Primer” in this book.

  • Data type of the attribute. Some data types are simple to read, such as attributes stored as strings. Other attributes might require some manipulation in VBScript in order to read them, such as octet string and large integer data types.

    If an attribute cannot be easily displayed by using the LDAP provider and VBScript, determine whether you can read the attribute with a persistent interface such as IADsUser or by using the WinNT provider.

  • Number of entries an attribute can hold. Attributes are either single-valued, containing a single entry; or multivalued, containing one or more entries. Use the Get and Put methods of IADs to manage single-valued attributes and use the GetEx and PutEx methods of IADs to manage multivalued attributes. To clear entries from both single-valued and multivalued attributes, use the PutEx method.

Note

Note

In rare instances, an attribute might be defined as multivalued but only be capable of holding a single value. In this case, you can use the Get and Put methods of IADs to manage them. In the remainder of this section, any multivalued attribute that holds a maximum of one entry is noted.

When modifying values using either Put or PutEx, you will need to specify the type of operation being performed (clear, update, append, or delete). These operations are listed in Table 7.7.

Table 7.7. Put and PutEx Operations

Constant

Value

Description

ADS_PROPERTY_CLEAR

1

Clears the value (or values) from the specified attribute.

ADS_PROPERTY_UPDATE

2

Replaces the value in the specified attribute with new values.

ADS_PROPERTY_APPEND

3

Appends a new value to the value (or values) in the specified attribute.

ADS_PROPERTY_DELETE

4

Deletes the value (or values) from the specified attribute.

Administering the General Properties Page

The General tab appears first by default when you view the Properties dialog box of a user account object. This tab contains attributes that are commonly used to identify particular users in the directory. The information on this page is available to all Active Directory users when they access the properties of a user account by browsing the directory from Windows Explorer.

The General properties page is shown in this chapter to demonstrate methods for reading and writing user account attributes. These same methods can be used for reading and writing the user account attributes found on the other properties pages.

The lDAPDisplayName of each attribute is commonly used to read and write entries to the General properties page. Therefore, it is important for you to be able to identify these attributes by name. The labels appearing on the property pages are often different from the lDAPDisplayNames of their corresponding attributes. On the General properties page, only two labels are the same as their lDAPDisplayNames: the Description label (with the lDAPDisplayName description) and the Initials label (with the lDAPDisplayName initials). Figure 7.1 shows the General properties page of the MyerKen user account and the lDAPDisplayNames as they appear in the Active Directory schema for each user interface label on this page.

User Account Attributes on the General Properties Page

Figure 7.1. User Account Attributes on the General Properties Page

Table 7.8 lists selected properties of the attributes appearing on the General properties page of a user account object.

Table 7.8. User Account Attributes on the General Properties Page and Selected Attribute Definitions

lDAPDisplayName

Single-valued or Multivalued

Indexed

Data Type

In Global Catalog

givenName

Single-valued

Yes

String

Yes

initials

Single-valued

No

String

No

sn

Single-valued

Yes

String

Yes

displayName

Single-valued

Yes

String

Yes

description

Multivalued

No

String

Yes

physicalDeliveryOfficeName

Single-valued

Yes

String

No

telephoneNumber

Single-valued

No

String

Yes

otherTelephone

Multivalued

No

String

No

mail

Single-valued

Yes

String

Yes

wWWHomePage

Single-valued

No

String

No

url

Multivalued

No

String

No

The cn/name attribute, MyerKen, that appears near the top of Figure 7.1 does not appear in Table 7.8 because this attribute is automatically created when the user account object is created, and it cannot be changed by simply modifying the cn/name attribute. Modifying this attribute is equivalent to renaming the user account object. For information about renaming a user account, see “Moving and Renaming User Accounts” later in this chapter.

Reading Attributes

As shown in Table 7.8, the General properties page contains both single-valued and multivalued attributes. Use the Get method of IADs to return all single-valued attributes, and use the GetEx method of IADs to return entries assigned to multivalued attributes.

For example, the following lines of code use the Get method to return the givenName and the GetEx method to return the otherTelephone value:

strGivenName = objUser.Get("givenName")
strOtherTelephone = objUser.GetEx("otherTelephone")

Using the correct method is very important. If you use the GetEx method to retrieve a single-valued attribute (such as givenName), you will generate a “Data type mismatch” error. If you use the Get method to retrieve a multivalued attribute (such as otherTelephones), no error will be generated. However, no data will be retrieved, either.

Scripting Steps

Listing 7.12 contains a script that reads entries assigned to single-valued and multivalued attributes that appear on the General properties page of a user account object. To carry out this task, the script performs the following steps:

  1. Use the On Error Resume Next statement to bypass the ADSI error generated when an attribute cannot be found in the local property cache.

    A discussion of this ADSI error follows the script.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Use the GetInfo method to initialize the local cache with attributes of the user account object.

    This step is not required, but it ensures that the most up-to-date attribute values of the ADSI object are retrieved.

  4. Use the Get method to retrieve values from single-valued attributes.

    What happens if no value exists for a specified attribute? For example, suppose no value has been configured for physicalDeliveryOfficeName. When the script calls the Get method on a nonexistent attribute, an error is generated. More information about this behavior follows the script.

  5. Use the GetEx method to retrieve entries assigned to multivalued attributes.

    The GetEx method returns an array.

    The same type of error generated for the Get method is also generated for the GetEx method for multivalued attributes that are not part of the user account object being evaluated.

  6. Display each single-valued attribute’s lDAPDisplayName and value.

  7. Using a For Each loop, display each multivalued attribute’s lDAPDisplayName and value.

Example 7.12. Reading Attributes on the General Properties Page

 1 On Error Resume Next
 2 Set objUser = GetObject _
 3     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 4 objUser.GetInfo
 5
 6 strGivenName = objUser.Get("givenName")
 7 strInitials = objUser.Get("initials")
 8 strSn = objUser.Get("sn")
 9 strDisplayName = objUser.Get("displayName")
10 strPhysicalDeliveryOfficeName = _
11     objUser.Get("physicalDeliveryOfficeName")
12 strTelephoneNumber = objUser.Get("telephoneNumber")
13 strMail = objUser.Get("mail")
14 strWwwHomePage = objUser.Get("wWWHomePage")
15
16 strDescription = objUser.GetEx("description")
17 strOtherTelephone = objUser.GetEx("otherTelephone")
18 strUrl = objUser.GetEx("url")
19
20 Wscript.Echo "givenName: " & strGivenName
21 Wscript.Echo "initials: " & strInitials
22 Wscript.Echo "sn: " & strSn
23 Wscript.Echo "displayName: " & strDisplayName
24 Wscript.Echo "physicalDeliveryOfficeName: " & _
25    strPhysicalDeliveryOfficeName
26 Wscript.Echo "telephoneNumber: " & strTelephoneNumber
27 Wscript.Echo "mail: " & strMail
28 Wscript.Echo "wWWHomePage: " & strWwwHomePage
29
30 For Each strValue in strDescription
31     Wscript.Echo "description: " & strValue
32 Next
33 For Each strValue in strOtherTelephone
34     Wscript.Echo "otherTelephone: " & strValue
35 Next
36 For Each strValue in strUrl
37     Wscript.Echo "url: " & strValue
38 Next

If any of the attributes do not contain values and the On Error Resume Next statement is not present, the script returns the following ADSI error message when the Get or GetEx method attempts to read values of the nonexistent attributes:

Active Directory: The directory property cannot be found in the cache.

If an attribute does not contain a value, the attribute does not exist according to LDAP specifications. This results in an ADSI error with an error code of &h8000500D.

You can avoid this error in the following ways:

  • Place the On Error Resume Next statement at the top of the script, as shown in line 1 of Listing 7.12.

  • Use the On Error Resume Next statement to test for the &h8000500D error code. If there is an error, display the attribute’s name and a message stating that there is no value. Otherwise, display the attribute’s name and value.

For information about error handling, see “VBScript Primer” and “Creating Enterprise Scripts” in this book.

Note

Note

You can use the properties of the IADsUser interface to return values and avoid errors generated when an attribute is not in the property cache. However, not all attributes appearing on the General properties page have corresponding properties in the IADsUser interface. Therefore, using the IADs interface and implementing error handling in your scripts is the preferred method of reading and writing attribute values.

Writing Values to the Attributes

Using scripts to configure user accounts is a good way to ensure a consistent attribute format and uniform attribute content among user accounts. For example, the script can dictate whether the displayName attribute should contain each user’s givenName, initial, and sn in that order (for example, Ken E. Myer) or, alternatively, the sn, a comma, and the givenName (for example, Myer, Ken). Even if you make a mistake when configuring user account attributes with scripts, the mistake is uniform and likely to be easier to fix than the variety of mistakes common to manual entry.

The methods for writing single-valued and multivalued attributes are different. Use the Put method of IADs to assign single-valued entries, and use the PutEx method with the ADS_PROPERTY_UPDATE control code to write multivalued entries.

Scripting Steps

Listing 7.13 contains a script that writes values to the attributes appearing on the General properties page. Any existing entries are replaced with new entries specified in the script. 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 14–19).

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

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Use the Put method of IADs to update all single-valued attributes in the local property cache. Any existing entries in the local property cache are replaced.

    The Put method uses the attribute’s lDAPDisplayName to identify the target attribute.

  4. Use the PutEx method of IADs to update all multivalued attributes.

    Because ADS_PROPERTY_UPDATE is specified when PutEx is called, any existing entries are replaced in the local property cache.

  5. Use SetInfo to commit the entries assigned to the user account object in the local property cache to Active Directory.

Example 7.13. Writing Values to Attributes

 1 Const ADS_PROPERTY_UPDATE = 2
 2 Set objUser = GetObject _
 3     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 4
 5 objUser.Put "givenName", "Ken"
 6 objUser.Put "initials", "E."
 7 objUser.Put "sn", "Myer"
 8 objUser.Put "displayName", "Myer, Ken"
 9 objUser.Put "physicalDeliveryOfficeName", "Room 4358"
10 objUser.Put "telephoneNumber", "(425) 707-9795"
11 objUser.Put "mail", "[email protected]"
12 objUser.Put "wWWHomePage", "http://www.fabrikam.com"
13
14 objUser.PutEx ADS_PROPERTY_UPDATE, _
15     "description", Array("Management staff")
16 objUser.PutEx ADS_PROPERTY_UPDATE, _
17     "otherTelephone", Array("(425) 707-9794", "(425) 707-9790")
18 objUser.PutEx ADS_PROPERTY_UPDATE, _
19     "url", Array("http://www.fabrikam.com/management")
20
21 objUser.SetInfo

You can use the Put method to write values to single-valued or multivalued attributes. As shown in Listing 7.13, there is no need to retrieve existing entries from attributes to assign new entries. This method replaces any existing entries. Therefore, if you need to log the values stored in existing attributes, retrieve the current entries using the Get method and store the values in a log file prior to using the Put method.

To confirm that an entry has been assigned, use the GetInfo (or GetInfoEx) method to retrieve the assigned entry or entries from Active Directory.

Modifying a Multivalued Attribute

The PutEx method can modify assigned entries in multivalued attributes by specifying either append or delete operations as the first argument of the method. The append control code (ADS_PROPERTY_APPEND) adds to the entries already stored in a multivalued attribute, and the delete control code (ADS_PROPERTY_DELETE) removes specified entries from the multivalued attribute.

Using PutEx to perform modifications to multivalued attributes is efficient because a single trip to the server is all that is required to perform modifications. ADSI does not check Active Directory first to modify the entries in multivalued attributes. When a modification request is received using the SetInfo method, the control code is sent with the modification request and Active Directory performs the identified action.

To confirm that an entry has been modified, use the GetInfo (or GetInfoEx) method to retrieve the modified entry or entries from Active Directory.

Note

Note

Even though the description attribute is multivalued, it behaves like a single-valued attribute when performing append or delete operations because it can contain a maximum of one entry. This is to allow for pre-Active Directory compatibility, and this attribute behaves as a single-valued attribute only for security principals such as the user account object.

Scripting Steps

There are multiple ways to modify a multivalued attribute:

  • Adding an entry to a multivalued attribute

  • Removing an entry assigned to a multivalued attribute

Adding an entry to a multivalued attribute

Listing 7.14 contains a script that demonstrates how to add an entry to a multivalued attribute on the General properties page. 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 the mode of modification (used on line 6).

    This control code appends an entry to a multivalued attribute.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Use the PutEx method of IADs to append the http://www.fabrikam.com/policy entry to the url attribute.

    If the http://www.fabrikam.com/policy entry is already assigned to the multivalued attribute, PutEx does not add a duplicate entry.

  4. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.14. Appending an Entry to a Multivalued Attribute

1 Const ADS_PROPERTY_APPEND = 3
2
3 Set objUser = GetObject _
4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
5
6 objUser.PutEx ADS_PROPERTY_APPEND, _
7     "url", Array("http://www.fabrikam.com/policy")
8
9 objUser.SetInfo

Removing an entry assigned to a multivalued attribute

Listing 7.15 contains a script that demonstrates how to use the delete operation of PutEx to remove an entry assigned to a multivalued attribute on the General properties page. To carry out this task, the script performs the following steps:

  1. Set the ADS_PROPERTY_DELETE constant to the control code parameter used by the PutEx method to indicate the mode of modification (used on lines 6–9).

    This control code deletes an entry from a multivalued attribute.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Use the PutEx method of IADs to delete the (425) 707-9790 entry in the otherTelephone attribute.

    The script in Listing 7.13 added this value to the otherTelephone multivalued attribute by using the PutEx and the ADS_PROPERTY_UPDATE control code. If the (425) 707-9790 entry was not previously assigned to the multivalued attribute, PutEx simply ignores the delete operation.

  4. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.15. Deleting an Entry in a Multivalued Attribute

 1 Const ADS_PROPERTY_DELETE = 4
 2
 3 Set objUser = GetObject _
 4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
 5
 6 objUser.PutEx ADS_PROPERTY_DELETE, _
 7     "otherTelephone", Array("(425) 707-9790")
 8 objUser.PutEx ADS_PROPERTY_DELETE, _
 9     "initials", Array("E.")
10
11 objUser.SetInfo

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

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

The number (425) 707-9791 is added as an entry to the otherTelephone attribute when SetInfo is called, but the number (425) 707-9790 is not deleted.

Another important nuance of using PutEx is that the order of the entries stored in multivalued attributes is not guaranteed. For example, suppose you enter three telephone numbers: (425) 707-9791, (425) 707-9792, and (425) 707-9793. If you write a script that returns these phone numbers, the data might be returned in this order instead: (425) 707-9792, (425) 707-9791, and (425) 707-9793.

Therefore, whenever you write scripts that operate on multivalued attributes, do not depend on a specific ordering of entries.

Clearing Attributes

Using the PutEx method with the clear control code (ADS_PROPERTY_CLEAR) applies to both single-valued and multivalued attributes. This control code deletes all existing entries. 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, while the following code will work to update the telephoneNumber attribute, the telephoneNumber attribute is not empty. Instead, it contains a single space:

objUser.Put "telephoneNumber", " "

To Active Directory, this means that the user actually has a phone number, regardless of whether it is valid phone number. If you search Active Directory for all users without a phone number (that is, all users for whom the telephoneNumber attribute is null), this user will not be included in the returned data.

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

Scripting Steps

Listing 7.16 contains a script that demonstrates how to remove an entry in a single-valued attribute and all entries in a multivalued attribute. 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 the mode of modification (used on lines 6 and 7).

    This control code removes entries from single-valued and multivalued attributes.

  2. Bind to the user account object by using the GetObject function and the LDAP provider.

  3. Use the PutEx method of IADs to remove the entry assigned to the initials single-valued attribute.

    If no entry is assigned to the attribute, PutEx simply ignores the remove operation.

  4. Use the PutEx method of IADs to remove any entries assigned to the otherTelephone attribute.

    If no entries are assigned to the attribute, PutEx simply ignores the remove operation.

  5. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.16. Removing Entries in Selected Single-valued and Multivalued Attributes

1 Const ADS_PROPERTY_CLEAR = 1
2
3 Set objUser = GetObject _
4     ("LDAP://cn=MyerKen,ou=Management,dc=NA,dc=fabrikam,dc=com")
5
6 objUser.PutEx ADS_PROPERTY_CLEAR, "initials", 0
7 objUser.PutEx ADS_PROPERTY_CLEAR, "otherTelephone", 0
8
9 objUser.SetInfo

Copying, Moving, and Renaming User Accounts

Copying and moving user accounts are relatively common administrative tasks. While it is not necessary to copy a user account in order to create a new one, it saves time because common attributes can be copied from an existing user account to a new user account.

Moving user accounts is an important task for managing changes within an organization. For example, if a user is promoted, it might be necessary to move the user’s account to another OU. A user account name might need to be changed if a user’s last name (sn attribute) changes or if a new employee takes over for an employee who has left the company.

Copying User Accounts

User account attributes are often similar from one account to the next. For example, organization policies might specify that all user accounts in the human resources department should be members of the HR global group, have HR Department listed in the department attribute, and have the same URL specified in the wWWHomePage attribute.

If you create user accounts that contain many configured attributes, and those attributes are similar from one user account to the next, you can copy selected attributes from an existing user account to a newly created one. To streamline this process further, consider creating a template user account that contains mandatory attributes and the optional attributes that are similar from one user account to the next. For example, create a user account named HRUser, configure optional attributes, and then use this template user account to create user accounts for employees of the human resources department. Keep the template user account disabled so that no one can log on with this user account.

The Active Directory Users and Computers console provides a copy user feature that you can access by right-clicking an existing user account. This feature copies a set of optional attributes by default. You cannot control which attributes are copied to the new user account. ADSI does not contain a method specifically designed to duplicate this capability by using the WinNT or LDAP providers. However, you can create a script that uses ADSI to copy selected attributes of existing user accounts after creating new user accounts.

To do this, the script reads selected attributes from the template account and then configures the new user account with those same values. For example, suppose the wWWHomePage attribute in the template account is configured as http://www.fabrikam.com. The script reads this value and then configures the wWWHomePage attribute for the new user account to also be http://www.fabrikam.com.

Scripting Steps

Listing 7.17 contains a script that creates a new user account and then copies selected attributes from a template user account to the new user account. To carry out this task, the script performs the following steps:

  1. Bind to the target container, the HR OU, by using the GetObject function and the LDAP provider.

  2. Create the new user account, and set the object’s mandatory attributes in the local property cache.

  3. Commit the new object to the Active Directory.

  4. Bind to the template user account object by using the GetObject function and the LDAP provider.

  5. Create an array that contains all of the optional attributes that will be applied to the new user account.

  6. Use the GetInfoEx method of IADs to copy selected attributes to the local property cache.

    It is not necessary to use GetInfoEx, because the Get method called later in the script will perform an implicit GetInfo call that copies all the attributes of the user account object into the local property cache. However, because an array is created in the script for writing selected attributes to the new user account, you can also use the array and the GetInfoEx method to selectively copy attributes to the local property cache.

  7. Create a loop to get each value of the attributes defined in the array and to write that value to the new user account in the local property cache.

  8. Commit the change to the user account object in the local property cache to Active Directory.

Example 7.17. Copying the Attributes from One User Account to Another

 1 Set objOU = GetObject("LDAP://ou=HR,dc=NA,dc=fabrikam,dc=com")
 2 Set objUser = objOU.Create("User", "cn=BarrAdam")
 3 objUser.Put "sAMAccountName", "barradam"
 4 objUser.SetInfo
 5
 6 Set objUserTemplate = _
 7     GetObject("LDAP://cn=HRUser,ou=HR,dc=fabrikam,dc=com")
 8 arrAttributes = _
 9     Array("description", "wWWHomePage", "department", "company")
10 objUserTemplate.GetInfoEx arrAttributes, 0
11
12 For Each strAttrib in arrAttributes
13     strValue = objUserTemplate.Get(strAttrib)
14     objUser.Put strAttrib, strValue
15 Next
16
17 objUser.SetInfo

Another way of completing the copy user account task is by using the schema attribute of IADs to determine which attributes of the user class are optional, then check the template user account to determine which of these attributes contain values. For those that do contain values, write those values to the new user account object. This approach does not require you to define the specific attributes you want assigned to the new user account.

Moving and Renaming User Accounts

When an employee changes roles within the organization, it might be necessary to move the employee’s user account to another OU within the same domain, or even from one domain to another. A user account might need to be renamed if, for example, user account naming conventions are changed, an employee changes his or her name, or a new employee replaces an existing employee.

How you move or rename a user account depends on where you plan to move it. Using ADSI, you can move or rename a user account using the MoveHere method of IADsContainer. The MoveHere method supports the following move and rename operations:

  • Moving a user account to a different container within the same domain

  • Renaming a user account within the same container

  • Renaming and moving a user account to a different container within the same domain

  • Moving a user account to another domain

  • Renaming an account while moving it to another domain

You cannot use the MoveHere method to move a user account to another forest.

Preparing a User Account for a Cross-Domain Move

Moving user accounts to other domains within the same forest is possible when the following 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 a user while logged on to the destination domain, the following message will appear:

    (null): Inappropriate authentication
    

    To move a user from one domain to another, you must have permission to remove a user from the source domain and add a user to the target domain. For example, a user with administrator credentials in a root domain can move a user to a child domain because the user is a member of the Enterprise Admins group. However, a user with administrator credentials in a child domain cannot move a user to a parent domain because the user does not have permission, by default, to add user accounts to the parent domain.

  • The user account to be moved must not be a member of a global group in the source domain, or the move operation will fail. Therefore, you must remove the user from any global groups before attempting the move.

    By default, a user account is made a member of the Domain Users global group. This group is also configured as the Primary group for compatibility with Macintosh clients and POSIX-compliant applications.

    You cannot remove a group configured as the Primary group. Therefore, make the user a member of a Universal group, configure that group as primary, and then remove the user’s membership in any global groups. If you do not remove the user account from all global groups, the move operation will fail and the following message will appear:

    (null): The server is unwilling to process the request.
    

    After the move is completed, the moved user account is automatically made a member of the Domain Users global group in the target domain. Membership in any Universal groups is automatically revoked.

The MoveHere method allows you to create a script to move or rename a user account. Table 7.9 shows the arguments of the MoveHere method.

Table 7.9. Arguments of the MoveHere Method

Argument

Type

Required

Default

Description

Source container

string

Yes

None

The name of the provider and the distinguishedName attribute of the target container.

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.

To use the MoveHere method, the script must first bind to the target (destination) container where the user account should be moved or renamed. The target container is not an argument of the MoveHere method.

Scripting Steps

The scripting steps for each type of move operation are similar.

Moving a user account to a different container within the same domain

Listing 7.18 contains a script that moves a user account from one OU to another OU within the same domain. To carry out this task, the script performs the following steps:

  1. Bind to the target OU by using the GetObject function and the LDAP provider.

  2. Use the MoveHere method of IADsContainer to move the user account from the HR OU to the Sales OU.

Example 7.18. Moving a User Account to a Different OU Within the Same Domain

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

Line 4 of Listing 7.18 shows that the BarrAdam user account is moved but not renamed. The second argument of the MoveHere method, the relative distinguished name, is identical to the relative distinguished name portion of the distinguishedName specified in the first argument (cn=BarrAdam). Therefore, the second argument can be written as vbNullString rather than the actual RDN:

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

Renaming a user account within the same container

Renaming a user account is similar to moving a user account. The difference is that the second argument of the MoveHere method is a name other than the relative distinguished name originally assigned to the user account. Also, you can rename a user account without moving it to another container.

Listing 7.19 contains a script that renames a user account from within the OU in which it currently resides. To carry out this task, the script performs the following steps:

  1. Bind to the target OU by using the GetObject function and the LDAP provider.

  2. Use the MoveHere method of IADsContainer to rename the user account to LewJudy in its current container.

Example 7.19. Renaming a User Account Within the Same OU

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

Renaming and moving a user account to a different container within the same domain

You can perform a rename operation while simultaneously moving the user account to another location. Simply change the target container specified on line 1 of Listing 7.19 to a different OU.

Listing 7.20 contains a script that renames a user account and moves it to a different OU. To carry out this task, the script performs the following steps:

  1. Bind to the target OU by using the GetObject function and the LDAP provider.

  2. Use the MoveHere method of IADsContainer to rename the user account and move it to the Management OU.

Example 7.20. Renaming and Moving a User Account to a Different OU

1 Set objOU = GetObject("LDAP://ou=Management,dc=NA,dc=fabrikam,dc=com")
2
3 objOU.MoveHere _
4     "LDAP://cn=LewJudy,ou=Sales,dc=NA,dc=fabrikam,dc=com", _
5         "cn=AckermanPilar"

Moving a user account to another domain

There are times when a user account might have to be moved to another domain within the forest — for example, if a user is moving to another location within the company and that location contains a different domain in the forest. It might also be necessary to move user accounts if the forest is being expanded into additional child domains or consolidated into a smaller number of domains.

Before moving a user account to another domain in the forest, you must make sure that all of the conditions outlined in the introduction to this section are met. Once these conditions are met, writing a script to move a user account to another domain is similar to moving a user account to another OU within the same domain.

Listing 7.21 contains a script that moves a user account to an OU in a child domain. To carry out this task, the script performs the following steps:

  1. Bind to the target OU by using the GetObject function and the LDAP provider.

  2. Use the MoveHere method of IADsContainer to rename the user account and move it to the Management OU.

Example 7.21. Moving a User Account to an OU in a Different Domain

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

If you need to move an OU or another container (and all of the objects within the container) to a different domain in the forest, use the Movetree.exe command-line tool. For information about this tool, install the Windows Support Tools from the SupportTools folder on the Windows 2000 Server installation CD. Following installation, from a command prompt, type movetree /? for syntax and Movetree examples.

Deleting User Accounts

Deleting a user account object in Active Directory is the final step in the life cycle of a user account. Deleting a user account clears all of its attributes and tombstones the account in Active Directory. The object remains marked with a tombstone until the cleanup process permanently removes it. Once deleted, there is no way to recover the user account.

To delete a user account by using an ADSI script, you must use the Delete method of the IADsContainer interface. Like IADs, IADsContainer is a core interface. IADsContainer is used to create, delete, and manage objects contained inside other objects. In this case, the user account object is contained in either an OU or one of the built-in containers, such as the Users container.

Table 7.10 shows the arguments of the Delete method.

Table 7.10. Arguments of the Delete Method

Argument

Type

Required

Default

Description

Class

string

Yes

None

Name of the schema class object to delete

Relative Distinguished Name

string

Yes

None

Value of the object’s name attribute

Scripting Steps

Listing 7.22 contains a script that deletes a user account from an OU. To carry out this task, the script performs the following steps:

  1. Bind to the OU from which the object will be deleted by using the GetObject function and the LDAP provider.

  2. Call the Delete method of the IADsContainer interface.

Example 7.22. Deleting an Active Directory User Account

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

The user is immediately deleted from Active Directory. You do not need to call SetInfo to commit the change.

Searching Active Directory for User Accounts

A critical part of scripting user account management is searching the Active Directory database. Some of the user account management tasks that involve searching Active Directory include:

  • Verifying that a user account does not already exist before attempting to create one.

  • Finding all user account objects that do not contain a value for a particular attribute.

  • Reading the value of an attribute from all user accounts before modifying the value.

  • Setting an attribute of all user account objects that meet a specific criterion to the same value.

The query interfaces available to VBScript are the ActiveX® Data Object (ADO) interfaces. These interfaces use OLE DB to read information from the Active Directory database. The information returned by the ADO interfaces is read-only; you cannot use ADO from VBScript to modify the result set returned by a query. However, after the result set is returned, you can use ADSI interfaces and methods to modify values and then write the values back to the Active Directory database. For an illustration of this technique, see “Modifying Multiple User Accounts by Using the Result Set from a Search,” later in this section.

A typical query using ADO almost always includes three objects:

  • Connection

  • Command

  • RecordSet

All of the tasks in this section use these objects to search the Active Directory.

The Connection object loads the OLE DB provider, ADsDSOObject, to establish a link to the Active Directory database through the ADSI search interface, IDirectorySearch.

After the link is established, the Command object initiates the query. The query passes through ADsDSOObject to IDirectorySearch, which then uses an LDAP query function to query the Active Directory database. The RecordSet object receives the data returned from the query.

The CommandText property of the Command object is used to specify the query to run against Active Directory. The CommandText property uses either LDAP or SQL dialect to define the query. With one exception, all examples in this section use the LDAP search dialect to assign a query string to the CommandText property. 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.

LDAP search dialect consists of four parts:

  • Search base

  • One or more search filters

  • One or more attributes

  • Search scope

Search base specifies where in Active Directory to begin the search. Search filters specify the criteria for the search. Attributes specify the data that should be returned, and search scope specifies where in the Active Directory database to start and finish the search.

Note

Note

For more information about constructing and running queries of the Active Directory database, see “ADSI Scripting Primer” in this book.

Search Base

The search base of a query string consists of three parts: angle brackets surrounding the expressions, a moniker, and a distinguishedName. The syntax for the search base is:

<moniker://distinguishedName>

The moniker is either LDAP or GC, and the distinguishedName specifies the starting point of the search. For example, if you want to query the contents of the Management OU in the na.fabrikam.com domain for attributes contained in the global catalog, the search base is:

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

Using the LDAP moniker instructs the query to perform a search using a full replica of the Active Directory database in a domain and, depending on the query, possibly all subdomains. In contrast, using the GC moniker instructs the query to search a global catalog server, which contains a partial replica of its domain and all of its child domains. Consequently, if you query a global catalog server in the root domain, the query contains data from all domains in the forest. Therefore, if all attributes that you want to query are contained in the global catalog, it is more efficient to query this data source than to search one or more full replicas of the Active Directory database in the forest.

Rather then hard code the search base into a query string, you can retrieve the rootDomainNamingContext from the RootDSE object. For information about retrieving this value using a script, see “ADSI Scripting Primer” in this book.

Filters

Search filters are an optional but important part of the LDAP search dialect used by the OLE DB provider to query the Active Directory database. A search filter consists of the name of a filter property, an operator, and a value. Each search filter is enclosed in parentheses, and search filters can be combined.

When combining search filters, preface the filter clause with an AND operator (&), an OR operator (|), or a NOT operator (!) and surround the entire filter clause in parentheses. There are a number of user account search filter examples in the remainder of this section. For more details about search filter syntax, see “ADSI Scripting Primer” in this book.

To limit a search to a specific type of object, specify either the objectClass or objectCategory search filter property. Table 7.11 shows the relationship between the user account types and the objectCategory and objectClass properties.

Table 7.11. User Account Types and the Values of objectCategory and objectClass

Account Type

objectCategory

objectClass

User Account

person

Top:person;organizationalPerson;user

contact

person

Top:person;organizationalPerson;contact

The objectCategory property lets you limit the query to all user account types that were derived from a class whose defaultObjectCategory attribute is person. The objectClass property lets you limit the query to objects matching any one of the values stored in the objectClass multivalued attribute.

Before determining which filter property to use or whether both filter properties are necessary, it is important to understand which objects are returned by each filter property and value combination. Consider the following example of an OU named R&D with two user account types: User1 (user account) and ContactUser1 (contact account). Table 7.12 shows search filters that use the objectCategory and objectClass properties to limit a search to user account types.

Table 7.12. Values Returned by Filter Properties

Filters

Returns

(objectCategory = person)

User1, ContactUser1

(objectClass = top),

(objectClass = person), or

(objectClass = organizationalPerson)

User1, ContactUser1

(objectClass = user)

User1

(objectClass = contact)

ContactUser1

If a computer object also existed in the R&D OU, the search filter examples in rows 2 and 3 would return the computer object. This is because the objectClass attribute of a computer object is Top:person;organizationalPerson;user;computer.

The objectCategory attribute is the better choice for most searches involving user objects, not only because it limits the search to user account types but because the objectCategory filter is single-valued, indexed, and stored in the global catalog. For more information about performing efficient searches, see “ADSI Scripting Primer” in this book.

The third row in Table 7.12 shows a filter example that limits the search to the user, the user account type that is a security principal. Another way to accomplish this is to specify a filter that uses both the objectCategory and objectClass properties, as shown in the following example:

(&(objectCategory=person)(objectClass=user))

Because the objectCategory attribute is indexed and in the global catalog, using this filter first should provide an efficient method of limiting the results before the objectClass filter is evaluated. However, to use the objectCategory attribute stored in the global catalog, you must use the GC moniker in the search base.

Other attributes of user account types are commonly specified in a search filter for limiting the result set returned from a user account type query. For example, you can search on a specific sAMAccountName value to verify that a particular name does not exist in the domain. A filter to return a sAMAccountName of MyerKen is as follows: (sAMAccountName = myerken). You must specify the lDAPDisplayName of an attribute to use it in a search filter.

Search filters also support wildcards such as the * (ANY operator). See “Using Wildcards in Search Filters” later in this section for code examples that contain wildcards, and see “ADSI Scripting Primer” in this book for more information about wildcards in search filters.

Attributes

The second to last part of a query that uses LDAP search dialect contains the attributes that the query should return.

Specify attributes to return by using their lDAPDisplayName. Separate each attribute by a comma, and separate the entire set of attributes from the rest of the query string by semicolons. The following code sample specifies the name, initials, sn, and mail attributes:

;name, initials, sn, mail;

Search Scope

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

Base. This option searches the dn specified in the search base. For example, a query for the name attribute with a search base of <GC://ou=Management,dc=NA,dc=fabrikam,dc=com> and no search filter returns the name value of the OU, Management. No child objects of the Management OU are searched.

Onelevel. This option searches the immediate children of the specified search base. For example, a query for the name attribute with a search base of <GC://ou=Management,dc=NA,dc=fabrikam,dc=com> and no search filter returns the name values of all objects in the Management OU. Any child containers in the Management OU are not searched, but the names of the child containers are included in the result set.

Subtree. This option searches the entire subtree, including the object specified in the search base. For example, a query for the name attribute with a search base of <GC://ou=Management,dc=NA,dc=fabrikam,dc=com> and no search filter returns the name values of the all objects in the Management OU and the name of the Management OU itself. All child containers in the Management OU are also searched for their name values. This is the default scope option; if you do not specify a scope option in the query, subtree is used.

Note

Note

The search scope can also be specified as a parameter of the Command object’s property collection, but when LDAP search dialect is used, it is more commonly assigned as part of the CommandText property.

Searching for an Attribute in a Container

Most searching tasks start with checking objects in a container for the value of an attribute. For example, you might want to identify all objects in a container by retrieving the value of their distinguishedName or name attributes.

Scripting Steps

You can use either LDAP search dialect or SQL syntax to search for attributes in a container.

Using LDAP search dialect to perform a search to display all names in an OU

Listing 7.23 contains a script that uses LDAP search dialect to display the value of an attribute assigned to all objects in an OU. To carry out this task, the script performs the following steps:

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

    Line 1 creates a connection object using ADO, and line 2 opens the connection using the ADSI OLE DB provider.

  2. Create an ADO Command object, and assign the ADO connection to it.

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

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Line 8 specifies the search base, the attribute to return, and the search scope.

    1. The search base, surrounded by angle brackets (< >), specifies the LDAP moniker to query — the Management OU in the na.fabrikam.com domain.

    2. The attribute to return, which appears after two semicolons, specifies the lDAPDisplayName of the attribute that the query should return — the name attribute.

    3. The search scope, appearing at the end of the query string, specifies where to perform the query — onelevel, which performs the search in the Management OU. The contents of any child OUs of the management OU are not searched.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

    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.

    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 and helps ensure that a script will not be using computing resources unnecessarily.

Example 7.23. Performing a Search to Display All Names in an OU

 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://ou=Management,dc=NA,dc=fabrikam,dc=com>;;name;onelevel"
 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

Listing 7.23 is intentionally simple to show the fundamental components of a search routine that uses ADO to search the Active Directory database.

Using SQL syntax to perform a search to display all names in an OU

To use SQL syntax to duplicate the results of the query statement in Listing 7.23, specify the search scope as a property of the Command object and use SQL syntax in place of line 8.

Listing 7.24 contains a script that uses SQL syntax to display the value of an attribute assigned to objects in an OU. To carry out this, task the script performs the following steps:

  1. Set the ADS_SCOPE_ONELEVEL constant.

    This constant is part of the ADS_SCOPEENUM enumeration, which specifies the search scope of a query.

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

  3. Create an ADO Command object, and assign the ADO connection to it.

  4. Set the ADO Command object’s searchscope property to ADS_SCOPE_ONELEVEL.

    This limits the search to a single container, excluding the parent object. The scope is specified in this way because it is not possible to specify the search scope in the SQL dialect.

  5. Assign the query string to the CommandText property of the ADO Command object. The string uses SQL dialect.

  6. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

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

  7. 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.

  8. Close the connection object.

Example 7.24. Using SQL Syntax to Perform a Search to Display All Names in an OU

 1 Const ADS_SCOPE_ONELEVEL = 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 objCommand.Properties("searchscope") = ADS_SCOPE_ONELEVEL
 9
10 objCommand.CommandText = _
11     "SELECT name FROM 'LDAP://ou=Management,dc=NA,dc=fabrikam,dc=com'"
12
13 Set objRecordSet = objCommand.Execute
14
15 While Not objRecordset.EOF
16     Wscript.Echo objRecordset.Fields("name")
17     objRecordset.MoveNext
18 Wend
19
20 objConnection.Close

The remaining tasks in this section show script examples that use LDAP search dialect. For more information about using SQL search dialect to perform 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.

Limiting a Search for an Attribute in a Container to User Account Types

The search in the preceding section is not limited to user account types in the Management OU. The name attributes of other objects, such as computers and OUs, are also found. To specify criteria to limit your search, you add one or more search filters to your query statement.

Scripting Steps

Listing 7.25 contains a script that uses LDAP search dialect to limit a result set to the value of an attribute assigned to a specific type of user account in an OU. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Lines 8–10 specify the search base, two search filters, the attribute to return, and the search scope.

    The first search filter limits the query to all objects that are assigned the defaultObjectCategory of person. The objectCategory property of the LDAP search dialect maps to the defaultObjectCategory of an object’s class.

    The second search filter limits the query to all user account types whose objectClass attribute is user. Anything returned by the query must satisfy both filter conditions because the search filters are prefaced with the ampersand (&), which is the AND operator.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

    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 7.25. Performing a Search to Display the Names of User Account Types That Are Security Principals in an OU

 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://ou=Management,dc=NA,dc=fabrikam,dc=com>;" & _
 9         "(&(objectCategory=person)(objectClass=user));" & _
10             "name;onelevel"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordset.EOF
15     Wscript.Echo objRecordset.Fields("name")
16     objRecordset.MoveNext
17 Wend
18
19 objConnection.Close

Searching for a User Account Attribute in a Container and Its Subcontainers

The script in Listing 7.25 is limited to returning the name attribute of objects in the Management OU. If there are child OUs of the Management OU, objects within the child OUs are not searched for the value of their name attribute. To find all objects meeting the criteria of the search in the Management OU and all child OUs, change the search scope from onelevel (line 10 in Listing 7.25) to subtree.

To expand the search to all objects in the domain, modify the search base to the domain and specify subtree as the search scope. The LDAP search dialect as it would appear in a script for this search is the following:

objCommand.CommandText = _
    "<LDAP://dc=NA,dc=fabrikam,dc=com>;" & _
        "(&(objectCategory=person)(objectClass=user));" & _
           "name;subtree"

Using the LDAP moniker in the LDAP search dialect of the preceding example limits the search to the contents of the na.fabrikam.com domain by default. Any child domains of na.fabrikam.com are not searched. This default behavior can be changed, but if the attributes that should be returned by the script are in the global catalog, a better option is to use the GC moniker to bind to the global catalog instead of the domain.

Unlike the domain, the global catalog contains a partial attribute list of all objects in its domain and all child domains. Therefore, if you bind to a global catalog server in the root domain, you can perform a forest-wide search from a single global catalog server.

Scripting Steps

Listing 7.26 contains a script that uses LDAP search dialect to limit a result set to the values of an attribute assigned to a specific type of user account in a forest. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Lines 8–10 specify the search base, two search filters, the attribute to return, and the search scope.

    The search base binds to the global catalog because the name attribute is contained in the global catalog, and the global catalog in the root domain contains a partial replica of all objects in the forest.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  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 7.26. Performing a Search to Display the Names of User Account Types That Are Security Principals in a 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>;" & _
 9       "(&(objectCategory=person)(objectClass=user));" & _
10           "name;subtree"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordset.EOF
15     Wscript.Echo objRecordset.Fields("name")
16     objRecordset.MoveNext
17 Wend
18
19 objConnection.Close

Verifying That an Attribute Is Unique in the Forest

The sAMAccountName attribute must be unique among all security principal objects within a forest. If you are using a script to create a user account in a domain, one way to verify that the sAMAccountName has not already been used is to search for the sAMAccountName attribute in the forest.

Scripting Steps

Listing 7.27 contains a script that uses LDAP search dialect to verify that a user account with a particular sAMAccountName does not already exist. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Lines 8–11 specify the search base, two search filters, the attribute to return, and the search scope.

    The search filter on line 10 limits the query to a sAMAccountName of myerken.

  4. The first part of line 11 requests two attributes, sAMAccountName and the distinguishedName. The distinguishedName is specified so that the output of the script displays the exact location of the user account object with the specified sAMAccountName.

  5. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

    If the RecordCount property of the RecordSet object is 0, display a message stating that the sAMAccountName is not in use.

    If the RecordCount property is not 0, 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.

    A sAMAccountName value can be used only once in a forest. However, it is possible that user account types can exist in the LostAndFound container in a domain. A user account in this container does not prevent you from creating a duplicate user account type with the sAMAccountName. However, if the sAMAccountName is in use in another container, the While Wend statement will display both the sAMAccountName in LostAndFound and the sAMAccountName in the other container.

  6. Close the Connection object.

Example 7.27. Performing a Search to Determine Whether a User Account Name Is in Use

 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>;" & _
 9         "(&(objectCategory=person)(objectClass=user)" & _
10             "(sAMAccountName=myerken));" & _
11                 "sAMAccountName, distinguishedName;subtree"
12
13 Set objRecordSet = objCommand.Execute
14
15 If objRecordSet.RecordCount = 0 Then
16     Wscript.Echo "The sAMAccountName is not in use."
17 Else
18     While Not objRecordset.EOF
19         Wscript.Echo "sAMAccountName = " & _
20         objRecordset.Fields("sAMAccountName")
21         Wscript.Echo "distinguishedName = " & _
22         objRecordset.Fields("distinguishedName")
23         objRecordset.MoveNext
24     Wend
25 End If
26
27 objConnection.Close

Searching for Empty Attribute Values

Determining whether an attribute does not have a value is a common searching task. For example, company policy might dictate that each user account must contain an office telephone number or an e-mail address. Thus, searching for all user accounts that are missing these values will help you determine which accounts are not in compliance with company policy.

Scripting Steps

Two scripting approaches for finding attributes that do not contain a value are as follows:

  • Using the not present operator of a search filter (!attribute_name=*) to test for the absence of an attribute

  • Using the VBScript IsNull function to test for the absence of an attribute

Using the Not Present operator of a search filter

Listing 7.28 contains a script that uses a filter to find all user accounts in the forest that do not contain a value for an attribute. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Line 8 specifies the search base using the GC moniker to query the global catalog server in the Active Directory root domain, fabrikam.com, because the mail attribute in the search filter and the distinguishedName attribute are replicated to the global catalog.

    Line 9 specifies the search filter for the query. The filter, which uses the objectCategory property, limits the query to user account types, including contact accounts. The mail filter limits the query to all user account types whose mail attribute does not contain a value.

    Line 10 specifies the attribute of the objects to return, the distinguishedName attribute, and the scope of the search.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  5. Use an If Then Else statement to determine whether the recordset is empty by checking the EOF (end of file) property of the RecordSet object. If EOF is true, display a message stating that all user accounts contain a value for the mail attribute; otherwise, display each record that does not contain a value for the mail attribute.

  6. To display the records, use a While Wend statement to loop through all of the records in the RecordSet object. For each record, display the distinguishedName values stored in the Fields collection of the RecordSet object.

  7. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  8. Close the Connection object.

Example 7.28. Using the Not Present Operator to Display All User Accounts with an Empty Attribute

 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>;" & _
 9          "(&(objectCategory=person)(!mail=*));" & _
10              "distinguishedName;subtree"
11
12 Set objRecordSet = objCommand.Execute
13
14 If objRecordset.EOF Then
15     Wscript.Echo _
16        "All user accounts contain a value for the mail attribute."
17 Else
18     Wscript.Echo "User account(s) without a mail value:"
19     While Not objRecordset.EOF
20         Wscript.Echo objRecordset.Fields("distinguishedName")
21         objRecordset.MoveNext
22     Wend
23 End If
24
25 objConnection.Close

Using the IsNull function to find attributes without values

You can also determine whether an attribute is empty by using the IsNull VBScript function to find the user account types that do not contain values in the mail attribute.

Listing 7.29 contains a script that uses the VBScript IsNull function to determine which user accounts in the forest do not contain a value for an attribute. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Line 9 specifies the search filter for the query. The filter using the objectCategory property limits the query to user account types, including contact accounts. However, a search filter to limit the search to user account types without a mail attribute is not specified.

    Line 10 specifies the attributes of the objects to return, the distinguishedName and mail attributes, and the scope of the search. The mail attribute is returned by the search so that the script can later test whether any value is contained in the attribute.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  5. Initialize the variable blnNoEmptyMailAttribute to True. This variable will remain true unless a single record is found that does not contain the mail attribute.

  6. Use a While Wend statement to loop through all of the records in the RecordSet object. For each record, test whether the mail attribute is empty by using the VBScript IsNull function.

  7. If a mail attribute is empty, set the blnNoEmptyMailAttribute to False and then display the distinguishedName value of the user account.

  8. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  9. When all records are tested, use an If Then statement to test whether blnNoEmptyMailAttribute is true. If it is true, display a message stating that all user accounts contain a value for the mail attribute.

  10. Close the Connection object.

Example 7.29. Using IsNull to Display All User Accounts with an Empty Attribute

 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>;" & _
 9        "(objectCategory=person);" & _
10            "distinguishedName,mail;subtree"
11
12 Set objRecordSet = objCommand.Execute
13 blnNoEmptyMailAttribute = True
14 While Not objRecordset.EOF
15     If IsNull(objRecordset.Fields("mail")) Then
16         blnNoEmptyMailAttribute = False
17         Wscript.Echo objRecordset.Fields("distinguishedName")
18     End If
19     objRecordset.MoveNext
20     If blnNoEmptyMailAttribute Then
21         Wscript.Echo _
22             "All user accounts contain a value for the mail attribute."
23     End If
24 Wend
25
26 objConnection.Close

This approach does not require that the script use an ! (NOT operator) in the search filter. The NOT operator requires additional processing by the domain controller servicing the search request. However, because the filter does not limit the user accounts to those that do not contain a mail attribute, all user account types are returned by the query. The client must then test all of the records to determine whether the mail attribute is empty. This approach increases network traffic because the server returns all user account records, and it increases the processing requirements placed on the client computer running the script because the client computer must read each record to determine whether IsNull is true.

The scripts in Listing 7.28 and Listing 7.29 demonstrate that performance optimization is an important consideration when writing scripts to search Active Directory. For more information about optimizing scripts that search Active Directory, see “ADSI Scripting Primer” in this book.

Using Wildcards in Search Filters

It is often necessary to locate all attributes that contain similar but not identical values so that these values can be modified. For example, you might need to locate all user accounts that start with the same network path in their user profile path, or user accounts with the same area code specified for their telephoneNumber attribute.

Scripting Steps

Listing 7.30 contains a script that finds all user accounts in the forest that contain a similar value for an attribute. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Line 8 specifies the search base by using the GC moniker to query the global catalog server in the Active Directory root domain, fabrikam.com, because the telephoneNumber and distinguishedName attributes are replicated to the global catalog.

    Line 9 specifies the search filters for the query. The objectCategory filter limits the query to all user account types. The telephoneNumber filter uses the any operator to limit the query to telephoneNumber attribute values starting with 707.

    Line 10 specifies the attributes of the objects to return, the distinguishedName and telephoneNumber attributes, and the scope of the search.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  5. Use an If Then Else statement to determine whether the recordset is empty by checking the EOF property of the RecordSet object. If EOF is true, display a message stating that no user accounts were found with the specified area code. Otherwise, display each record that starts with 707 for the telephoneNumber attribute.

  6. To display the records, use a While Wend statement to loop through all of the records in the RecordSet object. For each record, display the distinguishedName and telephoneNumber values stored in the Fields collection of the RecordSet object.

  7. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  8. Close the Connection object.

Example 7.30. Searching for User Accounts That Contain a Similar Value in an Attribute

 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>;" & _
 9         "(&(objectCategory=person)(telephoneNumber=707*));" & _
10             "distinguishedName,telephoneNumber;subtree"
11
12 Set objRecordSet = objCommand.Execute
13
14 If objRecordset.EOF Then
15     Wscript.Echo _
16         "No user accounts found with this area code."
17 Else
18     Wscript.Echo "User account(s) with the specified area code:"
19     While Not objRecordset.EOF
20     Wscript.Echo objRecordset.Fields("distinguishedName") & ": " & _
21         objRecordset.Fields("telephoneNumber")
22     objRecordset.MoveNext
23     Wend
24 End If
25
26 objConnection.Close

The example shown in Listing 7.30 uses a search filter that performs post-wildcard matching on an indexed attribute. In this example, post-wildcard matching means that all telephoneNumber attribute values that start with 707 are returned. Pre-wildcard matching, such as (telephoneNumber=*707-9794), and mid-wildcard matching, such as (telephoneNumber=425*9794), should not be performed against a potentially large result set. This is because the server performs significantly more processing to return such a result set than to return a result set from a post-wildcard match. If you must perform a pre- or mid-wildcard match, consider limiting the search to a smaller search base and search scope.

Searching for Multivalued Attributes

At times it might be necessary to search for multivalued attributes. For example, you might want a list of user accounts with URLs listed in the url multivalued attribute, or you might want to retrieve a list of direct reports listed in the directReports multivalued attribute for user accounts in a particular OU.

Multivalued attributes are not indexed and not located in the global catalog, so searches for this type of attribute should be limited in scope.

Scripting Steps

Listing 7.31 contains a script that finds all user accounts in an OU that contain values in a multivalued attribute. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

    Line 8 specifies the search base using the LDAP moniker to query a domain controller in the na.fabrikam.com domain because the otherTelephone attribute is not replicated to the global catalog. The search base has been intentionally limited to the Management OU because not only is the otherTelephone attribute not in the global catalog, but it is not an indexed attribute, either. Limiting the result set by specifying the Management OU as the search base avoids overburdening the domain controller with an intensive search request.

    Line 9 specifies the objectCategory=person and otherTelephone=* search filters to limit the result set to all user account types that contain values for the otherTelephone attribute. This does not exclude the contact account type, which can contain a value for the otherTelephone attribute. User account types without a value for the otherTelephone attribute are not returned by the query because the * (ANY operator) means that the attribute must be present — that is, it must contain a value.

    Line 10 specifies the attributes of the objects to return, the cn and otherTelephone attributes, and the scope of the search. The search scope has been limited to onelevel so that only user accounts in the Management OU are searched and child OUs are not.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  5. Use a While Wend statement to loop through all of the records in the RecordSet object.

  6. Display the cn value, a single-valued attribute stored in the Fields collection of the RecordSet object.

  7. Use a For Each loop to read each value stored in the otherTelephone field of the RecordSet object and then display the values.

    The end of line 16 shows that the Value property of the Fields collection is specified. The Value property is the default property and does not need to be specified when single-valued attributes are displayed. However, it is required for this script to display the values contained in the otherTelephone multivalued attribute.

  8. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  9. Close the Connection object.

Example 7.31. Searching for User Accounts Containing a Particular Multivalued Attribute

 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://ou=Management,dc=NA,dc=fabrikam,dc=com>;" & _
 9         "(&(objectCategory=person)(otherTelephone=*));" & _
10             "cn,otherTelephone;onelevel"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordset.EOF
15     Wscript.Echo objRecordset.Fields("cn") & VbCr
16     For Each varRecord in objRecordset.Fields("otherTelephone").Value
17         Wscript.stdOut.Write varRecord & " "
18     Next
19     Wscript.Echo VbCrLf
20     objRecordset.MoveNext
21 Wend
22
23 objConnection.Close

Sorting the Result Set

Sorting a large result set can be useful, particularly when you need to group similar values together in an easy-to-read list. Ideally, you should perform searches on indexed attributes because the server performs the sort operation while it is building the result set. Otherwise, the server must generate the entire result set before performing a sort operation. To instruct the server to perform a sort operation, set the Sort On property of the Command object to the attribute you want to sort. If multiple attributes are defined for the sort operation, separate each one with a comma.

Even though the physicalDeliveryOfficeName attribute that appears in Listing 7.32 is not in the global catalog, it is an indexed attribute. Therefore, the server can efficiently sort the result set.

An interesting caveat to searching the directory with referral chasing and sorting enabled is that each domain controller performs the search operation independently. Therefore, the result set is returned in separately sorted blocks. That is, the result set for the root domain will be sorted, and then the result set for each child domain will be sorted. Conversely, a search of the global catalog with sorting enabled returns a single sorted list to the client computer. To obtain a single sorted result set in this way, the attribute must be replicated to the global catalog.

Scripting Steps

Listing 7.32 contains a script that lists all user account types in a domain and the value of an attribute that is not contained in the global catalog. Sorting is enabled on an indexed attribute. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Sort the result set by physicalDeliveryOfficeName by setting the Sort On property of the Command object equal to the attribute’s lDAPDisplayName.

  4. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect.

  5. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  6. Use a While Wend statement to loop through all of the records in the RecordSet object and display the value of the distinguishedName and physicalDeliveryOfficeName attributes.

  7. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  8. Close the Connection object.

Example 7.32. Sorting a Result Set from a Search of the Active Directory

 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") = "physicalDeliveryOfficeName"
 8
 9 objCommand.CommandText = _
10     "<LDAP://dc=NA,dc=fabrikam,dc=com>;" & _
11         "(objectCategory=person);" & _
12             "distinguishedName,physicalDeliveryOfficeName;subtree"
13
14 Set objRecordSet = objCommand.Execute
15
16 While Not objRecordset.EOF
17     Wscript.Echo objRecordset.Fields("distinguishedName") & ": " & _
18         objRecordset.Fields("physicalDeliveryOfficeName")
19     objRecordset.MoveNext
20 Wend
21
22 objConnection.Close

Modifying Multiple User Accounts by Using the Result Set from a Search

The need to modify attributes for a specific set of accounts is frequently the reason for performing a search in the first place. For example, you might want to modify an attribute in user accounts so that the value is identical in the accounts retrieved by the search request.

The result set returned by the ADSI OLE DB provider is read-only. Therefore, to modify the data returned in the result set, you must use a different provider and a different interface, such as the LDAP provider and the IADs core interface.

To modify the contents of a result set, configure the search to request the ADsPath attribute. The value of the ADsPath attribute is the binding string passed to the GetObject function. The GetObject function loads the user object in the local property cache. Once the user object is in the property cache, you can write to it using an interface such as IADs.

Scripting Steps

Listing 7.33 contains a script that retrieves a result set and then modifies an attribute in user account objects. To carry out this task, the script performs the following steps:

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

  2. Create an ADO Command object, and assign the ADO connection to it.

  3. Assign the query string to the CommandText property of the ADO Command object. The string uses LDAP search dialect. The search request returns the value of the ADsPath attribute.

  4. Run the query by assigning the Execute method to the Command object and storing the return value in the RecordSet object, objRecordSet.

  5. Use a While Wend statement to loop through all of the records in the RecordSet object.

  6. In the loop, initialize the strADsPath variable to hold the ADsPath value. This value is the LDAP binding string.

  7. Bind to the user account object, specified in the strADsPath variable, by using the GetObject function and the LDAP provider.

  8. Use the Put method of IADs to update the company attribute, contained in the local property cache, to Fabrikam.

  9. Use SetInfo to commit the company value assigned to the user account object in the local property cache to Active Directory.

  10. Move to the next record in the recordset by using the MoveNext method of the RecordSet object. When all records are processed, end the loop.

  11. Use the RecordCount property of the RecordSet object to display the number of user accounts that were modified.

  12. Close the Connection object.

Example 7.33. Modifying 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             "ADsPath;subtree"
11
12 Set objRecordSet = objCommand.Execute
13
14 While Not objRecordset.EOF
15     strADsPath = objRecordset.Fields("ADsPath")
16     Set objUser = GetObject(strADsPath)
17     objUser.Put "company", "Fabrikam"
18     objUser.SetInfo
19     objRecordset.MoveNext
20 Wend
21
22 Wscript.Echo objRecordSet.RecordCount & " user accounts modified."
23
24 objConnection.Close

Managing User Accounts by Enumeration

Another way to manage multiple user accounts is to enumerate a container’s contents by using the IADsContainer interface. Enumeration is the process of returning a list of objects in a container.

IADsContainer is a multipurpose interface that is used to complete a number of user account management tasks, such as:

  • Using the Create method of IADsContainer to create a user account (demonstrated in “Creating User Accounts” earlier in this chapter)

  • Using the MoveHere method to move and rename a user account (demonstrated in “Moving and Renaming User Accounts” earlier in this chapter).

  • Using the Delete method of IADsContainer to remove a user account (demonstrated in “Deleting User Accounts” earlier in this chapter.

Combining these methods and the methods of the IADs interface with the ability of the IADsContainer interface to enumerate a container allows you to manage multiple user accounts.

Limiting Enumeration with Filters and Hints

The Filter and Hints properties of IADsContainer allow limiting the number of objects returned by enumeration. The filter property is similar to the search filters discussed earlier in “Searching Active Directory for User Accounts.” Unlike search filters, the Filter property of IADsContainer requires the client to filter objects rather than the server. The Hints property is equivalent to the attributes requested in a search query. For example, you can use the Hints property to instruct IADsContainer to return only the cn or the ADsPath of an object.

IADsContainer also provides methods, including Create, MoveHere, and Delete (discussed earlier in this chapter), which can be used to perform account management tasks on enumerated objects.

Because IADsContainer enumeration loads all objects from a container into the local property cache, using enumeration for a large number of objects can affect client performance. Therefore, to retrieve the contents of a container with many objects, use the ADO OLEDB provider to perform a search.

Scripting Steps

Listing 7.34 contains a script that enumerates a container and then modifies an attribute in user account objects. To carry out this task, the script performs the following steps:

  1. Bind to the target container, the Management OU, by using the GetObject function and the LDAP provider.

  2. Set the Filter property of the IADsContainer interface to return user account objects.

    Contact account types are not returned by this filter.

  3. Use a For Each loop to assign each object stored in the objOU container object to the objUser variable.

  4. Display the value of the IADs name property for each user account in the enumeration.

  5. Use the Put method of IADs to update the company single-valued attribute in the local property cache to Fabrikam.

  6. Use SetInfo to commit the company value assigned to the user account object in the local property cache to Active Directory.

  7. Use the Get method of IADs to read the value of the company attribute for each user object.

  8. Repeat the loop for each object in the enumeration.

Example 7.34. Modifying Multiple User Accounts in a Container by Using Enumeration

 1 Set objOU = GetObject("LDAP://ou=Management,dc=NA,dc=fabrikam,dc=com")
 2
 3 objOU.Filter = Array("user")
 4
 5 For Each objUser In objOU
 6     Wscript.Echo "Modified " & objUser.Name
 7     objUser.Put "company", "Fabrikam"
 8     objUser.SetInfo
 9     Wscript.Echo "The new company name is: " & _
10         objUser.Get("company")
11 Next
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset