Enumerating NT Users, Computers, Domains, and Groups

If you often produce reports for all your Windows NT servers or change the attributes of all your users, you might be running a script with a text-based input file containing a list of target objects. Although this approach is popular, it requires that you keep the input file up-to-date. An alternate approach is to enumerate the target servers or users at runtime. Active Directory Service Interfaces (ADSI) makes enumeration simple. I'll demonstrate how you can easily enumerate a variety of objects exposed via ADSI to obtain a list of domains, domain computers, users, and groups throughout an NT 4.0 or 3.51 network.

The following examples illustrate how to use GetObject and Filter methods of ADSI's most generic interfaces: IADs and IADsContainer. The GetObject method retrieves an object's IADs interface of an object in a container; the Filter method gets or sets a filter to retrieve objects in a container. (If you're unfamiliar with ADSI and its interfaces, see Alistair Lowe-Norris, "An ADSI Primer, Part 1," page 6.)

The examples use Windows Scripting Host (WSH) with Visual Basic Script (VBScript), but you can use any Component Object Model (COM)-enabled scripting language. You can download WSH from http://msdn.microsoft.com/scripting. Select Windows Scripting Host from the navigation menu on the left side. On the screen that appears, click Downloads. Besides the scripting language, you need to install ADSI. You can download the ADSI from the Microsoft site at http://www.microsoft.com/ntserver/nts/ downloads/previews/ADSI25/default.asp.

Enumerating NT Domains
You can query the WinNT namespace using ADSI to retrieve a list of available NT domains. Listing 1 provides an example of how to develop a procedure to retrieve such a list. (You can download the procedures in Listings 1 and 2 from the Win32 Scripting Journal Web site at http://www.winntmag.com/newsletter/scripting.)

You begin the script by using VBScript's Function statement to create a procedure. You must specify whether the procedure is to be accessible to all scripts (i.e., Public Function) or to other procedures within the same script only (i.e., Private Function). Whether Public or Private, the Function statement includes the procedure's name, arguments, variables, and code. All of Listing 1 is part of the Function statement. As the first line of Listing 1 shows, this procedure's name is GetAvailableNTDomains. The empty parentheses behind the name specify that there are no arguments.

Next, the VBScript Dim statements declare that GetAvailableNTDomains will use three variables: objIADsContainer, objIADsDomain, and vReturn. All variables in VBScript are of the variant type. A variant data-type lets you hold any type of data (e.g., string, integer, objects), so you should use a naming convention when writing scripts in VBScript. A naming convention prefixes a variable name with the type of data it holds. The prefix serves no real function other than to help you keep track of expected data types. In these examples, I use obj to signify that a variable is an object and v to signify that a variable is variant array.

A variant array is a variable that can contain one or more other variables. To access a variable within an array, you need to know the index value (i.e., a number that identifies where the variable resides). The ReDim statement following the declaration of vReturn provides the index value.

Before you can use an object variable, you must create a connection to an existing object. One way to create this connection is to use VBScript's Set statement, which assigns an object reference to a variable. VBScript creates references to objects rather than making copies of them to conserve system resources. In the case of objIADsContainer, you're referencing the container object that represents the WinNT namespace. To create this reference, you use VBScript's GetObject method with a valid ADsPath that points to the container object representing the WinNT namespace. ADsPaths are case sensitive and enclosed in quotation marks because they represent a string variable.

Now that you've established the connection, you can use VBScript's For Each...Next statement to loop through each domain (objIADsDomain) in the WinNT namespace (objIADsContainer), applying VBScript's If...Then statement. To properly fill the return value (vReturn) of the function, you need to use an If...Then statement to check the first element in the array (vReturn(0)). If this element isn't a blank string (i.e., not ""), you need to add one element to vReturn.

To add an element, you use ReDim Preserve. The Preserve argument lets you allocate additional elements to the array without affecting data contained in existing elements. Because you're adding only one element at a time to the array, you can use VBScript's Ubound function. Ubound returns the number of elements that an array contains. In this case, you want to add one element to the Ubound of vReturn. This new element goes at the end of the array.

Now that you have a place in the array for a domain name, you can set this array element's value. The objIADsDomain's Name property returns the name of the domain. You place this value in the array element you just created, again using the Ubound function.

After you're finished using objIADsContainer and objIADsDomain, you set them to Nothing to remove the reference between these variables and their respective existing objects. Removing the reference lets the system reallocate the resources that the procedure used.

Next, the return value for the function needs to be set to the array vReturn. In this case, the return value is a variant array containing NT domains. Finally, the script ends by exiting the procedure.

Enumerating NT Computer Accounts
After you've listed the NT domains, you can create a procedure that returns an array containing the computer accounts for a particular domain. As Listing 2 shows, to enumerate NT computer accounts, you use the Function statement to create the GetDomainComputers procedure. Unlike GetAvailableNTDomains in Listing 1, GetDomainComputers has an argument, ByVal strDomain. Domain is the name of the argument, and the prefix str specifies that this argument is a string variable. ByVal specifies that you want to pass the value, rather than the address, of the argument to the procedure. If you want to pass the address, you would specify ByRef instead. ByRef lets the procedure access and therefore manipulate the variable. ByVal lets the procedure access only a copy of the variable, so the procedure can't change the variable.

GetDomainComputers uses GetObject with the AdsPath "WinNT://" & strDomain. This path returns an ADSI container object (IADsContainer) that represents the domain you're interested in. Through this container object, you access the objects representing the users (i.e., User objects), groups (i.e., Group objects), computer accounts (i.e., Computer objects), and other members of that domain. Because you're only interested in Computer objects, you need to filter out the others. You accomplish filtration by using IADsContainer's Filter method. You first use VBScript's Array function to convert "Computer" to a variant array, then set the Filter method to this variable's value. Finally, as you iterate through the container of computer accounts with the For Each...Next statement, you add each Computer object to the array that will become the return value of the GetDomainComputers procedure.

After you get the return values from GetDomainComputers, you can use one of those values to retrieve an array of User accounts for a specified computer or domain. The procedure to enumerate user accounts for a specified computer—called GetComputerUsers (Listing 3)—is on the Win32 Scripting Journal Web site. The site also contains the GetComputerGroups procedure, which you can use to return a list of NT groups on a specified computer. You can also easily modify the procedures to list services, printers, and shares running on an NT server.

You can copy the GetAvailableNTDomains, GetDomainComputers, GetComputerUsers, or GetComputerGroups procedure into a VB or Active Server Pages (ASP) application and use that application without modifying it. If you would rather run a script from the command line, you can download the NTEnum.vbs script from the Win32 Scripting Journal Web site. By passing in the appropriate command-line arguments, which I've provided in an accompanying table on the Web site, the script lists domains, computers, users, or groups on your screen.

A Welcome Addition
ADSI promises ease of data access and administration for the next version of NT. The GetAvailableNTDomains, GetDomainComputers, GetComputerUsers, and GetComputerGroups procedures are a great place to start exploring the possibilities available with WSH and ADSI. Keep in mind that ADSI provides access to not only the WinNT namespace but also additional namespaces, such as Lightweight Directory Access Protocol (LDAP) and Novell Directory Services (NDS). ADSI promises to be a versatile tool in your scripting toolkit.