Editor's note: This article is the sixth part of a 12-part series about Active Directory Service Interfaces (ADSI) The series started in the January 1999 issue. Refer to previous installments for definitions and background information.
In the previous five articles, I discussed the basics of ADSI and how ADSI works. In the remaining articles, I'll show you how to use ADSI to help you with daily tasks, such as manipulating user accounts, services, shares, and sessions in Windows NT's SAM and Windows 2000's (Win2K's) Active Directory (AD). This month, I show you how to automate two fundamental administrative tasks: creating and manipulating user accounts.
Although tools to create user accounts already exist (e.g., the Microsoft Windows NT Server 4.0 Resource Kit's Addusers utility), ADSI's versatility lets you quickly write a script that creates one or many user accounts and manipulate existing accounts. For example, you can write a script that creates one standard or full-featured user account or a script that creates 1000 full-featured user accounts. You can even create a command-line utility that unlocks locked-out user accounts.
Creating a Standard User Account
You can quickly create a standard user account (i.e., a user account with minimal attributes) with the ADSI code in Listing 1. Listing 1 is a user-specific script. In other words, the script includes user data (e.g., usernames, ADsPaths) in the code.
Listing 1 contains three sections. The first section uses the WinNT: provider to create a user account in a NT domain. The second section creates a user account in a local SAM on an NT or Win2K workstation or member server. The third section uses the LDAP: provider to create a user account in a Win2K domain.
The second section demonstrates that you can use the WinNT: provider to create users in a Win2K environment. However, you can't use the WinNT: provider to set all the AD attributes that an account might require. Thus, in the listings here, I use the LDAP: provider for Win2K's AD.
In "An ADSI Primer, Part 2: Manipulating Active Directory Objects" (February 1999), I showed you how to create ADSI Group objects. Creating a user account with ADSI is almost identical to creating a group. You first need to bind to the parent container, and then you use the IADsContainer::Create method to create the user. You then set the properties and write the property cache out with IADs::SetInfo.
When you create standard users in a Win2K domain, you need to be aware of two important User object attributes: sAMAccountName and userPrincipalName. The User object has several mandatory attributes. The system sets these mandatory attributes, except for one, sAMAccountName, which specifies the name that represents the user to NT clients and servers that are lower in the Win2K hierarchy. You must set the sAMAccountName attribute before you call IADs::SetInfo. The userPrincipalName attribute isn't mandatory, but I recommend that you set this property value. If you include the userPrincipalName attribute, Win2K users can log on with their fully qualified Request for Comments (RFC) 822 email address. (For more information about RFC 822, go to ftp://ftp.isi.edu/ innotes/rfc822.txt.)
Creating a Fully Featured User Account
Creating standard users is acceptable if you need to get the task done quickly. However, you probably want to create fully featured users (i.e., users for whom you set all applicable attributes). The approaches you use to create fully featured users in the NT and Win2K environments differ slightly because Win2K offers many more properties than NT offers, such as the office and home addresses of users, lists of email addresses, and lists of pager, fax, and phone numbers. Listing 2 shows how to create a fully featured user in NT, and Listing 3, shows how to create a fully featured user in Win2K. Both listings contain user-specific scripts.
You can manipulate User objects with a special interface called IADsUser. This interface's methods and property methods let you directly set many of the User object's property values. Tables 1, 2, and 3 on the Win32 Scripting Journal Web site contain the methods, read-write property methods, and read-only property methods, respectively, for the IADsUser interface. I split the data into three tables so that you can easily see all the ways you can read and manipulate User objects and their properties.
NT. Listing 2 uses several IADsUser property methods and several constant values to create a fully featured user in NT. The script sets several read-write properties for the User object, including HomeDirectory.
In NT, you can establish a home directory for a user in two ways. You can specify an explicit local path that the system automatically maps during logon, or you can specify a Uniform Naming Convention (UNC) path to a remote share that you map to a drive letter. If you want to map to a local path (e.g., C:\), you don't set the HomeDirDrive property and use an explicit path for IADsUser::HomeDirectory. If you want to map a drive letter to a remote share, you set the HomeDirDrive property to the drive letter you want to map to and use a UNC path for IADsUser::HomeDirectory.
The User object has several read-write attributes that Listing 2 doesn't include but that you might want to set for NT users. Those attributes are PasswordExpired, PrimaryGroupID, ObjectSID, and Parameters.
The PasswordExpired attribute specifies whether users must change their password the next time they log on. A value of 1 means that users must change their password; a value of 0 means that users don't have to change their password. The PrimaryGroupID property holds the primary group ID as an integer, and the ObjectSID attribute holds security ID (SID) data. The Parameters property holds a host of extra user data relating to other Microsoft products, such as NT Server 4.0, Terminal Server Edition and File-and-Print-for-NetWare data.
The Parameters property illustrates how Win2K's AD is much better than NT's SAM. Previously, the only way to add extra property data into the static SAM was through the Parameters property method. With AD, you can extend the User object to hold whatever properties you need. Thus, if you need a Languages-Spoken attribute on the User object, you create it and then use IADs::Get and IADs::Put to get and set the attribute.
You can't use VBScript to access all the read-write attributes. For example, ADSI returns the ObjectSID attribute as a byte array. Unfortunately, VBScript understands only variant arrays, which renders the returned data useless. As a result, you must use Perl, Visual Basic (VB), or C++ if you want to use ObjectSID.
Most of the code in Listing 2 is self-explanatory, except for the two areas at callouts A and B. At callout A in Listing 2, you use two hexadecimal constants to explicitly force the new user account to have a password that never expires and that the user can't change. The code to set these password requirements might seem complicated, but the code involves only simple arithmetic. The sidebar "Boolean Arithmetic with Hexadecimal Values," page 10, explains this arithmetic.
If you prefer to not use hex constants, you might be able to use a User object property method. For example, you can use the IADsUser::AccountDisabled property method instead of the UF_ACCOUNTDISABLE constant to disable an account. Similarly, you can use the IADsUser::IsAccountLocked property method instead of the UF_LOCKOUT constant to lock an account. These IADs property methods hide the arithmetic within a simple Boolean value.
At callout B in Listing 2, you create the home directory and set permissions to it. You create the directory by obtaining a reference to a FileSystemObject object and calling the CreateFolder method if the directory doesn't already exist. You set the permissions by running NT's Cacls command from within Windows Scripting Host (WSH) via the WshShell::Run method. You need to include three parameters. The first parameter is the command you want to execute. The second parameter can be any of the following constant values based on your needs: Const vbHide = 0, Const vbNormalFocus = 1, Const vbMinimizedFocus = 2, Const vbMaximizedFocus = 3, Const vbNormalNoFocus = 4, and Const vbMinimizedNoFocus = 6. The last parameter is TRUE if you want the script to wait until the Cacls command finishes before continuing to the next line, which sets the password.
Win2K. Listing 3 shows how to create the same user in Win2K's AD. Listing 3 is similar to Listing 2, with one major difference: The property name userFlags changes to userAccountControl for the extended settings at callout A in Listing 3. Other minor differences exist, such as the use of more constants and property methods. Win2K lets you set many property values for users, including multivalue properties that you set via an array. For example, you can list several telephone numbers for the TelephoneNumber, TelephoneMobile, and TelephoneHome properties. Through the use of constants, you can even set up AD to let users log on with smart cards.
Creating Many User Accounts
User-specific scripts work well if you have to create only a few user accounts. If you need to create many user accounts at one time or if you create new accounts often, using a universal script with an input file is more efficient. The input file includes the user data so that you can use the script, without modifying it, to create any user account. For example, Figure 1, page 9, shows the users-to-create.txt input file that provides the user data for the universal script in Listing 4, page 12. Although this input file includes only four data sets, you can include as many data sets as you want. You just need to include a data set for each user account that you want to create.
As Figure 1 shows, each data set goes on a separate line. A data set can contain as many values as you want. The data sets in the users-to-create.txt file have five values: username, expiration date, description, full name, and password. You use colons to separate the values.
The universal script reads in the user data to create the user accounts. As Listing 4 shows, you use FileSystemObject (FSO) and TextStream (TS) objects to manipulate the user data. For information about FSO and TS objects, go to the Microsoft Developer Network (MSDN) Web site at http://msdn.microsoft.com/ library/devprods/vs6/vb/html/ vbconfolderfileprocessing.htm. After you create a reference to an FSO object and assign that reference to the fso variable, you apply the FileSystemObject::OpenTextFile method to open the users-to-create.txt file, setting the user data to the tsInputFile TS variable. You then use a While loop with the TextStream::AtEndOfStream method to loop through each line in tsInputFile until the end of the file. Once you reach the end of the file, you use the TextStream::Close method to end the script.
The While loop is the heart of the script. You begin the While loop by applying the TextStream::ReadLine method to read in one line of tsInputFile at a time. The strLine string variable holds the retrieved data from that line, which you pass to VBScript's Split function. Using the colon as the separator, this function splits the data set into its five parts, setting the data to the arrInput array variable. This array has index values that correspond to the five parts: 0 represents the username, 1 represents the expiration date, 2 represents the description, 3 represents the full name, and 4 represents the password.
The code in the middle of the While loop is similar to the code in Listing 3. After you create a reference to an ADSI User object and assign that reference to the adsUser variable, you set that user's property values (including the home drive). You then use SetInfo, create the home directory, set the directory permissions, and set the password. However, instead of specifying each user's username, expiration date, description, full name, and password in the code, you specify the appropriate array index value. For example, for those property values in which you need to specify the username, you put arrInput(0) instead of vlaunders, aglowenorris, kbemowski, or jkellett.
The While loop ends with setting adsUser to nothing. You need to clear adsUser because you use this variable again when the TextStream::ReadLine method reads in the next line from tsInputFile to create the next user account.
Instead of reading in user data from a text file, you can read in data from other sources, such as a Web-based form, Microsoft Word document, Excel spreadsheet, Access database, or specially formatted Microsoft Outlook email message. You can also use command-line arguments to pass in user data, as the next example shows.
Creating an Account Unlocker Utility
Imagine that you need a utility that quickly enables and unlocks an NT or Win2K user account that has become locked. Writing a user-specific script is inefficient if you have many users. Using an input file to pass in the needed user data to a universal script is also inefficient. You'd have to create the input file just before you run the universal script because you can't predict whose account you need to unlock. The best approach is to use command-line arguments to pass in the user data as you need it.
The scripts in Listing 5 and 6 use this approach to enable and unlock NT and Win2K user accounts, respectively. (If you have a mixed NT and Win2K network, you can combine these two utilities into one script.) You can run the scripts from a command window or the Start menu's Run option.
NT. You pass in two arguments—domain name and username—to the script in Listing 5. You use the Wscript::Arguments property method to retrieve the arguments. The Wscript::Arguments property method stores the arguments as a collection, indexing them from 0 to x, in which x is the number of arguments minus 1. The wshArgs collection in Listing 5 has the argument wshArgs(0), which represents the domain name, and wshArgs(1), which represents the username.
You use the WshArguments::Count method to count the number of arguments. If the count is 0, the script sends an error message and then quits. I recommend that you use the Wscript.Echo method to display the error message so that you can use cscript.exe or wscript.exe to run the script. If you use MsgBox (which displays messages as dialog boxes) in a script that you run from cscript.exe, the error messages will be illegible in the command window.
Next, you use the GetObject method to try to connect to the user account. Instead of specifying the actual ADsPath to the User object (which would make the script user-specific), you concatenate (&) the following elements in this order: "WinNT://" (i.e., the provider), wshArgs(0) (i.e., the domain name), "/" (i.e., the slash that separates the domain name and username), wshArgs(1) (i.e., the username), and ",user" (i.e., the object class).
If the connection attempt fails, the script writes an error message and then quits. If the attempt succeeds, the script puts the output from that attempt into the strOutput text string variable. That way, if you're running wscript.exe rather than cscript.exe, the results appear in one dialog box.
The next two sections attempt to enable and unlock the user account, respectively. However, the script doesn't quit if an attempt fails. The Err::Clear method, which works only if you enable On Error Resume Next, clears the error object so that you can detect the next error.
Whether an attempt succeeds or fails, the output goes to the strOutput string variable. You concatenate any new output text to strOutput. The vbTab constant (which inserts a tab) and the vbCrLf constant (which inserts a line return) ensure that any new text that you concatenate appears in separate indented lines underneath the user's ADsPath. Finally, you use the Echo method to print the results in strOutput.
This script is simple but powerful. You can easily add to the script so that it performs other tasks, such as changing passwords and account expiration dates.
Win2K. Because Win2K's AD supports the WinNT: provider, you can use Listing 5 to enable and unlock Win2K user accounts. However, I recommend that you use the script in Listing 6 instead because accessing AD via the LDAP: provider is a more elegant and efficient approach.
Although more elegant and efficient, using the LDAP: provider is a little tricky because users can exist in any container anywhere in a domain tree. Thus, you can't immediately attempt to bind to the user account because you don't know the ADsPath. You must first conduct an ADSI search to obtain the ADsPath.
In Part 5 of this series, I showed you how to use ADO objects to perform ADSI searches. Listing 6 uses ADO's Connection and Recordset objects to perform a search that locates the user's ADsPath. As callout A in Listing 6 shows, you begin this search by using null credentials to open an ADO connection. Using null credentials is a simple method of searching a directory without authenticating to it first. In other words, null credentials let you anonymously search the directory. If the ADO connection fails, the script displays an error message and then quits. If the connection succeeds, the script executes a search that includes the two arguments of wshArgs(0) and wshArgs(1) to form a set of filters. If you put the individual filters on separate lines and substitute the domain and username for wshArgs(1) and wshArgs(2), the filter set looks like
<LDAP://dc=windows,dc=mycorp,dc=com>; ((objectClass=User)(cn=vlaunders)); ADsPath; SubTree
(For more information about using filters, see "Understanding ADO Search Filters in LDAP Queries," May 1999.)
After you set the results of the search to the adoRecordset variable, you test that variable to see whether it contains an ADsPath. If adoRecordset is empty, the script displays an error message and then quits. If adoRecordset isn't empty, the script attempts to bind to the ADsPath. The rest of the script in Listing 6 proceeds similarly to the script in Listing 5.
More Manipulation Techniques Next Month
At this point, you're likely to be an ADSI scripting maestro, proficient at manipulating NT or Win2K users. Next month, I'll show you how to become proficient at manipulating NT and Win2K sessions and resources. Specifically, I'll discuss how to write ADSI scripts that change the username and password for a service, create shares, connect users, and count the current logons to a machine.
This article is adapted from Alistair G. Lowe-Norris' forthcoming book about the Windows 2000 Active Directory (O'Reilly and Associates).