Editor's note: This article is the eighth 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.
Last month, I showed you how to use ADSI to manipulate objects such as services and shares that exist on a local machine's SAM. This month, I want to extend that usage by showing you how to use ADSI to
- Enumerate a client's sessions and resources
- Show which users are currently logged on to a server and count all the logged-on users across a domain's PDC, BDCs, and other servers
Enumerating Sessions and Resources
Windows NT hosts two kinds of dynamic objects that you can use with ADSI to gain read-only access: sessions (i.e., instances in which users are connecting to a computer) and resources (i.e., instances in which users are accessing a file on a computer). When users connect to a file or a share on a computer, that instance creates both a session and a resource object. When the user disconnects, these dynamic objects cease to exist.
You can access these dynamic objects by connecting directly to the Windows 2000 (Win2K) or NT service, which is called a server service. Although each server service has a user-friendly display name that appears in the Computer Management console (in Win2K) or the Services applet in Control Panel (in NT), each server service also has an ordinary name that you use when connecting to it with ADSI. For example, Server is the display name of the service that has the ordinary name of LanManServer. If you enumerate all the services on a machine, you can use IADsService:: DisplayName to print the display name and IADs::Name to print the ordinary name.
LanManServer is an object of type FileService. FileService objects are responsible for maintaining the sessions and resources in their jurisdictions. You can use the IADsFileServiceOperations interface to access information about these sessions and resources. This simple interface has two methods: IADsFileServiceOperations::Sessions and IADsFileServiceOperations::Resources. Both methods return collections of objects that you can iterate through with a For Each...Next loop. When you're iterating through a collection in this manner, the system is using IADs Collection::GetObject to retrieve each item from the collection. As a result, you can use the same IADsCollection::GetObject method to retrieve a specific session or resource object. You can then use the IADsSession or IADsResource interface to manipulate that session or resource object's properties to access information. For example, if you retrieve a session object, you can access such information as the username of the user who is logged on and how long that user has been logged on.
Listing 1 contains a script that uses IADsSession to iterate through all the sessions on a particular machine. Figure 1 shows the output from this script.
The script in Listing 1 is straightforward. It uses the IADs::Name property method and IADsSession property methods to retrieve data about the session. The IADs::Name property method displays the object name, which is the name that you would use with IADsCollection::GetObject to individually retrieve the specific session. As Figure 1 shows, the object name always follows the format user\COMPUTER. For example, the object name for user1's session on COMPUTER1 is user1\COMPUTER1. However, in some sessions, the underlying system rather than a person is connecting to the computer. In such sessions, the object name follows the format \COMPUTER (e.g., \SERVER1).
You can use IADsSession property methods to retrieve the individual components of the object name. The IADsSession::Computer property method retrieves the computer component (e.g., COMPUTER1). The Connected User and Client Computer Name fields in Figure 1 contain the results of these property methods. The IADsSession::User property method retrieves the user component of the object name (e.g., user1).
Callout A in Listing 1 highlights an important consideration when you're specifying WinNT: provider paths in a script. If you use only the computer name in the path with code such as
your script will execute slowly because the system must locate the machine and its workgroup. However, if you include the workgroup in the path with code such as
your script will execute significantly faster because the system can immediately access the machine.
Listing 2 contains a script that enumerates the resources in use on a machine. Figure 2 shows the output from this script. The output lists those files that each user has open. The Microsoft Excel spreadsheet that user3 has open is locked. (If you want to see locks in action, have one user open up a shared document and have another user try to open it.)
A Utility to Show User Sessions
You can use ADSI to write a script that displays which users are currently logged on to a server and counts all the logged-on users across a domain. For simplicity, suppose that you have only two servers in your domain. You want to determine and display the maximum number of simultaneous sessions on each server, the total number of sessions across the domain, the total number of unique connected users on the domain, and an alphabetized list of usernames. (Users can simultaneously connect to both servers from their computer. However, you want to count these users only once—i.e., count the unique connected users.)
You can use the session object to construct ShowUsers.vbs, a useful utility that determines and displays this user session information. You can find ShowUsers.vbs on the Win32 Scripting Journal Web site. You run this utility from your desktop. What follows is an overview of how the ShowUsers.vbs utility obtains, manipulates, and displays the data. For an in-depth look at how the script works, see ShowUsers.vbs on the Web site. I heavily commented this script to show how it works line by line.
Obtaining the Data
ShowUsers.vbs begins by iterating through all your servers. To specify the servers you want to scan, you can either hard-code the server information in the script or have the script dynamically retrieve this information. I've hard-coded the server information in ShowUsers.vbs.
When the utility iterates through all the servers, it ignores any empty usernames (which specify interserver connections) and usernames with a trailing dollar sign (which denote users that are computers). For each valid session, the script records the username and increments the session count by one.
The script uses a dynamic array (arrResults) to store the username data because the number of usernames in the array will change each time you run the utility. The script uses a multidimensional array (arrServerResults) to store the servers' names and maximum number of connected sessions. The arrServerResults array stores this information in a simple table, putting the server names in the first column, the counts in the second column, and the data in the rows. To access data in arrServerResults, you include the indexes of first and second dimensions, respectively, in parentheses. For example, arrServerResults(0,1) accesses the data in the first row (0), second column (1). Thus, your server names are in arrServerResults(0,0) and arrServerResults(1,0). The corresponding session counts are in arrServerResults(0,1) and arrServerResults(1,1). For more information about multidimensional arrays and dynamic arrays, see Dino Esposito, "Understanding VBScript," July 1999.
Manipulate the Data
After the script iterates through every server, you have a list of server session counts and a list of the usernames of those users who were connected to the servers at that time. Because some of the users might have been connected to both servers and hence might appear in the username list twice, the script uses two subprocedures to manipulate the data. One subprocedure sorts the usernames; the other subprocedure removes duplicate usernames.
The sort subprocedure. If you took any computing science courses in college, you likely remember having to perform bubble sorts and shell sorts. Although including a general-purpose quick sort like the bubble or shell sort in VBScript would've made sense, Microsoft failed to do so. Fortunately, I found the Quicksort algorithm in the article "Sorts of All Types: VBA Algorithms for Getting Things in Order" (Microsoft Office and VBA Developer, March 1998, http://msdn. microsoft.com/ library/periodic/period98/html/ ovbad0398sorting.htm). The Quicksort algorithm is for Visual Basic for Applications (VBA), but I easily adapted the code for VBScript.
The Quicksort subprocedure takes in an array indexed from 0 to UBound(array) and sorts the values in the array between the two indexes you pass in as arguments. For example, if you specify
Quicksort sorts elements 7 through 19 in arrMyArray. I maintained Quicksort's ability to sort between two indexes in case I want to reuse the procedure in another script that needs that functionality. However, in ShowUsers.vbs, you need to sort the whole array of usernames—i.e., between indexes 0 and UBound(array).
The duplicate removal subprocedure. After Quicksort sorts the username list, the RemoveDuplicates subprocedure removes any duplicate usernames. Like Quicksort, RemoveDuplicates takes in an array and two indexes as arguments. (When I created this subprocedure, I gave it the ability to work between two indexes so that RemoveDuplicates and Quicksort are comparable.) When RemoveDuplicates enumerates through a sorted list, it ignores items with the same name as the next item in the list and then passes the remaining elements to a new array. For example, if a sorted list reads
RemoveDuplicates reads the list as
and passes the remaining elements
to a new array. The subprocedure creates a new array because placing the results into a second array is faster than manipulating the existing array.
Displaying the Data
You can't use the MsgBox function to display the data because the data set that the utility will gather will likely be large. Although you can use the WScript.Echo method at the command prompt, that display method is out of date for today's Win32 platform. Thus, the script writes the data to a file.
After all the data is in the file, the script uses the Shell::Run method to automatically open Notepad, maximize the window, display the results, and delete the file when you close Notepad. The Shell::Run method lets you open and use an application such as Notepad synchronously with the script. The script pauses execution when the application is open and doesn't start up again until you close Notepad.
Room for Improvement
Although ShowUsers.vbs is useful, this utility is lacking in one area: Users can legitimately use two connection slots if their IADsSession:: Computer names are different, but the utility counts the user only once. For example, user1 might log on to the domain twice, once on COMPUTER1 and once on SERVER1, but my script counts user1 only once because of the RemoveDuplicates subprocedure. If you want to make the script even better, you can create an extension to the utility that remedies this situation. For example, the extension might log all user counts to a file every 5 minutes for later analysis.
That's it for this month. Next month, I'll delve into printer management.
This article is adapted from Alistair G. Lowe-Norris' forthcoming book about the Windows 2000 Active Directory (O'Reilly and Associates).