Tuesday, October 30, 2007

using WMI and VBScript to audit your workstations

Auditing hardware and software for all machines in your domain can be time consuming. For small organizations, it may be a bit manageable provided that you already have a checklist of items that you need to look at for auditing purposes. CPU, disk, RAM, OS, service pack versions, etc. are just a few of those things you would like to take into account when doing auditing and inventory. For larger organizations, this could be a big challenge. Since I work for an organization that provides IT services for clients, we are required to maintain server information for auditing and inventory purposes. Imagine me doing this for almost 200+ Windows 2000 and Windows 2003 servers in multiple domains, not to mention Windows XP workstations. Being a lazy guy as I am, I wrote a script to simply automate this task. It uses VBScript and WMI to retrieve hardware, software and operating system information from computers in the domain. This requires administrative rights on the machine where this script is executed. It generates a text file (with filename servername_yyyymmdd_Audit.txt) which contains the information retrieved by the script. This is my list of information, you can always generate a lot more information by referring to the WMI SDK. Simply replace the value in the strComputer variable to the name/IP address of the computer you wish to audit

On Error Resume Next

Const HKEY_LOCAL_MACHINE = &H80000002

'change this value to the IP address or hostname of the machine you need to audit
strIPvalue = "."

CALL GenerateReport(strIPvalue)

WScript.Echo "Inventory Complete "

'SUB-ROUTINE GenerateReport
SUB GenerateReport(strIPvalue)

'Script to change a filename using timestamps
strPath = "C:\" 'Change the path to appropriate value
strMonth = DatePart("m", Now())
strDay = DatePart("d",Now())

if Len(strMonth)=1 then
strMonth = "0" & strMonth
strMonth = strMonth
end if

if Len(strDay)=1 then
strDay = "0" & strDay
strDay = strDay
end if

strFileName = DatePart("yyyy",Now()) & strMonth & strDay
strFileName = Replace(strFileName,":","")

'Variable Declarations
Const ForAppending = 8

'Main Body
On Error Resume Next

strComputer = strIPvalue
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Dim strIE
Set objWMIService2 = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2\Applications\MicrosoftIE")
Set colIESettings = objWMIService2.ExecQuery("Select * from MicrosoftIE_Summary")
For Each strIESetting in colIESettings
strIE= " INTERNET EXPLORER: " & strIESetting.Name & " v" & strIESetting.Version & VBCRLF

'Get Operation System & Processor Information
Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor",,48)
For Each objItem in colItems
CompName = objItem.SystemName

Set objFSO = CreateObject("Scripting.FileSystemObject")
if objFSO.FileExists(strPath & CompName & "_" & strFileName & "_Audit.txt") then
end if

'Set the file location to collect the data
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile(strPath & CompName & "_" & strFileName & "_Audit.txt", ForAppending, True)

objTextFile.Write "================================================================" & VBCRLF & VBCRLF
objTextFile.Write " DATE: " & FormatDateTime(Now(),1) & " " & VBCRLF
objTextFile.Write " TIME: " & FormatDateTime(Now(),3) & " " & VBCRLF & VBCRLF
objTextFile.Write "================================================================" & VBCRLF & VBCRLF & VBCRLF & VBCRLF & VBCRLF

objTextFile.Write "COMPUTER" & VBCRLF
'Get OPERATING SYSTEM & Processor Information
objTextFile.Write " COMPUTER NAME: " & CompName & VBCRLF

Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor",,48)
For Each objItem in colItems
objTextFile.Write " PROCESSOR: " & objItem.Name & VBCRLF

Set colProcs = objWMIService.ExecQuery("Select * from Win32_ComputerSystem")

For Each objItem in colProcs
objTextFile.Write " NUMBER OF PROCESSORS: " & objItem.NumberOfProcessors & VBCRLF & VBCRLF

'Get DOMAIN NAME information
Set colItems = objWMIService.ExecQuery("Select * from Win32_NTDomain")

For Each objItem in colItems
objTextFile.Write " DOMAIN NAME: " & objItem.DomainName & VBCRLF

'Get OS Information
Set colSettings = objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colSettings
objTextFile.Write " OPERATING SYSTEM: " & objOperatingSystem.Name & VBCRLF
objTextFile.Write " VERSION: " & objOperatingSystem.Version & VBCRLF
objTextFile.Write " SERVICE PACK: " & objOperatingSystem.ServicePackMajorVersion & "." & objOperatingSystem.ServicePackMinorVersion & VBCRLF
objTextFile.Write strIE & VBCRLF & VBCRLF & VBCRLF & VBCRLF

objTextFile.Write "MOTHERBOARD" & VBCRLF

'Get Main Board Information
Set colItems = objWMIService.ExecQuery("Select * from Win32_BaseBoard",,48)
For Each objItem in colItems
objTextFile.Write " MAINBOARD MANUFACTURER: " & objItem.Manufacturer & VBCRLF
objTextFile.Write " MAINBOARD PRODUCT: " & objItem.Product & VBCRLF

'Get BIOS Information
Set colItems = objWMIService.ExecQuery("Select * from Win32_BIOS",,48)
For Each objItem in colItems
objTextFile.Write " BIOS MANUFACTURER: " & objItem.Manufacturer & VBCRLF
objTextFile.Write " BIOS VERSION: " & objItem.Version & VBCRLF & VBCRLF & VBCRLF & VBCRLF & VBCRLF

objTextFile.Write "MEMORY" & VBCRLF

'Get Total Physical memory
Set colSettings = objWMIService.ExecQuery("Select * from Win32_ComputerSystem")
For Each objComputer in colSettings
objTextFile.Write " TOTAL PHYSICAL RAM: " & Round((objComputer.TotalPhysicalMemory/1000000000),4) & " GB" & VBCRLF


'Get Logical Disk Size and Partition Information
Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk Where DriveType = 3")
For Each objDisk in colDisks
intFreeSpace = objDisk.FreeSpace
intTotalSpace = objDisk.Size
pctFreeSpace = intFreeSpace / intTotalSpace
objTextFile.Write " DISK " & objDisk.DeviceID & " (" & objDisk.FileSystem & ") " & Round((objDisk.Size/1000000000),4) & " GB ("& Round((intFreeSpace/1000000000)*1.024,4) & " GB Free Space)" & VBCRLF


'Get NETWORK ADAPTERS information
Dim strIP, strSubnet, strDescription

Set colNicConfigs = objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")

For Each objNicConfig In colNicConfigs
'Assign description values to variable

For Each strIPAddress In objNicConfig.IPAddress
'Assign IP Address to variable

For Each strIPSubnet In objNicConfig.IPSubnet
'Assign Subnet to variable
strSubnet = strIPSubnet

objTextFile.Write " NETWORK ADAPTER: " & strDescription & VBCRLF
objTextFile.Write " IP ADDRESS: " & strIP & VBCRLF
objTextFile.Write " SUBNET MASK: " & strSubnet & VBCRLF & VBCRLF



Set colNicConfigs =NOTHING



Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")

strKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
objReg.EnumKey HKEY_LOCAL_MACHINE, strKeyPath, arrSubKeys

For Each subkey In arrSubKeys
strSubKeyPath = strKeyPath & "\" & subkey

strString = "DisplayName"
objReg.GetStringValue HKEY_LOCAL_MACHINE, strSubKeyPath, strString, strDisplayName

strString = "DisplayVersion"
objReg.GetStringValue HKEY_LOCAL_MACHINE, strSubKeyPath, strString, strDisplayVersion

If strDisplayName <> "" And strDisplayVersion <> "" Then
objTextFile.Write " " & strDisplayName & " " & strDisplayVersion & VBCRLF
End If


'Close text file after writing logs

objTextFile.Write VbCrLf

'Clean Up

SET colSettings=NOTHING


Function HostOnline(strComputername)

Set sTempFolder = objFso.GetSpecialFolder(TEMPFOLDER)
sTempFile = objFso.GetTempName
sTempFile = sTempFolder & "\" & sTempFile

objShell.Run "cmd /c ping -n 2 -l 8 " & strComputername & ">" & sTempFile,0,True

Set oFile = objFso.GetFile(sTempFile)
set oTS = oFile.OpenAsTextStream(ForReading)
do while oTS.AtEndOfStream <> True
sReturn = oTS.ReadLine
if instr(sReturn, "Reply")>0 then
HostOnline = True
Exit Do
End If

End Function

If you are dealing with an organization with more than 50 computers and servers, it would still be tiresome to manually execute this script on each machine/server. A better way to do it is to have an Excel spreadsheet that contains the machine names/IP addresses of all the computers in your domain. read through the list and generate the text files based on that list. In my next blog entry, I will start with reading an Excel spreadsheet using VBScript and continue on to incorporate this script.

Making a fool of your applications - File System

An understanding of how your applications work will make it a bit easy for you to fix problems when they arise. One common scenario I have seen in most applications is the concept of application logging - creating logs using either the old-fashioned log files or storing the data in a database. Logging helps application developers troubleshoot application-related problems in production environments when the need arise. I've had my shares of creating application logs in the past where I use text files to store activities happening in the application. There's a downside in using this concept. One, you are introducing additional overhead in your application since you have to do an additional step, not to mention the I/O necessary to write the log in the file system or the database. Another downside is log maintenance. If you do not maintain the logs, it wouldn't take long before they fill up your disk, causing your application to fail. There are ways to work around this. You can toggle logging and only turn it on when needed. Another approach is to create a default log maintenance procedure in the application itself like truncating logs or deleting log files older than a specific date. Maintenance will be a terrible headache if not included as a part of the logging mechanism.

I have had the opportunity to deal with such a case. I had an encounter with an application which logs every transaction by creating XML files. XML is a great way to store data. But what I have seen for the past few years is that the use of XML has been misunderstood as something to replace a relational database. This misunderstanding of its purpose has caused a lot of problems particularly when it comes to performance. You see, in order for you to read the data in the XML file, you have to load it in memory before you can even do those methods as parsing using XPath and XQuery. Imagine doing this to load a million XML files. My problem was to delete the log files stored as XML. I couldn't just delete them since they have increased in number that simply running Windows Explorer has caused my session to hang. My next step is to delete the folder containing the log files. But it's not as easy as that. The folder containing the logs is being locked by the application which is typical of all applications creating logs. To work around this problem, I had to find out what application or service is, stop it the service, rename the folder containing the logs, create a new folder with the same name as that of the old log folder, restart the service and, then, delete the old folder. The application will still see the logs folder except that now it's a new folder but with the same name. This made sure that I can still do maintenance by deleting the logs folder while making sure that application downtime is kept at a minimum. Bottomline still remains, we people are indeed smarter than these machines.