Hey, Scripting Guy! Who Are You?
The Microsoft Scripting Guys
Download the code for this article: HeyScriptingGuy2007-082007_08.exe (152KB)
Not too long ago, the Scripting Guy who writes this column was watching a baseball game on TV. When the game was over, he decided to read a magazine but he left the TV on. As he finished the magazine he glanced up at the TV to discover he was watching an old World War II movie. In fact, he glanced up right about the time the actors were running through one of those tried-and-true scenarios: a young American GI is on sentry duty late at night when he hears a noise.
"Who are you?" barked the sentry.
"It's just me, Sgt. Smith," replied a voice.
"Sgt. Smith? There's no Sgt. Smith in this outfit."
"I'm new here; I just transferred in from A Company."
"Oh, really? From A Company? OK, Smith: who won the 1934 World Series?"
"The New York Yankees."
Wrong answer, "Smith"! (Who, as you might expect, was immediately arrested and thrown into the brig.) Any good American, any real American, knows that the St. Louis Cardinals—you know, Dizzy Dean and the Gas House Gang—won the 1934 World Series. Anyone who doesn't know that can only be one thing: a spy.
If anyone reading this month's column did not know that the St. Louis Cardinals won the 1934 World Series, well, all we can say is this: the jig is up; we know you're a spy. We hope you'll have the decency to turn yourself into the nearest office of the FBI. Or at least give them a call; when it comes to spies, the FBI will pick up and deliver.
As the Scripting Guy who writes this column watched, he realized that he'd have a pretty good chance at answering the sentry's second question: who won the 1966 World Series? The Baltimore Orioles. 1960 World Series? Pittsburgh Pirates. 1994 World Series? Ha! A trick question: there was no World Series in 1994.
However, in this day and age, he'd have extreme difficulty answering the first question: who are you? That's not as easy a question today as it might have been back in the 1940s. After all, with Active Directory® alone users can have any number of different identities, including:
- Their first name (givenName).
- Their last name (sn).
- Their display name (displayName).
- Their user principal name (userPrincipalName).
- Their logon name (samAccountName).
- Their distinguished name (distinguishedName).
All of these names identify the same person, and all are names that—depending on the circumstances—are important to know. And that's a problem. Most users know their first name and last name. But if you ask a user, "What's your distinguished name?" only a few will be able to reply, "Why, that's simple! I'm CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com. But all my friends call me CN=Ken.Myer."
As a system administrator or help desk person, you need to know these names as well. But how are you supposed to get this information? Well, one approach would be to suspend your users from meat hooks until they finally tell you their distinguished name. That would probably work, but most human resources departments discourage that sort of thing these days. So you might have to fall back on Plan B: a script. What kind of script? Well, for starters, how about the script in Figure 1?
Figure 1 Retrieving user attributes with ADSystemInfo
On Error Resume Next Set objSysInfo = CreateObject("ADSystemInfo") strUser = objSysInfo.UserName Set objUser = GetObject("LDAP://" & strUser) WScript.Echo "First Name: " & objUser.givenName WScript.Echo "Last Name: " & objUser.sn WScript.Echo "Display Name: " & objUser.displayName WScript.Echo "User Principal Name: " & objUser.userPrincipalName WScript.Echo "SAM Account Name: " & objUser.sAMAccountName WScript.Echo "Distinguished Name: " & objUser.distinguishedName
Boy, if only Sgt. Smith had studied VBScript, huh? This script takes advantage of a little-known (but extremely useful) ADSI object named ADSystemInfo. This is a nifty little object that can return all sorts of information about the user currently logged on to the local computer, as well as about the local computer itself and the domain it belongs to. For example, have a look at Figure 2.
Figure 2 Display all sorts of domain information
On Error Resume Next Set objSysInfo = CreateObject("ADSystemInfo") Wscript.Echo "User name: " & objSysInfo.UserName Wscript.Echo "Computer name: " & objSysInfo.ComputerName Wscript.Echo "Site name: " & objSysInfo.SiteName Wscript.Echo "Domain short name: " & objSysInfo.DomainShortName Wscript.Echo "Domain DNS name: " & objSysInfo.DomainDNSName Wscript.Echo "Forest DNS name: " & objSysInfo.ForestDNSName Wscript.Echo "PDC role owner: " & objSysInfo.PDCRoleOwner Wscript.Echo "Schema role owner: " & objSysInfo.SchemaRoleOwner Wscript.Echo "Domain is in native mode: " & objSysInfo.IsNativeMode
For today, however, the only property we care about is the UserName property. What's so special about UserName? Well, it just happens to correspond to the distinguishedName property. And what's so special about distinguishedName? Well, distinguishedName is similar to a UNC file path: just like a UNC path enables us to uniquely identify a file somewhere on our network, distinguishedName (for example, CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) enables us to uniquely identify a user account in Active Directory. In turn, that enables us to bind to that user account. And once we've made that connection, we can retrieve any information we want about a user—including who he is.
And that's exactly what we do in the first script we showed you. To begin with, we create an instance of the ADSystemInfo object and then assign the value of the UserName property to a variable named strUser:
Set objSysInfo = CreateObject("ADSystemInfo") strUser = objSysInfo.UserName
Notice that we don't have to do much of anything here. For example, we don't have to specify the user name, the user domain, or the OU in which the user account resides; ADSystemInfo does all of that for us. Once we have the user's distinguishedName, we can then bind to his or her user account using this line of code:
Set objUser = GetObject("LDAP://" & strUser)
And, again, once we make the connection we can echo back the values of any of that account's Active Directory attributes. In our sample script, we've simply echoed back some of the user's name properties, but we could just as easily have retrieved the telephone number, office location, e-mail address, and so on.
That's great. All you have to do is hand out this script to all your users and they'll never again have to ask themselves, "Who am I?" (Or, if they do, they'll have a quick and easy way to figure it out.) But what about a related question: who are you? It's one thing to be able to identify the user logged on to the local computer. But how in the world can you determine who's logged on to a remote computer? As you might expect, that's a more difficult nut to crack.
And, sorry, we thought of that, too; unfortunately, you can't simply point our ADSystemInfo script towards a remote computer. The reason you can't is because the ADSystemInfo object can only be created locally. That leaves us with three possibilities:
- Create a logon script that records the name of the logged-on user in some readily-accessible location.
- Use the WMI class Win32_ComputerSystem and the UserName property.
- Do something else.
The first option sounds pretty inviting. As you might have noticed, the ADSystemInfo object can return the distinguished name of the computer (the ComputerName property) as well as the distinguished name of the user. Consider, if you will, the sample script shown in Figure 3.
Figure 3 Logon script
On Error Resume Next Set objSysInfo = CreateObject("ADSystemInfo") strUser = objSysInfo.UserName strComputer = objSysInfo.ComputerName Set objUser = GetObject("LDAP://" & _ strUser) strUserName = objUser.displayName Set objComputer = GetObject("LDAP://" & _ strComputer) objComputer.Description = strUserName objComputer.SetInfo
What are we doing here? To begin with, we're grabbing the values of the UserName and the ComputerName properties and storing them in a pair of variables (strUser and strComputer). We then bind to the user account in Active Directory (just like we did before), retrieve the value of the displayName attribute (just like we did before), and store that value in a variable named strUserName. Pretty straightforward, right?
We are then able to use this line of code to connect to the Active Directory computer account:
Set objComputer = GetObject("LDAP://" & _ strComputer)
After we make that connection, we assign the user's displayName to the computer's Description property and then call the SetInfo method to write that change to Active Directory:
objComputer.Description = strUserName objComputer.SetInfo
Why do we do that? That's easy. Suppose Ken Myer logs on to the computer atl-ws-01. Guess what the value of atl-ws-01's Description property will be? You got it: Ken Myer, the very person currently logged on to the computer. Want to know who's logged on to atl-ws-01? Just check the Description property.
Now, in general, this scenario works pretty well—and works even better if you have a logoff script that clears the Description property each time the user logs off. It's not a foolproof solution, however. Why not? Well, logon scripts don't always run. For example, they typically don't run when people log on using RAS. Likewise, a logon script won't run if a user unplugs his or her network connection, logs on using cached credentials, and then plugs the computer back into the network. And suppose a user just turns off his computer without logging off. That means his logoff script will never run. In that case, Ken Myer will still allegedly be logged on to atl-ws-01 even though the machine isn't even running. In other words, this is a useful technique, but....
So what about option 2, using the Win32_ComputerSystem class? Again, this will often work, but the problem with the Win32_ComputerSystem class is that it doesn't always return the name of the logged-on user, especially for users who don't have administrator rights (and, especially, for computers running Windows® 2000). The script in Figure 4 will probably tell you who's logged on to a computer but, again, no guarantees.
Figure 4 Find out who is logged on with WMI
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery("Select * From Win32_ComputerSystem") For Each objItem in colItems Wscript.Echo objItem.UserName Next
Incidentally, in this case the UserName property gets reported back in the format domain\username. In other words: FABRIKAM\kenmyer.
Oh, we almost forgot: even if a name does come back, that doesn't mean a user is actually logged on to the computer. When Ken Myer logs off atl-ws-01, his name is retained as the value of the UserName property and doesn't get replaced until someone new logs on.
But wait; that doesn't make us spies. Here's another approach we can take. If someone is logged on to a computer, the process Explorer.exe is bound to be running. In general, if Explorer.exe is not running, no one is logged on to the machine. And because Explorer.exe runs under the credentials of the logged-on user, we can almost always determine who's logged on to a computer by using a script similar to the one in Figure 5.
Figure 5 Determining the owner of Explorer.exe
strComputer = "atl-ws-01" Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_Process Where Name = 'explorer.exe'") If colItems.Count = 0 Then Wscript.Echo "No one is logged on to the computer." Else For Each objProcess in colItems objProcess.GetOwner strUser, strDomain Wscript.Echo strDomain & "\" & strUser Next End If
As you can see, in this case we are connecting to the WMI service on a remote machine (atl-ws-01, to be precise). We then use this line of code to retrieve a collection of all the instances of the Win32_Process class that have the name Explorer.exe:
Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_Process Where " & _ "Name = 'explorer.exe'")
Now what? Well, as we noted a moment ago, if Explorer.exe isn't running, the odds are pretty good that no one is logged on to the computer. How do we know whether Explorer.exe is running? One very simple way is to check the value of the collection's Count property. If the Count is equal to 0, we have an empty collection, and the only way we can have an empty collection is if there are no instances of Explorer.exe running on atl-ws-01. If that's the case, we echo back a message saying that no one is logged on to the computer:
Wscript.Echo "No one is logged on " & _ "to the computer."
If the Count isn't equal to 0, we set up a For Each loop to walk through the collection of processes that have the name Explorer.exe (and, yes, we're assuming that our collection will invariably have just one item in it). For each instance of Explorer.exe we then call the GetOwner method to determine whose account Explorer.exe is running under:
objProcess.GetOwner strUser, strDomain
Notice that we pass GetOwner a pair of out parameters: strUser and strDomain. Out parameters are simply variables that we name and supply to a method; the method will then assign values to those out parameters. In this case, strUser will be assigned the logon name of the logged-on user (kenmyer) and strDomain will be assigned the domain name for the logged-on user (FABRIKAM). All we have to do then is echo back the values of these two out parameters:
Wscript.Echo strDomain & "\" & strUser
You know what? That's pretty good. But we can go one step further. When we use the GetOwner method, we get back the logon name (the samAccountName) for the user logged on to the computer. That's good, but, as we noted earlier, users have all sorts of names besides their samAccountName. To truly answer the question "Who are you?" it would be nice to know, say, the user's displayName. But we can't determine these other names using GetOwner, can we?
No, we can't. However, we can take the samAccountName, plug it into an Active Directory search script, and then locate and bind to the user account with that logon name (a task made easier by the fact that samAccountNames must be unique within a domain). And once we bind to the user account we can echo back the values of any Active Directory property, including displayName.
We don't have time to explain the script in Figure 6 in detail; for more information about searching Active Directory, take a look at "Dude, Where's My Printer?". Suffice it to say that this script determines the logon name of the logged-on user, searches Active Directory for the user with that logon name (samAccountName), binds to the user account in question, and then echoes back the user's displayName. And all with one hand tied behind its back!
Figure 6 Binding to the logged on user
strComputer = "atl-ws-01" Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_Process Where Name = 'explorer.exe'") If colItems.Count = 0 Then Wscript.Echo "No one is logged on to the computer." Else For Each objProcess in colItems objProcess.GetOwner strUser,strDomain Next End If Const ADS_SCOPE_SUBTREE = 2 Set objConnection = CreateObject("ADODB.Connection") Set objCommand = CreateObject("ADODB.Command") objConnection.Provider = "ADsDSOObject" objConnection.Open "Active Directory Provider" Set objCommand.ActiveConnection = objConnection objCommand.Properties("Page Size") = 1000 objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE objCommand.CommandText = "SELECT displayName FROM " & _ "'LDAP://DC=wingroup,DC=fabrikam,DC=com' WHERE " & _ "objectCategory='user' " & _ "AND samAccountName = '" & strUser & "'" Set objRecordSet = objCommand.Execute objRecordSet.MoveFirst Do Until objRecordSet.EOF Wscript.Echo objRecordSet.Fields("displayName").Value objRecordSet.MoveNext Loop
So what are our key takeaways from today? (That, by the way, is how Microsoft people talk. If you want to drive the Scripting Guy absolutely crazy, just walk up to him and say something like, "We need to triage the key takeaways, action items, and non-goals for all the stakeholders.") Well, for one thing, we now know how to get information about the user logged onto a computer—either the local machine or a remote computer. More important, we know what to do should we ever slip through a time warp and find ourselves back in World War II: make sure you have a copy of this column (so you can answer the question "Who are you?"), and—whatever you do—always carry around a list of World Series winners. After all, you never know when someone is going to ask you, "Who won the 1903 World Series?"
Note: The Boston Red Sox. This, by the way, was the very first World Series. Now, who do you suppose won the 1904 World Series? What do you mean, you don't know? Could you excuse us for a second; we need to go make a phone call....
The Microsoft Scripting Guys work for—well, are employed by—Microsoft. When not playing/coaching/watching baseball (and various other activities) they run the TechNet Script Center. Check it out at www.scriptingguys.com.
© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.