Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How can I run scripts against multiple computers and gather information?

Scripting for the Enterprise

How can I run scripts against multiple computers and gather information?

by  markdmac  Posted    (Edited  )
Making Scripts Enterprise Ready
By Mark D. MacLachlan

If you are new to scripting and looking for some great reference material and sample scripts you will most definitely want to visit http://www.microsoft.com/technet/scriptcenter/default.mspx and download copies of the WMI Scriptomatic tool and the Portable Script center. Each is filled with valuable examples that can help you handle nearly any task. The problem with these resources is that they only provide examples for a single computer. Lacking is a good explanation on how to convert these scripts to make them ôEnterprise Ready.ö

Well, I am here to help you in that endeavor. Converting scripts to run against multiple computers is not as hard as you might think. I have found there are three methods that seem to cover most circumstances, reading directly from Active Directory, reading from a text file and reading from an Excel spreadsheet. Each has a primary goal of identifying what computers to run the script against.

Reading From Active Directory
Reading directly from Active Directory is the most straight forward approach. Using vbscript we can connect to the domain and directly enumerate a full list of computer objects. The main problem with this is that it becomes more difficult to exclude certain computers (such as servers and admin workstations) from the list.

If you need to script against all computer objects in your domain, then the following code will do the trick.

Code:
On Error Resume Next

Dim PCQuery, objConnection, objCommand, objRecordSet
Dim oRootDSE, strDNC
[green]
'First get domain information[/green]
Set oRootDSE = GetObject("LDAP://rootDSE")
strDNC = oRootDSE.get("defaultNamingContext")[green]
' other categories = computer, user, printqueue, group[/green]
PCQuery = "<LDAP://" & strDNC & _
	 ">;(objectCategory=computer)" & _
       ";distinguishedName,name;subtree"

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Open "Provider=ADsDSOObject;"
objCommand.ActiveConnection = objConnection
objCommand.CommandText = PCQuery
Set objRecordSet = objCommand.Execute

Do Until objRecordSet.EOF[green]
    'assign the computer name and distinguished path to variables[/green]
    strComputer = objRecordSet.Fields("name")
    strComputerDN = objRecordSet.Fields("distinguishedName")[green]
    'Put the worker process of your code in here[/green][red]
    '*******************************************
        
    
    '*******************************************[/red]
    objrecordset.MoveNext
Loop

objConnection.Close

We can do some tweaking to the above code to limit our results to a specific OU and sub OUs. Add the OU information to this line:
Code:
PCQuery = "<LDAP://" & strDNC & _
Like this:
Code:
PCQuery = "<LDAP://[blue]CN=Computers,[/blue]" & strDNC & _

[red]Note the comma at the end of CN=Computers. [/red]

OK, so the above code is a handy little bit of code. You can drop your code into the indicated worker section and you are good to go.

Let's make one enhancement to the above script. Let's take a look at making sure that a PC is on line before we try to have our worker process do something to it.

Within the above [red]red[/red] section, we will add code that will take the current value of strComputer and verify if the machine is online. There are two primary ways we could accomplish this task.

1. Try to establish a WMI connection, wait for timeout failure and record the result.

2. Ping the machine and check ping status.

Of the two methods, the Ping Status is the fastest and provides the most information.

OK, so within that [red]red[/red] section add the following bit of code.

Code:
strPingStatus = PingStatus(strComputer)
If strPingStatus = "Success" Then[green]
    'Ping was good, do some work![/green][red]
    '*****************************************
    'Worker section goes in here now.
    '*****************************************[/red]
Else[green]
    'Machine unreachable.  Add code to log the name here[/green]
    Wscript.Echo "Failure pinging " & strComputer & ": " & strPingStatus
End If

OK, so the above calls a function to check the ping status. Add the following function under the line objConnection.Close

Code:
Function PingStatus(strComputer)

    On Error Resume Next
    
    Set objWMIService = GetObject("winmgmts:" _
      & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
    Set colPings = objWMIService.ExecQuery _
      ("SELECT * FROM Win32_PingStatus WHERE Address = '" & strComputer & "'")
    For Each objPing in colPings
        Select Case objPing.StatusCode
            Case 0 PingStatus = "Success"
            Case 11001 PingStatus = "Status code 11001 - Buffer Too Small"
            Case 11002 PingStatus = "Status code 11002 - Destination Net Unreachable"
            Case 11003 PingStatus = "Status code 11003 - Destination Host Unreachable"
            Case 11004 PingStatus = _
              "Status code 11004 - Destination Protocol Unreachable"
            Case 11005 PingStatus = "Status code 11005 - Destination Port Unreachable"
            Case 11006 PingStatus = "Status code 11006 - No Resources"
            Case 11007 PingStatus = "Status code 11007 - Bad Option"
            Case 11008 PingStatus = "Status code 11008 - Hardware Error"
            Case 11009 PingStatus = "Status code 11009 - Packet Too Big"
            Case 11010 PingStatus = "Status code 11010 - Request Timed Out"
            Case 11011 PingStatus = "Status code 11011 - Bad Request"
            Case 11012 PingStatus = "Status code 11012 - Bad Route"
            Case 11013 PingStatus = "Status code 11013 - TimeToLive Expired Transit"
            Case 11014 PingStatus = _
              "Status code 11014 - TimeToLive Expired Reassembly"
            Case 11015 PingStatus = "Status code 11015 - Parameter Problem"
            Case 11016 PingStatus = "Status code 11016 - Source Quench"
            Case 11017 PingStatus = "Status code 11017 - Option Too Big"
            Case 11018 PingStatus = "Status code 11018 - Bad Destination"
            Case 11032 PingStatus = "Status code 11032 - Negotiating IPSEC"
            Case 11050 PingStatus = "Status code 11050 - General Failure"
            Case Else PingStatus = "Status code " & objPing.StatusCode & _
               " - Unable to determine cause of failure."
        End Select
    Next

End Function

That's it. The script will now check to see if a machine is alive and will only take action if it can be reached. In this example I am just echoing out the reason for failure but you could easily write that error back to a text file so you have a list of machines that were not accepting connections. This list will let you use the below methods to retry the machines at a later date.

OK, now let's take a look at running code against a smaller subset of machines and control our targets with a text file. Note that you can use the above ping check in the below examples too by placing the code within the loop that iterates through machine names.

Reading From a Text File

Before we dive into the code, let's define what this file will look like. You will want to create a standard text file with one workstation name per line. For standardization I like to call this wslist.txt. If you always use this name, you will be able to quickly cut and paste the code I am about to show you into any script that might utilize a list of computers. Creating reusable code is essential to efficient scripting.

I'll show you a simple sample WSLIST file in a moment. If you want to grab some real data for your network you can do it with this script. Just save this to a text file with a .VBS extension and double click the file to run it.

Code:
[b]
'==========================================================================
'
' NAME: <EnumerateDomainComputers.vbs>
'
' AUTHOR: Mark D. MacLachlan , The Spider's Parlor
' URL: http://www.thespidersparlor.com
' DATE  : 5/20/2004
'
' COMMENT: generates a list of domain computers
' MODIFICATIONS: Added support to automatically find the Domain NetBIOS Name
'==========================================================================

Dim objIADsContainer          ' ActiveDs.IADsDomain -  '   Container object
Dim objIADsComputer           ' ActiveDs.IADsComputer 
Dim Partition, Partitions
Set Partitions = GetObject("LDAP://CN=Partitions,CN=Configuration," & _
GetObject("LDAP://RootDSE").Get("DefaultNamingContext"))
On Error Resume Next
For Each Partition In Partitions
strDomain = Partition.Get("nETBIOSName")
If Err.Number = 0 then Exit For
Next
Set Partitions = Nothing

   
' connect to the computer.
Set objIADsContainer = GetObject("WinNT://" & strDomain)

' set the filter to retrieve only objects of class Computer
objIADsContainer.Filter = Array("Computer")

   
For Each objIADsComputer In objIADsContainer
      report = report & objIADsComputer.Name & vbCrLf
Next
   

Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.CreateTextFile ("wslist.txt", ForWriting)
ts.write report


Set fso = Nothing
Set objIADsComputer = Nothing
Set objIADsContainer = Nothing


MsgBox "Done"[/b]


OK, back to the discussion.
Our sample wslist file might look like this:

Wksxp1
Wksxp2
Wksxp3
Wksxp4


Notice we donÆt have any header information, just the workstation names.

Now letÆs write some code that will read this file and create a one dimensional array from the data.
Code:
[b] On Error Resume Next
[green]
'open the file system object[/green]
Set oFSO = CreateObject("Scripting.FileSystemObject")
set WSHShell = wscript.createObject("wscript.shell")
[green]'open the data file[/green]
Set oTextStream = oFSO.OpenTextFile("wslist.txt")
[green]'make an array from the data file[/green]
RemotePC = Split(oTextStream.ReadAll, vbNewLine)
[green]'close the data file[/green]
oTextStream.Close
[/b]

OK, we now have an array named RemotePC that is filled with the names of our workstations.

So how do we use it?

Keeping with Microsoft standards, we will loop through each element of the array and call it strComputer.
Code:
[b] For Each strComputer In RemotePC
[green]'Do something useful with strComputer here[/green]
[red]
    '*******************************************
        
    
    '*******************************************[/red]
Next[/b]

That is it! Simple huh? Take a look at a standard Microsoft script from the Windows Script Repository. This script will terminate the Notepad process if it is running.
Code:
[b] strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcessList = objWMIService.ExecQuery _
    ("Select * from Win32_Process Where Name = 'Notepad.exe'")
For Each objProcess in colProcessList
    objProcess.Terminate()
Next
 [/b]


To make this script enterprise ready, the first thing we want to do is get rid of the first line strComputer = ".".

We will replace this with our code that opens the wslist file. And include the For statement. The Next from our For Next statement will go at the very end of the script.
Code:
[b] On Error Resume Next
[green]
'open the file system object[/green]
Set oFSO = CreateObject("Scripting.FileSystemObject")
set WSHShell = wscript.createObject("wscript.shell")[green]
'open the data file[/green]
Set oTextStream = oFSO.OpenTextFile("wslist.txt")[green]
'make an array from the data file[/green]
RemotePC = Split(oTextStream.ReadAll, vbNewLine)[green]
'close the data file[/green]
oTextStream.Close
For Each strComputer In RemotePC

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcessList = objWMIService.ExecQuery _
    ("Select * from Win32_Process Where Name = 'Notepad.exe'")
   For Each objProcess in colProcessList
       objProcess.Terminate()
   Next

Next[/b]

That is it! This script is now ready to be run against multiple machines!

Reading and Writing To/From Excel

OK, so letÆs look at another script. This one will grab some inventory information for you. It gets the OS, Memory, Processor and Disk information from a computer.
Code:
[b]
strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each OS In colSettings
	Wscript.Echo OS.Caption
	Wscript.Echo OS.Version
Next   
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_ComputerSystem")
For Each objComputer in colSettings 
	Wscript.Echo objComputer.Name
	Wscript.Echo objComputer.TotalPhysicalMemory /1024\1024+1 & "MB"
Next
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_Processor")
For Each objProcessor in colSettings 
	Wscript.Echo objProcessor.Description
Next

Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
	Wscript.Echo objLogicalDisk.Size /1024\1024+1 & "MB"
	Wscript.Echo objLogicalDisk.FreeSpace /1024\1024+1 & "MB"
[/b]


OK, so this script grabs some useful information for us. Running it against my test machine returns the following:
Microsoft Windows XP Professional
5.1.2600
MARKDMAC
512MB
x86 Family 6 Model 8 Stepping 1
39061MB
13313MB

This lists the OS, Build, Machine Name, Installed Memory, Processor, Disk Drive Space and Free Space. But now letÆs make this ready for our Enterprise. You will most likely want to be able to sort the data you get, so letÆs use Microsoft Excel for this project.

First we want to create an Excel spreadsheet. LetÆs give the 1St row some headings.
Use the following from left to right.

Computer Name, Verified Inventory Name, Physical Memory, Processor, Disk Space, Free Disk Space

Looking at those column headings you might be wondering why I verify the Inventory Name. I do this to double check for DNS errors.

OK, now starting in Row 2 Column A put in all of your workstation names. Save your file but keep it open.

We will now start working on modifying our script so it can read the Excel spreadsheet to get the computer names which will become strComputer.

Code:
[b] on error resume next

set x = getobject(,"excel.application")
r = 2
do until len(x.cells(r, 1).value) = 0
strComputer = x.cells(r, 1).Value
[/b]

Notice that we have set r=2. We do this because row 1 has our header name. You should also notice our line that says do until len(x.cells(r, 1).value) = 0 this tells the script to stop when it runs out of computer names in Column A. While we are looking at that line, I want you to see that the cell is referenced by Row and Column. So why do we have a 1 instead of an A in our example (r,1)? Well, VBScript references those columns by numbers. A is 1, B is 2 etc. To avoid getting yourself all confused, you can have Excel use the same context. In Excel click Tools, Options. Now click the General tab and select R1C1 Reference Style.

OK, so Excel is now reading our machine names. Now letÆs look at how we can convert our Echo statements to write the data to Excel instead.

Wscript.Echo OS.Caption will become x.cells(2, 7).value = OS.Caption.

This tells our script to fill in the OS Caption to our open Excel Spreadsheet in Row 2 Column 7.

Our completed script will read as follows.
Code:
[b] '==========================================================================
'
' NAME: <MemProcDiskInventory.vbs>
'
' AUTHOR: Mark D. MacLachlan , The Spider's Parlor
' URL: http://www.thespidersparlor.com
' DATE  : 2/5/2003
'
' COMMENT: <Inventories computer configurations from a list of computers>
'==========================================================================
on error resume next

set x = getobject(,"excel.application")
r = 2
do until len(x.cells(r, 1).value) = 0
strComputer = x.cells(r, 1).Value

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each OS In colSettings
	x.cells(r, 7).value = OS.Caption
	x.cells(r, 8).value = OS.Version
Next   
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_ComputerSystem")
For Each objComputer in colSettings 
x.cells(r, 2).value = objComputer.Name
x.cells(r, 3).value = objComputer.TotalPhysicalMemory /1024\1024+1 & "MB"
Next
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_Processor")
For Each objProcessor in colSettings 
x.cells(r, 4).value = objProcessor.Description
Next


Set objWMIService = GetObject("winmgmts:")
Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
x.cells(r, 5).value = objLogicalDisk.Size /1024\1024+1 & "MB"
x.cells(r, 6).value = objLogicalDisk.FreeSpace /1024\1024+1 & "MB"

r = r + 1
loop[/b]

Take a peek at those last two lines. The loop statement belongs to our do until len(x.cells(r, 1).value) = 0 statement. The r = r + 1 tells our script with each part of the loop to move to the next row when reading machine names.

FAQ Update
OK, well maybe that's NOT "it"...
Since the original release of this FAQ, there have been some changes to Windows Security that may cause the above script to report information for the local computer on the lines for each of the other computers, or you may get failures entirely. After some investigation I have narrowed this down to Windows firewall issues in stand alone/workgroup machines and GPO settings in a Domain Environment along with issues connecting and specifying a user name and password to be used on the remote system. The problem with this method is that it then will break any queries to the local system.

OK, so I've taken the above script and added a few extra bits here that I think you should try to understand. Notice in this version of the script that I first determine the name of the system that the script is executing on. I do this so I can check if the name in the list matches the computer the script executes on and if so I specify the local Set command VS the command needed to query a remote system.

You will notice in this version also that I have added some code to clear out the collections after they have been written to Excel. This is done to ensure we don't write data from another system on the wrong line as would happen if there was a bind error in the previous version of this script. I've also taken each of the WMI collections and given them their own name rather than reusing the colSettings over and over. Last thing you will note is that I check for a binding error and if so report its description in place of the verified PC name. This can aid in troubleshooting permissions or network issues.

Before executing the modified script, modify the following GPO settings to allow WMI queries within your network.
Code:
[blue]
User Configuration
     Administrative Templates
          Microsoft Management Console
               Restricted/Permitted snap-ins
                    WMI Control  => Set to [b]Enabled[/b]
                    DCOM Configuration Extension  => Set to [b]Enabled[/b]

Computer Configuration
     Administrative Templates
          Network\Network Connections
               Windows Firewall
                    Domain Profile
                         Windows Firewall
                              Allow Remote administration exception  => Set to [b]Enabled[/b]
[/blue]

OK, with the policies updated you should now be able to run your inventory with the following code.

Code:
[green]
'==========================================================================
'
' NAME: <MemProcDiskInventory.vbs>
'
' AUTHOR: Mark D. MacLachlan , The Spider's Parlor
' URL: http://www.thespidersparlor.com
' DATE  : 2/5/2003
' MODIFIED 3/2/2006 Modifications to remote WMI calls.

' COMMENT: <Inventories computer configurations from a list of computers>
' NOTE:  Firewall security may block ths script from running remotely.[/green][red]
' Execute the following command on each workstation to allow remote WMI calls.
' netsh firewall set service type=remoteadmin mode=enable scope=all profile=all [/red]
[green]'==========================================================================[/green]
on error resume Next
Set WSHNetwork = CreateObject("Wscript.Network")

set x = getobject(,"excel.application")
r = 2

Set Locator = CreateObject("WbemScripting.SWbemLocator")

do until len(x.cells(r, 1).value) = 0
strComputer = x.cells(r, 1).Value
[green]' Set the local admin credentials[/green]
strUser = strComputer & "\administrator"
strPassword = "passwordgoeshere"
[green]
' Check if the strComputer is THIS machine and set objWMIService as needed[/green]
If lcase(strComputer) = LCase(WSHNetwork.ComputerName) Then 
	Set objWMIService = GetObject("winmgmts:" _
    	& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Else[green]
	'Remote systems need added security context to be specified.[/green]
	Set objWMIService = Locator.ConnectServer(strComputer, "root\cimv2", strUser, strPassword)
	objWMIService.Security_.ImpersonationLevel = 3
End If[green]
' Let us know if there is a binding issue.[/green]
If Err Then
	x.cells(r, 2).value = Err.Description
	Err.Clear
End If

Set OScolSettings = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each OS In OScolSettings
    x.cells(r, 7).value = OS.Caption
    x.cells(r, 8).value = OS.Version
Next
Set OScolSettings = Nothing
   
Set CScolSettings = objWMIService.ExecQuery _
    ("Select * from Win32_ComputerSystem")
For Each objComputer in CScolSettings 
x.cells(r, 2).value = objComputer.Name
x.cells(r, 3).value = objComputer.TotalPhysicalMemory /1024\1024+1 & "MB"
Next
Set CScolSettings = Nothing

Set ProccolSettings = objWMIService.ExecQuery _
    ("Select * from Win32_Processor")
For Each objProcessor in ProccolSettings 
x.cells(r, 4).value = objProcessor.Description
Next
Set ProccolSettings = Nothing

Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
x.cells(r, 5).value = objLogicalDisk.Size /1024\1024+1 & "MB"
x.cells(r, 6).value = objLogicalDisk.FreeSpace /1024\1024+1 & "MB"
Set objLogicalDisk = Nothing

r = r + 1
Set ObjWMIService = Nothing
Loop
MsgBox "Done"

Congratulations, you have now mastered or at least have an understanding on how you can convert scripts from running on just one computer to being able to run on multiple (and select) computers within your Enterprise. Now go and gather all the inventory information you can using the above script and tell your boss that it took you hours to gather the information for him. It can be our little secret that it was so easy.

I hope you have found this FAQ useful. If you have please let me know by rating it.
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top