Editor's note: This article is the seventh 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.
The future of automating systems management-related tasks lies with a technology called Windows Management Instrumentation. WMI is an implementation of the Desktop Management Task Force's (DMTF's) Web-Based Enterprise Management (WBEM) initiative, which provides standards for accessing and sharing management information in an enterprise environment. WMI has unique schema, classes, and interfaces. As part of the WBEM/WMI initiative, Microsoft intends to provide an ADSI-WMI bridge that will transparently expose data regardless of the underlying provider. Thus, WMI will likely become the primary technology that you'll use for systems management-related tasks.
In the meantime, you can use ADSI to accomplish some of those same tasks. ADSI provides a host of interfaces that you can use to manipulate persistent objects and dynamic objects in the Windows 2000 (Win2K) Active Directory (AD) or Windows NT SAM. Persistent objects are permanent parts of a directory. For example, a computer's shares and services are persistent objects. Dynamic objects aren't permanent parts of a directory. For example, sessions (i.e., connections to a machine) and print jobs that users initiate are dynamic objects.
ADSI's extra interfaces let you operate the dynamic parts of an OS and manipulate the permanent parts of a directory. In other words, ADSI lets you:
- Dynamically start, stop, and manage services, and manipulate the permanent attributes of those services.
- Dynamically manipulate computers' open resources and users' active sessions, and manipulate the permanent objects representing those computers and users.
- Dynamically manipulate print jobs, and manipulate the permanent queues.
This month, I describe how to use ADSI to start, stop, and manage Win2K or NT services and manipulate those services' attributes.
The Interface Methods and Properties
Rather than describe the various interface methods and properties as I've done in the past, I'll concentrate on how to use those methods and properties in scripts. You can find complete descriptions of the interface methods and properties on the Microsoft Developer Network (MSDN) Library CD-ROM or Web site. To access the descriptions on the MSDN Web site, go to http://msdn.microsoft.com/ library/default.htm and then navigate to Platform SDK, Networking Services, Active Directory, Active Directory Service Interfaces (ADSI) Version 2.5 Beta 1, ADSI Reference, ADSI Interfaces. From this point, you can navigate to Core Interfaces (which contains descriptions for IADs or IADsContainer), Persistent Object Interfaces (which contains descriptions for IADsFileShare and IADsService), and Dynamic-Object Interfaces (which contains the description for IADsServiceOperations). After you know the basics of the interface methods and properties, you can use them to identify and manipulate the services on a machine.
Identifying the Services
Suppose you want to identify all the services on an Win2K or NT computer. You can use several approaches to list the services.
The simplest approach is to bind to the parent computer and print all the permanent objects. As Listing 1 shows, the script binds to the server object with the path
in which mydomainorworkgroup is the name of your domain or workgroup and mycomputer is the name of your computer. The trailing ,computer parameter ensures that you retrieve a Computer object with that name in case a user or group called mycomputer exists.
Whether you're connecting to an Win2K or NT machine, you must always use the WinNT: provider. You use the WinNT: provider to access the Win2K OS because you're connecting to the machine's OS, not its directory service.
The script then uses the IADs::ADsPath property method to print the computer's full path and the IADs::Name and IADs::Class property methods to print the names and schema classes of the objects in that computer. Because the list of objects will be long and MsgBox has a size limit, the script uses the Wscript::Echo method rather than MsgBox to print the text.
You use the Windows Scripting Host (WSH) command-line processor, cscript.exe, to run the script. From the command line, you type
CSCRIPT listing1.vbs | MORE
When the script runs, the MORE command forces the text to scroll by in a command-line window a page at a time. If you don't include the MORE command, you can increase the output buffer size for cmd.exe (Win2K or NT) or command.exe (Windows 9x) so that you can get more than 25 lines in the buffer.
To increase the output buffer size, open the properties window for the shortcut that you use to start a command prompt. If you're using Win9x, select the Screen tab. As Screen 1 shows, use the drop-down list to set Initial size to the number of lines you want the window to display. You have more options if you're using NT or Win2K. As Screen 2 shows, the Layout tab lets you set the window's width (measured in characters) and height (measured in lines). You can also set the screen's buffer size. The buffer displays and holds the output so that you can scroll back through it.
Screen 3, page 8, contains example output from Listing 1. A couple of extra touches in the script make its output easier to read. First, the script uses the VBScript Err interface, which lets you see whether the container is empty to start with. Second, a simple integer-counting variable (intCount) prefixes all the objects with a count.
Although the script in Listing 1 prints the service objects, it also prints all other objects. You can adapt this script so that it prints only the services. If you replace the code at callout A in Listing 1 with the code in Listing 2, the script will print only the service objects.
However, you can use a more efficient approach to list a computer's services: Use the IADsContainer::Filter property method so that ADSI filters the objects for you. Here's how this approach works. Each object in a container (i.e., the computer) has a schema class that you can retrieve with the IADs::Class property method. As Screen 3 shows, these classes have names such as User, Group, Service, and PrintQueue. If you pass an array of these classes to the IADsContainer::Filter property method, the Filter property method returns just those classes of objects from the container. The Filter property method also affects the IADsContainer::Count property method so that the Count property method counts only those objects matching the filter.
You use the VBScript Array function to create the filter. So, for example, to create a filter that returns objects of the class Service, you use the code
adsComputer.Filter = Array("service")
Listing 3 contains a script that uses this code to filter out only the service objects. If you want to remove the filter, you use the VBScript Empty keyword to specify an empty property:
adsComputer.Filter = Empty
Using ADSI to retrieve the names of a machine's services is helpful, but you can use ADSI to accomplish a lot more. For example, you can use ADSI to dynamically manipulate services.
Let me use an example to illustrate how to dynamically manipulate a service. The online version of this article on the Win32 Scripting Journal Web site contains a command-line service utility that I created. This utility interacts with all the IADsServiceOperations interface methods. It functions by binding to the computer, connecting to the service on that computer, and using IADsServiceOperations to interact with the service. The service utility's format is the same as that of the command-line account unlocker utility I discussed last month.
The service utility takes three parameters that specify the domain or workgroup, the computer, and the service you want to connect to. If you pass in only these three parameters, the utility returns the service's current status. You can pass in additional parameters that tell the utility to complete a specific task. The possible tasks include starting, stopping, pausing, or continuing the service and changing the service password.
To create the service utility, I first declared the constants that describe the status of the service. (Although Visual Basic—VB—defines the names of constants, VBScript doesn't.) I declared eight constant names and set their values. For example, the constant to specify that a service has stopped is
Const ADS_SERVICE_STOPPED = 1
When the utility runs, it returns a constant's value rather than its name. For example, the utility returns a value of 1 rather than ADS_SERVICE_STOPPED. To make the utility more user-friendly, I used the arrServiceText array to translate the returned values into descriptive text. For example, the utility returns the message The service has stopped rather than returning the result of 1.
I indexed the constants to hold the descriptive text. Arrays always start with an index number of 0, so even though I used the statement
to declare that my array holds eight elements, the array automatically indexes the elements from 0 to 7. (For more information about arrays, see Dino Esposito, "Understanding VBScript," page 1.) Thus, I had to subtract 1 from every constant to get the correct index. For example, the code to prompt the utility to return the message The service has stopped is
arrServiceText(ADS_SERVICE_STOPPED - 1) = "The service has stopped."
Although I could've just used an index of 0, this approach ensures that the text and constant names match up when I'm writing the script. And if I make a mistake in my code, this approach minimizes the chance of me chasing the wrong error message.
After declaring the constants and variables, I included code that forces the script to quit if you don't pass in any parameters. Before the script quits, it prints a message detailing how to use the utility.
If you pass in at least one parameter, the utility initializes the output text string, binds to the server object, and sets the result to adsComputer, as the excerpt in Listing 4 shows. (In my scripts, I typically write the output data to a text string, but you can just as easily write the output data to a spreadsheet or text file for central tracking purposes.) The utility then uses the IADsContainer::GetObject method to retrieve an object of class Service from that computer and sets the result to the adsService variable. The IADsContainer::GetObject method differs from VBScript's GetObject function in that IADsContainer::GetObject lets you specify valid classes.
I could've bypassed using the adsComputer object and bound directly to the service object with code. However, because I used the adsComputer object, the utility checks the validity of not only the service name but also the computer name.
After the utility successfully binds to the service, it checks to see whether you've supplied any more parameters. If you pass in three or fewer parameters, the script uses the IADsServiceOperations::Status property method to check against the existing constants and print the relevant text from the arrServiceText array. If you pass in more than three parameters, the script uses a Select Case statement, which acts like a multiple If...Then...Else statement to compare the fourth parameter to the six cases listed. (Before the Select Case statement makes these comparisons, though, it uses the VBScript Lcase function to convert the fourth parameter to lowercase.)
The first four cases are fairly straightforward. They use the IADsServiceOperations::Start, IADsServiceOperations::Stop, IADsServiceOperations::Pause, and IADsServiceOperations::Continue methods, respectively, to start, stop, pause, or continue the service. The excerpt in Listing 5 shows the code for the "stop" case.
The fifth case uses the IADsServiceOperations::SetPassword method to set a service password. As the excerpt in Listing 6 shows, you specify the password you want as the fifth parameter. (The password must match the account password in the SAM or AD; otherwise, the service will fail with a logon error the next time the service tries to start.) When the script runs, the utility checks to see whether a fifth parameter exists. If the password exists, the utility sets it; if the password doesn't exist, the utility sets a blank password.
The last case is the Case Else statement. Similar to the Else condition's role in an If...Then...Else statement, the optional Case Else statement catches all invalid parameters and generates error messages. The Case Else statement always goes last in a list of case statements.
Applying the Knowledge
You now know how to identify and manipulate the services on an Win2K or NT computer. You can use this knowledge to create scripts that perform similar tasks. For example, you can transfer the knowledge to write a script that identifies and manipulates the shares on an Win2K or NT computer. (For tips about how to identify and manipulate shares, check out the Webexclusive sidebar "Creating and Manipulating Shares with ADSI." Go to the Win32 Scripting Journal Web site, and select View Current Issue in the Subscriber Areas. Select Creating and Manipulating Shares with ADSI from the July table of contents.)
Next month, you'll learn even more about the persistent and dynamic objects with which ADSI can interact. I'll discuss the interfaces you can use to manipulate open user sessions and resources. And I'll show you how to create a utility that counts simultaneously connected users across a domain.
This article is adapted from Alistair G. Lowe-Norris' forthcoming book about the Windows 2000 Active Directory (O'Reilly and Associates).