One of the most useful technologies that Microsoft has incorporated into the Windows OS is Windows terminal services, which lets you remotely log on to a machine. After logging on, you can interact with the remote machine as if you were physically logged on to it. The remote machine sends all video output over the network to you, and your mouse and keyboard input is sent to the remote machine.
Systems administrators use terminal services because it lets them remotely perform work that they formerly had to sit at the machine to perform. Terminal services lets administrators defragment hard disks, install drivers, apply service packs, and perform other routine tasks from outside the computer room. Administrators and end users also use terminal services to access their machines from home. Regardless of the reason people use terminal services, tracking who is logged on to a particular machine is difficult. Thus, I've written a couple of scripts—WTSListUsers.pl and WTSLogoff.pl—that can ease terminal services administration.
Jens Helberg's incredibly useful Win32 Perl extension Win32::Lanman (version 1.09 and later) exposes terminal services functions that let you list logon sessions, query for information about a given logon session, manage a user's terminal services logon rights, send a message to a logged-on user, and perform other administrative tasks. You can download the extension from http://www.cpan.org/modules/by-authors/id/J/JH/JHELBERG. My book Win32 Perl Programming: The Standard Extensions, 2nd edition, covers these functions in detail (http://www.roth.net/books/extensions2).
The script in Listing 1, page 2, lets you display a list of the users logged on to a given terminal services machine. To run the script, type
where myserver is the name of the terminal services server. The script then displays a list of users logged on to the terminal services server. For each user session, you'll see the username and domain. If the user is logged on over the network from a remote machine (as opposed to being logged on through the console—aka the keyboard), the script also displays the remote machine's IP address, network name, display size, and color depth.
The script calls the Win32::Lanman extension's WTSEnumerateSessions() function (at callout A in Listing 1). The function accepts the name of the terminal services server and a reference to an array and populates the array with an anonymous hash for each terminal services session. The hash contains the session ID, the session state (e.g., the user is disconnected, logged on, shadowing), and the session's window station name.
Next, the script examines each session. One session will have a state of WTSListen (i.e., a value of 0x04). This particular session isn't a user session—it's a session that's waiting for the next user to log on—therefore, no username is associated with it. The script ignores this session.
For each of the other sessions, the script calls the WTSQuerySessionInformation() function (at callout B). The script passes to the function the name of the terminal services server, the session ID, a reference to an array, and a reference to a hash. The array contains values that indicate which session information the function should obtain. For example, the constant WTSUserName (i.e., the value 0x05) indicates that the function should request the session's username.
The function populates the hash with keys and values that contain the information that the function requested. Note that not all the pieces of information are available for all the sessions. For example, a remote computer name isn't available for console sessions.
If the session represents a remote user (someone not logged on through the console's keyboard), the script performs additional steps. Two pieces of information that the function returns—the client machine's IP address and display size—are packed into binary structures that the script must unpack. The code at callout C unpacks the IP address structure into two components: the network identifier ($Net) and a 20-byte address array ($Address) that the script must also unpack. When $Net is a value of 0x02, the network is TCP/IP. (Other values indicate IPX, NetBIOS, or other networks.) If the network is TCP/IP, the client's IP address is in bytes 2 through 5 in $Address. The code at callout D looks in those positions to discover the address. The client display's width, height, and color depth are simply packed as three unsigned long values. The trick that the code in callout E performs is to roll the color depth value left by 2 bits to discover the color depth (i.e., the number of colors). The resulting value is the number of bits used to represent a color pixel.
The code at callout F finally displays the information about remote-client sessions that the script has discovered. The script prints to the screen the remote client's IP address, machine name, and display size and color depth.
Managing a Terminal Services Session
If you know the current terminal services sessions on a particular machine, you can perform an action that affects a session. The WTSLogoff.pl script, which Listing 2 shows, lets an administrator disconnect a user's terminal services session or log off the user. Disconnecting a session just closes the connection between the user and the terminal services machine. The user can reconnect later and continue where he or she had left off when you disconnected the session—programs keep running and no data loss occurs. Forcing a log off, however, causes all the user's running applications to close, which might result in the user losing data if he or she had any open files.
When you call WTSLogoff.pl, you pass in the name of the terminal services server and the ID of the session that you want to disconnect or log off. (You can find a session's ID by using WTSListUsers.pl, Task Manager, or Terminal Services Manager.) By default, the script forces the specified session to log off. However, if you pass in a /D parameter, the script disconnects the session instead. For example, the command
logs off session 3 on the terminal server MyServer. The command
disconnects session 3 on MyServer. The user can reconnect and continue his or her session at a later time.
As the code at callout A in Listing 2 shows, WTSLogoff.pl calls the WTSEnumerateSessions() and WTSQuerySessionInformation() functions to procure the remote clients' IP addresses. If you included the /D parameter when you launched WTSLogoff.pl, the code at callout B calls the WTSDisconnectSession() function to force the disconnection. If you didn't specify the /D parameter, the code at callout C calls the WTSLogoffSession() function to close all of the session's applications and log off the user.
Terminal Services Power with Perl
Terminal services is one of the most powerful features of Windows .NET Server, Windows XP, Windows 2000, and Windows NT. The Perl Win32::Lanman extension provides the functions to harness this powerful service. This month, I revealed just the tip of the iceberg. You can access many other interesting and useful terminal services features from Perl. Have a look at the Win32::Lanman extension's documentation and see which functions can make your job easier. Then drop me an email message and let me know what you think.