What’s Your Computer Name?
Extend the AD schema to store and find useful information
July 28, 2009
PROBLEM: Determining a computer's name can be difficult if you have to rely on user input.
SOLUTION: Add a new property to the built-in Active Directory User object to store the last computer the user logged on to.
REQUIREMENTS:
1. Windows 2003 domain (native, or mixed Windows 2000 mode)
2. Windows XP clients
3. OIDgen.vbs
4. MMC Active Directory Schema snap-in
5. ADSIEdit.msc
6. Text editor (I recommend Notepad++ because it performs keyword highlighting for Visual Basic—notepad-plus.sourceforge.net/uk/site.htm)
7. Logon scripts UpdateITUserInfo.vbs and DisplayITUserInfo.vbs
DIFFICULTY: 4 out of 5
SOLUTION STEPS:
1. Add a property (e.g., contosocom-LastComputer) to the built-in User object in Active Directory to store the last computer the user logged on to.
2. Add a logon script that updates the property every time the user logs on.
3. Grant all users permission to update the property.
4. Add a context menu to the User object that runs a script to retrieve and display the property.
During all my years of IT support, I can't tell you how many times I've asked a user, "What's your computer name?" only to be greeted with dumbfounded silence. And obtaining the answer to this question can be like pulling teeth. Do you tell the user to right-click My Computer, select Properties, then click the Computer Name tab? Not everyone keeps the My Computer icon on the desktop—and I've talked to many users who don't know what "right-click" means. What about typing ECHO %COMPUTERNAME% at a command prompt? This approach often isn't worth the effort—just getting a user to type cmd in the Run box can be frustrating.
Knowing a user's computer name is important for checking event logs, copying files to the C$ share, or examining the registry. And in a large organization, the faster you can obtain this information—without requiring user input—the better. I figured Active Directory (AD) would be the perfect place to store such information: You could obtain a computer name anytime without having to ask the user.
Of course, extending AD with custom attributes isn't high on any administrator's list of things to do, because you can't delete any class or attribute that you create. However, you can use the Microsoft Management Console (MMC) Active Directory Schema snap-in to disable classes and attributes. Once you disable a class or attribute, it's like it never existed.
In this article I explain how to create custom AD attributes that will let you find useful information. You'll be able to search AD for computer names and return the name of the user who last logged on. In addition, you'll be able to right-click a username in the MMC Active Directory Users and Computers snap-in and open a Computer Management snap-in for the computer the user last logged on to.
Generating the Base OID
Before you can create custom attributes or classes, you need your own Object Identifier (OID). An OID is the unique identifier that will be the base ID of all your attributes and classes. The reason you need your own OID is to make sure no conflicts occur when software packages try to extend the schema. For example, Microsoft Exchange adds numerous classes and objects to AD's schema, each with their own OIDs. If you try to use an OID that Exchange uses, you'll run into problems.
To obtain an OID, you need to run oidgen.vbs, which you can download from microsoft.com/technet/scriptcenter/scripts/ad/domains/addmvb03.mspx. The easiest approach is to run the .vbs file from the command prompt, then redirect the output to a text file. For illustration purposes, let's save oidgen.vbs to a directory we create in the root of the C drive, called OID. Next, open a command prompt and enter cdOID to go to the OID directory. Then enter
cscript oidgen.vbs >> OID.txt
to place a file called OID.txt in the directory. (For these steps to work, you must be in the directory where you saved the oidgen.vbs file.) Running oidgen.vbs generates the following base OID, which we'll use throughout the rest of the article: 1.2.840.113556.1.8000.2554.14285.12046.9006.19529.38451.15381839.12108022.
All classes will start with this number, ending in .1.n, where n is the class number. So the first class created will be 1.2.840.113556.1.8000.2554.14285.12046.9006.19529.38451.15381839.12108022.1.1.
All attributes will start with the base OID and end in .2.n, where n is the attribute number. The first attribute will be 1.2.840.113556.1.8000.2554.14285.12046.9006.19529.38451.15381839.12108022.2.1.
As a best practice, all the names of the classes and attributes you create should start with a prefix that contains your domain name. For example, if your domain name is Contoso.com, your prefix would be contosocom. Using your domain name as a prefix creates uniqueness across all your classes and attributes. In addition, your created classes and attributes will be easier to find amongst the hundreds of default ones. For more best practices, see the oidgen.vbs documentation.
Creating Attributes and Classes
Once you have a base OID, you can use the Active Directory Schema snap-in to create attributes and classes. The first time you use the Active Directory Schema snap-in, you have to register the file schmmgmt.dll by using the command
regsvr32 schmmgmt.dll
You can then create a console for browsing the AD schema. (For more information about installing the Active Directory Schema snap-in, see technet.microsoft.com/en-us/library/cc755885(WS.10).aspx.)
These schema changes must be performed from the server holding the Schema Master Flexible Single-Master Operation (FSMO) role. To determine which server is the Schema Master, open the Active Directory Schema snap-in, right-click Active Directory Schema, and select Operation Masters. The window that opens will show the current Schema Master.
Creating an attribute. To create the attribute that will be updated when the logon script runs, perform the following steps:
1. Log on to the server holding the Schema Master FSMO role.
2. Open the Active Directory Schema snap-in.
3. Right-click the Attributes folder and select Create Attribute. You'll receive a warning saying that you're permanently modifying AD—which is true. Once you create an attribute or class, you can only disable but not delete it.
4. Click Continue.
5. Enter the following information (replace the information shown here with the appropriate information for your environment, including the OID you obtained from running oidgen.vbs):
a. Common name: contosocom-LastComputer
b. LDAP display name: contosocom-LastComputer
c. Unique X500 OID: 1.2.840.113556.1.8000.2554.14285.12046.9006.19529.38451.15381839.12108022.2.1
d. Description: Last-Computer-User-Logged-On
e. Syntax: case-insensitive string
f. No minimum or maximum values are required.
6. Click OK.
You might have to refresh the list to see the attribute you just created. Double-click the attribute again and ensure that the following settings are enabled:
Allow this attribute to be shown in Advanced View (lets you run searches on the attribute)
Attribute active (makes the attribute active)
Replicate this attribute to the Global Catalog (lets the attribute be replicated to the other Global Catalogs)
Click OK.
Creating a class. To create the class that will be associated with the built-in user class, perform the following steps:
1. Right-click the Classes folder and select Create Class.
2. Click Continue.
3. Enter the following information (again, replacing with your own information):
a. Common name: contosocom-ITUserInfo
b. LDAP display name: contosocom-ITUserInfo
c. Unique X500 OID: 1.2.840.113556.1.8000.2554.14285.12046.9006.19529.38451.15381839.12108022.1.1
d. Description: Class-For-Storing-IT-Info
e. Class type: Auxiliary
4. Click Next.
5. Next to Optional Attributes, click Add.
6. Select contosocom-LastComputer.
7. Click OK.
8. Click Finish.
You might have to refresh the list to see the class you just created. Double-click the class again and ensure that Class is active is selected.
Associating a new class with the built-in class. To associate the class you just created with the built-in user class, perform the following steps from the Active Directory Schema snap-in:
1. In the Classes folder, double-click the user class.
2. Select the Relationship tab.
3. Next to Auxiliary Classes, click Add Class.
4. Select contosocom-ITUserInfo.
5. Click OK.
Using a Logon Script to Update User Attributes
To update the user object in Active Directory with the name of the computer the user is logging on to, run the script UpdateUserInfo.vbs that Listing 1 contains. This script takes the username, finds the Active Directory path for the user object, and updates the attribute (e.g., contosocom-LastComputer) for the user logging on. At Callout A, I define a constant for the LastComputer attribute and one for the organizational unit (OU) path. You'll need to modify the script for your environment.
To speed up the script, you can define the OU_PATH constant as the root of the OU where you placed your users. For example, if you have an OU named Accounts and you moved all your users to that OU, the OU_PATH would be LDAP://OU=Accounts,DC=contoso,DC=com. Just replace contoso and com with the naming convention used on your domain. So, if your domain name is widgetrepair.local, your LDAP path would be LDAP://OU=Accounts,DC=widgetrepair,DC=local. Using the base path requires the script to search all of AD for the user.
The code at Callout B defines a variable called bDebug and sets it to false. When bDebug is set to false, the user won't see any error messages. If you encounter problems with the script, try copying it locally, changing bDebug to true, then running the script. Never set bDebug to true in your production script unless you want every user to see all the error messages.
Next, the script finds the LDAP path to the user object in AD. Most administrators use the first initial last name convention for usernames (some also use the middle initial); so Joe Smith's username would be jsmith. However, in AD the user's LDAP path would be LDAP://CN=Joe Smith, DC=CONTOSO, DC=COM. If you tried to use LDAP://CN=JSmith, DC= CONTOSO, DC=COM as the LDAP path, you'd get an error. So how do you obtain the user's full name if you know only the user's logon name?
The GetLDAPPathFromUserName function obtains this information. Following our example with user Joe Smith, sUserName will equal jsmith at Callout C in Listing 1. GetLDAPPathFromUserName uses this information to run a search on AD for the LDAP path where the sAMAccountName (a fancy AD term for the username) is equal to jsmith.
The code at Callout D checks to see whether sLDAPPath is equal to nothing. If it isn't, the code calls UpdateITUserInfo, which adds the computer name to the user object.
To run UpdateITUserInfo.vbs at logon, put the VBS file in the NETLOGON share on the DC; then, in the Active Directory Users and Computers snap-in, enter UpdateITUserInfo.vbs for the logon script on the Profile tab of the user's properties. Alternatively, deploy a Group Policy setting for the script. You can also add the script to an existing batch language script by using the command
Cscript \SERVERIT_Scripts$UpdateITUserInfo.vbs
(Change the server name and share name for your environment.)
Configuring the Context Menu and Search and Display Options
Next, you need to create the search and display options and the user context menu item that appears in the Active Directory Users and Computers snap-in. Before you create the display options, you should create a shared directory in which to store the DisplayITUserInfo.vbs script, which Listing 2 contains. In this case, I created a directory called IT_Scripts$, on the computer SERVER.
Download the Windows Support Tools for Microsoft Windows 2003 (microsoft.com/downloads/details.aspx?FamilyId=6EC50B78-8BE1-4E81-B3BE-4E7AC4F0912D). Install the support tools on a domain controller (DC) and open ADSIEdit.msc. (The default location of ADSIEdit.msc is C:Program FilesSupport Tools.)
Click to expand the Configuration node; CN=Configuration,DC=contoso,DC=com; and Display Specifiers. Select CN=409, and double-click CN=user-display. Double-click adminContextMenu to display new items in the context menu when you right-click a user. In the Value to add section, enter the next number in the sequence, the text to display when you right-click a user, and the script to run when you click the menu item. For example:
6, IT User Info...,\SERVERIT_Scripts$DisplayITUserInfo.vbs
Click Add, OK. This action adds a menu item called IT User Info when you right-click a user in the Active Directory Users and Computers snap-in. When you click this item, the script DisplayITUserInfo.vbs will run.
The script DisplayITUserInfo.vbs takes the user's LDAP path and attempts to retrieve the Last Computer attribute. At Callout A in Listing 2, the variable Args is set to the built-in method Wscript.Arguments. Active Directory Users and Computers automatically passes the object's LDAP path (in this case, a user object) to the script. The script will display the attribute if it can find it. In addition, the script gives you the option of opening the Computer Management snap-in for the system. (See Callout B.)
Next, add a friendly name that you can use to search for and display the attribute. With the CN=user-Display properties still open, scroll down to attributeDisplayNames and click Edit. In the Value to add section, enter the class name and display name. For example:
contosocom-LastComputer, Last Computer
Click Add, OK.
Finally, you need to grant all users read/write access to the contosocom-LastComputer attribute. Open the Active Directory Users and Computers snap-in, click View, and select Advanced Features. Right-click the domain name. (Alternatively, you can go farther down the tree; for example, to the base OU for the users.) Select Properties. On the Security tab, click Advanced, Add. Specify Domain Users or Authenticated Users and click OK. Next, click the Properties tab and select User objects in the Apply onto section. Scroll to find Last Computer or contosocom-LastComputer. Select the Read and Write options. Click OK.
After all the pieces are configured, you can just sit back and wait for your users to log on. To determine whether a user has run the script, right-click the user in Active Directory Users and Computers and select IT Info. Depending on the size of your domain and how often your users log off, it might take several days or weeks for all your users to log off and back on again.
Note that when you first run DisplayITUserInfo.vbs, no users will show any values in the LastComputer attribute. After everyone has a chance to log on with the new logon script, you'll see some data. Also note that the script doesn't differentiate between computers that are on or off; if a computer is turned off, the Computer Management snap-in will generate an error.
You can also add a column in Active Directory Users and Computers for the Last Computer property by modifying the CN=organizationalUnit-Display object using ADSIEdit.msc. Click to expand the Configuration node; CN=Configuration,DC=contoso,DC=com; and Display Specifiers. Select CN=409, and double-click CN=organizationalUnit-Display. Scroll down to find extraColumns, and click Edit. In the Value to add box, enter contosocom-LastComputer,Last Computer,0,200,0 and click OK. See msdn.microsoft.com/en-us/library/ms677291(VS.85).aspx for more information about adding columns to Active Directory Users and Computers.
When you use Active Directory Users and Computers to view users, you can now see the last computer a user logged on to (if the user ran the logon script). Select the OU where you placed your users, click Add/Remove Columns from the View menu, select Last Computer in the right pane, click Add, and click OK. Keep in mind that this method won't work if you keep your users in the default Users container.
Worth the Effort
You might be wondering whether it's even worth the hassle to go to all this trouble. Well let me tell you: Being able to quickly search the AD schema for computer names recently saved me a huge amount of time when my company's URL filtering software reported that a particular IP address had made thousands of requests to one website within an hour. I knew a user couldn't be manually doing this, so I immediately suspected a virus. Within minutes, I was able to resolve the IP address to a computer name, then the computer name to a username, and send a technician out to remove the virus. It's useful for security, inventory tracking, and Help desk support—and certainly worth the effort.
Listing 1: UpdateITUserInfo.vbs
option Explicit
'******* BEGIN CALLOUT A *******
const ATTRIBUTE_NAME = "contosocom-LastComputer" ‘***Modify for your domain
const OU_PATH = "LDAP://DC=contoso,DC=com" ‘***Modify for you domain
'******* END CALLOUT A *******
Dim sUserName
Dim sLDAPPath
'******* BEGIN CALLOUT B *******
Dim bDebug
bDebug = false
'******* END CALLOUT B *******
'******* BEGIN CALLOUT C *******
sUserName = WScript.CreateObject("WScript.Network").UserName
sLDAPPath = GetLDAPPathFromUserName(sUserName)
'******* END CALLOUT C *******
'******* BEGIN CALLOUT D *******
if sLDAPPath <> vbNullString then
UpdateITUserInfo(sLDAPPath)
end if
'******* END CALLOUT D *******
function GetLDAPPathFromUserName(sUsername)
On Error Resume Next
Dim con
dim Com
dim rs
dim sADSPath
Set con = CreateObject("ADODB.Connection")
con.provider ="ADsDSOObject"
con.open "Active Directory Provider"
Set Com = CreateObject("ADODB.Command")
Set Com.ActiveConnection = con
Com.CommandText ="select adspath from '" & OU_PATH & "' WHERE objectCategory='person' AND sAMAccountName='" & sUsername & "' ORDER BY name"
Set rs = Com.Execute
Do While Not rs.EOF
sADSPath = rs("adspath")
rs.MoveNext
Loop
rs.Close
con.Close
Set rs = Nothing
Set con = Nothing
set Com = Nothing
if err.number <> 0 then
sADSPath = vbNullString
if bDebug then wscript.echo "Error Retrieving User LDAP path: #" & vbcrlf & err.number & " = " & err.description
err.clear
end if
if bDebug then wscript.echo "ADS Path = " & sADSPath
GetLDAPPathFromUserName = sADSPath
end function
Sub UpdateITUserInfo(sUserLDAPPath)
On Error Resume Next
dim oNet
dim oUser
set oUser = GetObject(sUserLDAPPath)
Set oNet = WScript.CreateObject("WScript.Network")
if LCase(oUser.Class) = "user" then
oUser.Put ATTRIBUTE_NAME, oNet.ComputerName
oUser.SetInfo
if err.number <> 0 then
if bDebug then wscript.echo "Error updating IT Info: " & vbcrlf & err.number & " = " & err.description
err.clear
end if
end if