Tracking Workstation Logons

Whenever I teach a Windows-related class of almost any kind, one of the big questions that tends to come up is “How can I tell what user is logged onto a workstation,” or “How can I tell which workstation a particular user is logged on to?”

 

Sadly, the answer is usually, “um, you can’t.” At least, not with the native Windows tools, simply because Windows doesn’t track that information anywhere. First of all, understand that Windows is inherently a multi-user operating system. You only really get a sense of that on a Remote Desktop Services server, where multiple users actually do log on simultaneously, but you can also see it on any server when a couple of administrators are remoting in using Remote Desktop Connection. Even Windows client operating systems, like Windows 7 or Windows XP, inherently understand the idea that more than one user can be logged on, interactively, at the same time— even if they don’t actually allow that to happen all the time (Fast User Switching in the Home editions of those operating systems do allow multiple users to be logged on at once, if not actually active simultaneously). Anyway, that multiple-user viewpoint means that Windows can’t just pop a username into a field someplace for you to check, because that field would need to be able to hold multiple user names.

 

The easiest solution would be if Microsoft put a multiple-value attribute onto the Computer class inside Active Directory. Then, whenever a user logged on to a computer, the computer would write that person’s name into that attribute, which you could then query to see who was logged on. When the user logged off, the computer could contact the domain and remove their name from the attribute. It wouldn’t be perfect, because sometimes a workstation doesn’t get that opportunity—say, if a user yanks the power plug. But because most organizations dedicate one computer to one user, you’d have the right information most of the time. But it doesn’t matter, because Microsoft hasn’t done that.

 

If you want this capability then, you’re going to have to build it yourself.

 

The Easiest Approach

 

I’m going to start by showing you how to do this in the easiest possible way from a technical perspective. I’m also going to assume that you’re in a mixed environment in terms of Windows versions, with the lowest-common-denominator being Windows XP on the client and Windows Server 2003 for servers. Those choices constrain my choices of technologies and approaches, and will make this article useful for the broadest number of readers. There are definitely other approaches you could take, but they’d require additional components, newer versions of Windows, and so forth. So we’ll start with this one, which is a pretty low-tech affair.

 

Basically, we’re going to write a logon script in VBScript, since that’s pre-installed on all of the operating system versions I want to target. That script is going to do nothing more than write the logging-on user’s name to a text file, which is stored on a file server. The text file won’t contain anything: It’s filename will take the form COMPUTERNAME-USERNAME.txt. That way, a single computer could write multiple files if multiple users were logged on. “Querying” this would simply involve scanning through the directory or even using the Search function in Windows Explorer. Hey, I said it was low-tech.

 



Figure 1 Example Logon Tracking Script

 

The logon script would look something like this:

 

Dim ad,fso,ts,un,cn
Set ad = CreateObject("ADSystemInfo")
Set fso = CreateObject("FileSystemObject")
un = ad.username
cn = ad.computername
Set ts = fso.OpenTextFile("\\server\share\"&cn&"-"&un&".txt",2)
ts.WriteLine Now()
ts.Close()

 

You’ll notice that I actually did write a timestamp inside the file, which could be useful for culling outdated files. We’ll need to pair this with a logoff script to delete the file:

 

Dim ad,fso,ts,un,cn
Set ad = CreateObject("ADSystemInfo")
Set fso = CreateObject("FileSystemObject")
un = ad.username
cn = ad.computername
On Error Resume Next
fso.DeleteFile("\\server\share\"&cn&"-"&un&".txt")

 

You do run a chance of leaving files hanging around when a user doesn’t log off properly; you could write another “maintenance” script that goes through the files, opens them to read the timestamp, and then deletes any that are more than a certain number of days old.

 

Yes, this is kind of a jury-rig, I get that. This is unfortunately the kind of thing you’re stuck with when Windows doesn’t natively do what you want, and when nobody’s produced a more robust add-in that you can buy. You could obviously do a better job of the logon script, or even incorporate this with other logon scripts, especially if you’re using tools that help you produce and manage logon and logoff scripts.

 

A Bit Harder

 

A somewhat sexier way of doing this would be to put the information into a database. Now, you have to bear in mind that you’ll have however many users all hitting this database at once when they log on in the morning, and when they log off at night, so an Access database isn’t going to cut it: You’ll need something like SQL Server. The full version, too, not a Developer or Express edition. And because this really will get a lot of traffic in mornings and afternoons, you’ll need to think carefully before hanging this new database onto an existing SQL Server machine that’s supporting some mission-critical application.

 

The logon script code gets quite a bit more complicated, since you need to connect to a SQL Server database, which means logging in to it, and firing off a SQL query. I’m not going to dive into the complete code, because frankly this approach is much more of a jury-rig than the last one. For examples of connecting to SQL Server from VBScript, refer to https://docs.microsoft.com/en-us/sql/ado/reference/ado-api/ado-code-examples-vbscript (the underlying technology is called ActiveX Data Objects, or ADO, so look for the ADO examples). A decent ADO tutorial can be found at https://www.w3schools.com/. That tutorial is meant for Active Server Pages (ASP), but it works fine from Windows Script Host, too—and that’s what executes logon scripts.

 

Even Harder

 

A more elegant approach—but one I bet almost nobody is willing to do—is to save the information to Active Directory (AD). Typically, that would mean using one of the unused attributes set aside for our use, or adding a custom attribute.

 

In AD, User objects do have a set of unused attributes intended for your own information. The problem is that they’re (a) tied to the User object, and not the Computer, and (b) they’re single-valued attributes. Now, populating the User object with the name of the computer the user is logged on to is actually fine, but a user can easily be logged onto multiple machines, so a single-valued attribute won’t suffice.

 

That leaves us with the second approach, which is to add a custom, multi-valued attribute. We could add it to the Computer class and plan to populate it with user names, or add it to the User class and plan to populate it with computer names. Either way, a logon/logoff script pair is still going to be required to handle the population and clearing of those attributes. Worse yet, this is going to require a schema extension to the directory. My experience is that most organizations would rather take away the CEO’s bonus than extend their AD schema, especially for something fairly trivial like this. It’s elegant, but not practical.

 

Hardest Yet

 

The very hardest—and completely impractical, by the way—approach would be to use the event logs. Event IS 4624 tells us when a user logs on to a workstation (it used event ID 528 in versions of Windows prior to Vista/2008). Unfortunately, you’d potentially have to scan every Security event log on every computer in your entire network to find a given user’s name—and you’d have to enable this level of auditing in the first place. Again, not terribly practical.

 

Still Using WINS?

 

Much as it pains us all, a lot of organizations are still using WINS—and here’s where you get a bonus. The WINS server software tracks user names, mapping them to computer names. https://support.microsoft.com/en-us discusses how to get at that information. Brace yourself: It’s a very old article, related to Windows NT—although WINS is also very old and primarily related to Windows NT, so what else would you expect?

 

Hooray for SysInternals?

 

As usual, another great solution comes from Mark Russinovich and his awesome collection of SysInternals tools, now hosted by Microsoft. Check out https://technet.microsoft.com/en-us/sysinternals/bb897545.aspx for a tool called PsLoggedOn. This has to be targeted at a specific computer, and it’ll tell you who is logged on right then. It won’t track who has logged on in the past, however, and short of a batch scan of every computer in your environment, it can’t answer “which computer is Joe logged into right now?”

 

What PsLoggedOn accomplishes can actually also be done through a WMI query (using the Win32_LogonSession class), although PsLoggedOn does a very neat job and is easy to use. If you did need to scan a whole mess of computers, using WMI in a VBScript or PowerShell script might be easier to code.

 

Answers, But Not Great Ones

 

What we’ve run up against is a capability most administrators wish they had at one time or another—and that Windows, unfortunately, simply doesn’t provide for. That means our solutions are all do-it-yourself, and not terribly pretty. They involve compromises—do you want to extend the AD schema, or pay for a SQL Server license? Perhaps one day Microsoft will extend Windows to automatically log this information in a default AD attribute, and we’ll all be happy. Until then—how do you think you’ll approach this problem?

About the Author