Tuesday, December 06, 2005

WMI: Active Directory Workstation inventory, version 2

I cleaned the original Active Directory workstation inventory script that I mentioned in last week’s post. Basically, I broke the logic out into functions, adding the free disk space check, and the Symantec antivirus definition date check, and made it a bit more readable and usable than the original script.

So how should you go about using this?

Well, first thing you’ll want to do is modify your LDAP path (search for the strLdapPath string) to make this work on your network. I have this commented, and it should be relatively self-explanatory.

The next thing you’ll want to do is consider if you should run the script. If you’ve got a larger environment (say hundreds or thousands of workstations) then it’s going to take quite a while to run. But on a small to mid-sized network, this shouldn’t be too bad.

What do I type to make this work?

So you copy and paste the below code into a text editor, and save it with a .VBS extension. Then, you make sure you’re either logged in as a domain admin, or that you’re executing this script under the context of a domain admin account, you then modify the LDAP path, drop to the command line, and use the following command:

“cscript scriptname.vbs >inventoryreport.txt”

This is going to iterate though all of the computer objects in active directory, ping them to determine if they’re on, and then do some work. Which after hitting all of the objects will leave you with a text file that contains all of your workstation names, their operating system versions, service pack levels, installation dates, computer manufacturer, bios revisions, service tags, processor types, currently logged-on user, model number, amount of ram, daylight savings zone, Symantec antivirus definition dates, and the amount of available free disk space.

Why is it just sitting here doing nothing?

It’s probably doing work. If you’d rather see the results echoed to the screen, then just get rid of the redirection, using the following command line “cscript scriptname.vbs”.

How is this useful?

Well, if you’re an administrator on a small to midsized network, this is a really handy inventory tool that can be used quickly and with very limited editing on your internal and/or customer networks. As a result, it’s cost effective, and you can audit or spot-check your clients. Secondly, if you take a look at a recent post of Susan Bradley, you’ll see this automates a bit of what new SBS net-admins need to for health-check and monitoring type of functions.

I plan to follow-up this post with some more specifics as to how I’m using on the SMB side of the business, and also start thinking about ways I can improve this (think Exchange database sizes) or to make it scale well for larger environments.

'AD Workstation Inventory version 2
'by Nick, wmi.wmi@gmail.com, http://addicted-to-it.blogspot.com
Dim objShell,objDef,objDate,objVer,objRev,objOutFile,objFSO,objNDate,objToday
Const MegabyteConversion = 1048576
Const Warning_Threshold = 1000

'Set the LDAP path to your Active Directory
strLdapPath = "'LDAP://DC=domain,DC=local'"
wscript.echo strLdapPath

On error resume next


Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"

Set objCOmmand.ActiveConnection = objConnection
objCommand.CommandText = _
"Select Name, Location from " & strLdapPath & " Where objectClass='computer'"
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
Set objRecordSet = objCommand.Execute

Do Until objRecordSet.EOF

strComputer = objRecordSet.Fields("Name").Value

Set objShell = CreateObject("WScript.Shell")
strCommand = "%comspec% /c ping -n 3 -w 1000 " & strComputer & ""
Set objExecObject = objShell.Exec(strCommand)

'clears echo replies left in strText from the previous loop

Do While Not objExecObject.StdOut.AtEndOfStream
strText = objExecObject.StdOut.ReadAll()
If Instr(strText, "Reply") > 0 Then

Set objWMIService = GetObject _
("winmgmts:\\" & strComputer & "\root\cimv2")

'The following functions produce the reports

If Err.Number > 0 then
strErrorSystems = strComputer & ", " & strErrorSystems

end if

'flushes error code from the previous loop

UnavailableSystems = strComputer & ", " & UnavailableSystems

End If


Wscript.Echo "The following systems were unavailable: " & UnavailableSystems
Wscript.Echo " "
Wscript.Echo "The following systems were on, but returned an error: " & strErrorSystems

Function BuildHeader
Wscript.Echo "-------------------------------------------------------------"
Wscript.Echo "Computer Name: " & strComputer
Wscript.Echo "-------------------------------------------------------------"
End Function

Function OperatingSystem
Set colItems = objWMIService.ExecQuery ("Select * From Win32_OperatingSystem")
For Each objItem in ColItems
strOs = objItem.Caption
strServPack = objItem.CSDVersion
strInstallDate = objItem.InstallDate
Wscript.Echo "Operating System: " & strOs
Wscript.Echo "Service Pack: " & strServPack
Wscript.Echo "Install Date: " & WMIDateStringToDate(strInstallDate)

End Function

Function BiosQuery
Set colItems2 = objWMIService.ExecQuery("Select * from Win32_BIOS",,48)
For Each objItem in colItems2
strDellTag = objItem.SerialNumber
strManu = objItem.Manufacturer
strBiosName = objitem.Name
WScript.Echo "Manufacturer: " & strManu
Wscript.Echo "BIOS Name: " & strBiosName
Wscript.Echo "Dell Service Tag: " & strDellTag
End Function

Function WMIDateStringToDate(dtmDate)
WScript.Echo dtm:
WMIDateStringToDate = CDate(Mid(dtmDate, 5, 2) & "/" & _
Mid(dtmDate, 7, 2) & "/" & Left(dtmDate, 4) _
& " " & Mid (dtmDate, 9, 2) & ":" & Mid(dtmDate, 11, 2) & ":" & Mid(dtmDate,13, 2))
End Function

Function ProcQuery(shost)
Set colItems4 = objWMIService.ExecQuery("SELECT * FROM Win32_Processor", "WQL", _
wbemFlagReturnImmediately + wbemFlagForwardOnly)
For Each objItem in colitems4
strProcessor = objItem.Name
Wscript.Echo "Processor type: " & strProcessor

End Function

Function DayLightSavingsEffect(shost)
Set colItems3 = objWMIService.ExecQuery("Select * from Win32_ComputerSystem",,48)
For Each objItem in colitems3
strUserName = objItem.Username
strModel = objItem.Model
strRAM = objItem.TotalPhysicalMemory
strTimeZone = (objItem.CurrentTimeZone / 60)
strDayLightSavings = objItem.DaylightInEffect

Wscript.Echo "Current User: " & strUsername
Wscript.Echo "Model: " & strModel
Wscript.Echo "RAM: " & strRAM
Wscript.Echo "Daylight Savings in effect: " & strDayLIghtSavings
Wscript.Echo "Time Zone: " & strTimeZone
strUsername = ""
End Function

Function SavDate(shost)
strCmdRun = "cmd /c"
strRegQ = "reg query "
strRegKey = "\HKLM\SOFTWARE\Symantec\SharedDefs\"
strCmdSw = " /v "
strRegKey2 = "DEFWATCH_10"
Set objExec = objShell.Exec(strCmdRun & strRegQ & "\\" & shost & strRegKey & strCmdSw & strRegKey2)
strExecResults = LCase(objExec.StdOut.ReadAll)

objVer = Right(strExecResults,16)
objRev = Right(objVer,7)
objDate = Left(objVer,8)
objYear = Left(objDate,4)
objMonth = Mid(objDate,5,2)
objDay = Right(objDate,2)
objNDate = CDATE(objMonth &"/"& objDay &"/"& objYear)
Wscript.Echo "Symantec Antivirus Definition Date: " & objNDate &" Rev. "& objRev & " "

End Function

Function DiskSpace
Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
If colLogicalDisk.Count = 0 Then
Wscript.Echo "No drives exist on this computer."
For Each objLogicalDisk in colLogicalDisk
FreeMegaBytes = objLogicalDisk.Freespace / MegabyteConversion

'If objLogicalDisk.DriveType = 3 Then
'If FreeMegaBytes <> 0 Then
FreeMegaBytes_String = Int(FreeMegaBytes) & " MB Free"
Wscript.Echo objLogicalDisk.DeviceID & ", " & FreeMegaBytes_String & ", " &

End if

'End If
'End If
End if
End Function

Function BuildFooter
Wscript.Echo "-------------------------------------------------------------"
End Function


joe said...

Consider changing

"Select Name, Location from " & strLdapPath & " Where objectClass='computer'"


"Select Name, Location from " & strLdapPath & " Where objectCategory='computer'"

Using objectclass as the search key is pretty inefficient unless you have indexed it or are running Longhorn AD.

That could mean the difference between the query taking seconds and minutes or even hours.

Nick said...

Joe - Thanks for the input. I hadn't considered the efficiencies of using objectCategory, and will incorporate your input. What did you think about the functions - did the logic make sense to you, or was it still a little messy?

Heh - I also checked your blog, and saw your "bug vs. design" post... after reading through it, I remember seeing your post a while back on EHLO about the MONAD way of querying and then throwing out what you don't need, and the inefficiencies of doing that for 150k mailboxes. Quite a detailed thread, and it’s funny that I specifically remember seeing it.

Which is just to say, that getting your input is much appreciated.


Nick said...

Updated release is here... http://addicted-to-it.blogspot.com/2006/12/wmi-ad-coit-v23-computer-object.html