Downloads
94278.zip

It's useful to be able to query a computer for the users that are logged on locally. You can do this with Sysinternals' free PsLoggedOn utility, but sometimes you want to use the information directly in a scripted task (e.g., contacting users who are logged on to a terminal server that needs a reboot because of a security patch). Although it's possible to execute the PsLoggedOn utility and parse its output in a script for use in other scripts, I decided to see whether I could gather the names of logged-on users by using a script instead of relying on PsLogged-On.

How PsLoggedOn Works
I started by downloading and examining the C source code for the Ps-LoggedOn utility to see how it determines locally logged-on users. The technique it uses isn't complex and is fairly easy to implement by calling the methods in Windows Management Instrumentation's (WMI's) StdRegProv class. An overview of the technique PsLoggedOn uses is as follows:

  1. Enumerate the subkeys in the computer's HKEY_USERS registry subtree.
  2. For each subkey in the subtree that contains a SID value, determine whether the subkey contains a Volatile Environment subkey. If it does, and if the Volatile Environment subkey contains one or more values, then the user that the SID represents is currently logged on.
  3. Convert the SID value into the corresponding username.

In Figure 1, the HKEY _USERS subtree is expanded to show a subkey that has a SID value. You can also see that the subkey contains a Volatile Environment subkey that contains values. Thus, you know that the user represented by the SID S-1-5-21-299502267-1078145449-725345543-500 is currently logged on.

Introducing LoggedOn.js
LoggedOn.js provides a couple of advantages over the PsLoggedOn tool. First, you don't have to parse Logged-On.js's output when you need a list of logged-on users for a script. Second, it doesn't rely on any third-party functionality. Depending on third-party code can be a problem in some environments. LoggedOn.js uses the same technique as PsLoggedOn to list local logons on a specified computer. To run the script, you need either Windows XP or later, or Windows 2000 with Windows Script Host (WSH) 5.6. The script's command-line syntax is

LoggedOn.js <computername>

where computername is the name of the computer whose locally logged-on users you want to retrieve. Logged-On.js connects to the specified computer and lists the users who are logged on locally.

If you need to query Win2K Service Pack 4 (SP4) computers for local logons, there's a problem you need to know about. Win2K SP4's StdRegProv class has a bug that prevents WMI-based scripts and programs from querying the HKEY_USERS subtree. To fix this bug, you need to get a copy of the hotfix mentioned in the Microsoft article "Cannot Use WMI to Query HKEY Users After You Install SP4" at http://support.microsoft.com/?kbid= 817478. The hotfix updates the stdprov.dll file in %SystemRoot%\system 32 and doesn't require a reboot. You'll have to call Microsoft Product Support Services to get a copy of the hotfix.

Also, for best results, I highly recommend that you use UPHClean, a free Microsoft utility that monitors a computer when users log off and ensures that user profiles are completely unloaded. Otherwise, Logged-On.js (and PsLoggedOn as well) might report logons for users who have since logged off but whose profiles didn't unload completely. For more information about the UPHClean service, see the Microsoft article "Troubleshooting profile unload issues" at http://support.microsoft.com/?kbid=837115.

Inside LoggedOn.js
The LoggedOn.js script, excerpted in Listing 1, starts by declaring some global variables. It then calls the Quit method of the WScript object, with the main function as a parameter. In other words, the main function's return value is the script's exit code.

The main function declares some variables and parses the command line. It uses the first unnamed argument (i.e., the first argument on the script's command line that doesn't start with a / character) as the computer name. Callout C in Listing 1 shows the try block that the main function uses to connect to the computer. If the connection succeeds, the code in the catch block never executes. In case an error occurs, the catch block causes the main function to display an error message and execute the return statement with the error number as its argument.

Calling WMI in JScript
JScript doesn't support calling methods that require output parameters. Many of the StdRegProv methods use output parameters to return their results. Microsoft's WMI programmers devised an alternative calling convention for WMI methods that doesn't require output parameters. I discussed this alternative calling convention in more detail in "Calling WMI Methods with JScript," November 2006, InstantDoc ID 93402, but here's a quick overview:

  1. Create an InParameters object for the method.
  2. Set the InParameters object's properties equal to the method's input parameters.
  3. Call the method using the Exec-Method_ method to return an Out-Parameters object.
  4. Access the OutParameters object's properties to determine the results of the method.

Callout D shows how the main function implements the above steps to execute the EnumKey method to enumerate the HKEY_USERS subtree. The OutParameters object's properties will vary depending on the method, but all OutParameters objects have a ReturnValue property that contains the method's result (a zero indicates that the method call succeeded). Call-out E shows how the main function checks whether the EnumKey method succeeded. If the method didn't succeed, the main function displays an error message and executes the return statement with the nonzero exit code as its argument.

Next, the main function accesses the sNames property of the OutParameters object, which contains a SafeArray (also known as a VBArray), and calls the toArray method to convert it into a JScript array. The function then creates an empty array to contain the list of users. It's necessary to use an array to contain the logged-on users because there might be multiple locally logged-on users. For example, terminal servers typically have many users logged on at once.

The main function's next task is to use the for statement to iterate the array of subkey names. The main function first creates an uppercase copy of the subkey name. If the subkey name isn't .DEFAULT and doesn't end with the string _CLASSES, then the subkey name is a SID value that represents a user. The main function then calls the GetLoggedOnUser function with the computer name and the subkey name (a SID value) as parameters. (I describe the GetLoggedOn User function a little later.) If the Get-LoggedOnUser function returns a non-empty string, then the main function adds the username to the array of users.

After building the array of logged-on users, the main function checks the array's length property. If the array is empty, the function outputs a message that there are no logged-on users and returns with a nonzero value. Otherwise, the main function echoes the array of logged-on users by calling the ArrayToString function (at callout A). The ArrayToString function accepts an array as a parameter and returns a string that contains the elements in the array separated by newlines.

The GetLoggedOnUser Function
The GetLoggedOnUser function (at callout B) looks for a subkey named like this:

HKEY_USERS\<subkey>\Volatile
  Environment

where subkey is the second parameter passed to the function and contains a SID value. If the specified subkey exists and contains one or more values, the GetLoggedOnUser function retrieves the Win32_SID object that corresponds to the subkey name (i.e., the user's SID) and builds a domain\ username string by referencing the Win32_SID object's ReferencedDomain-Name and AccountName properties. If the GetLoggedOnUser function fails to retrieve the Win32_SID object, it simply returns the SID value as the username.

The main function outputs the array of logged-on users as a single string, with each username on a separate line. You can use this output without having to parse it (as you would with PsLoggedOn). Add this script to your scripting toolkit, and you'll always be able to tell who is logged on locally to a computer.