Hey, Scripting Guy! How Can I Get WMI Information About More Than One Computer?

ScriptingGuy1

Hey, Scripting Guy! Question

I want to tell you that I love your articles. I have learned more about tea in the last three months than I ever thought was possible. In fact, if truth be told, I never thought about tea that much. I do like the can of tea from the vending machine, if that counts. Sometimes I drink that instead of soda pop, when I want to get the healthy antioxidants. To be honest, I am not sure what an antioxidant is, but I am sure I need a few every now and then. Anyway, I have a problem. All your scripts on the Script Center basically run against one computer—and like, man, if we only had one computer, I do not think I would need to be running scripts. How about showing me how to obtain WMI information from more than one computer?

– HM

SpacerHey, Scripting Guy! Answer

Hi HM,

Antioxidants help fight free radicals, which by the way have nothing to do with hippies. Antioxidants are found in lots of fruits, as well as in tea (green, black, and even white). So you are probably safe with your canned tea.

There are many ways that a script can be written to run against multiple computers. For some simple tasks, you can use Scriptomatic 2.0. It can accept text input or an array of computer names. We also have some tips in the scripting techniques section of the Script Repository. Most of the time, the addition of the code complicates a script, so it is left off for demonstration purposes.

This week we will be looking at working with WMI from within Windows PowerShell. In honor of this week, we have created a new WMI Scripting Hub that pulls together a number of useful articles and tools from all over the Script Center into a single location. Because the articles will be using Windows PowerShell, you may also be interested in the Windows PowerShell Scripting Hub. You will find information there that will tell you how to download and install Windows PowerShell.

Here is one such script called ReadArgumentsFromText.vbs. It is taken from the scripting techniques section of the Script Center Script Repository:

Const ForReading = 1

Set objDictionary = CreateObject(“Scripting.Dictionary”) Set objFSO = CreateObject(“Scripting.FileSystemObject”) Set objTextFile = objFSO.OpenTextFile(“c:\fso\servers.txt”, ForReading) i = 0

Do Until objTextFile.AtEndOfStream strNextLine = objTextFile.Readline objDictionary.Add i, strNextLine i = i + 1 Loop

For Each objItem in objDictionary Set colServices = GetObject(“winmgmts://” & _ objDictionary.Item(objItem) _ & “”).ExecQuery(“Select * from Win32_Service”) Wscript.Echo colServices.Count Next

The ReadArgumentsFromText.vbs script reads a text file named Servers.txt from the C:\Fso folder. The Servers.txt file is seen in the image following this paragraph, and as you can tell it is a simple text file with the name of a separate server on each line:

Image of the Servers.txt file

 

To read a text file using Windows PowerShell, we can use the Get-Content cmdlet and specify the path to the text file. The content of the text file is returned as an array of text. This is illustrated by indexing into the returned array and retrieving the data in the third line of the text file. This is seen here.

PS C:\> (Get-Content -Path C:\fso\Servers.txt)[2]
LocalHost

Once we have the array of text from the file, we pipeline it to the ForEach-Object cmdlet. The ForEach-Object cmdlet works similar to a For…Each…Next type of construction from VBScript. It provides access to each item as it comes across the array via the automatic variable $_. This allows us to work with each item as it comes across the pipeline. This is illustrated here where we take an array of computer names and pipeline them to the ForEach-Object cmdlet. Inside the code block (the curly brackets {}) we use the $_ automatic variable to print out each item in the array:

PS C:\> “berlin”,”Vista”,”LocalHost” | Foreach-Object { $_ }
berlin
Vista
LocalHost

The semicolon is used to separate commands in Windows PowerShell in the same way it is done in VBScript. What I want to do is to print out a message that says I am querying the computer, and then use the Get-WmiObject cmdlet to query the computer. We are retrieving BIOS information from the Win32_Bios WMI class. The –computername parameter is used to allow us to query a remote computer. The basic WMI query is seen here, where we retrieve BIOS information from the Win32_Bios WMI class from a remote computer named Berlin:

PS C:\> Get-WmiObject -Class win32_bios -ComputerName berlin

SMBIOSBIOSVersion : 080002 Manufacturer : American Megatrends Inc. Name : BIOS Date: 02/22/06 20:54:49 Ver: 08.00.02 SerialNumber : 2096-1160-0447-0846-3027-2471-99 Version : A M I – 2000622

The complete one-line command that reads the text file and queries each computer for the BIOS information is seen here:

Get-Content -Path C:\fso\Servers.txt | 
ForEach-Object { “Querying $_” ;Get-WmiObject -Class Win32_bios -ComputerName $_ }

When we run the above command, we receive the results seen here:

Image of the results of running the command

 

If the syntax shown above is too long, it can be shortened considerably through the use of aliases and positional arguments:

gc C:\fso\Servers.txt | % { “Querying $_” ; gwmi win32_bios -co $_ }

Gc is an alias for the Get-Content cmdlet. The default argument for the Get-Content cmdlet is –path and it can therefore be left out. The two commands seen here are exactly the same (only one is perhaps a bit easier to read):

gc C:\fso\Servers.txt
Get-Content -Path C:\fso\Servers.txt

The % character is an alias for the ForEach-Object cmdlet. There are four default one-character aliases in Windows PowerShell. The %, ?, h, and r. You can create your own aliases by using the New-Alias cmdlet. If, for example, you think that gc is too long, you can replace it with g by typing the following command:

New-Alias -Name g -Value Get-Content

Gwmi is an alias for the Get-WmiObject cmdlet. The default parameter is class, and it can therefore be left out. However, we cannot leave out the computername parameter because the cmdlet gets confused as to what we are trying to tell it. We are supplying only enough of the –computername parameter to disambiguate it from the other parameters that also start with the letter c, such as –class, -computername, and -credential. The long form of the ForEach-Object command and the short form of the same command are seen together here:

% { “Querying $_” ; gwmi win32_bios -co $_ }
ForEach-Object { “Querying $_” ;Get-WmiObject -Class Win32_bios -ComputerName $_ }

You may think, dude, with the short form being so much shorter, why bother writing the long form? The reason I personally seldom use parameters is that I think it is unfair to people just trying to learn Windows PowerShell to be confronted with a cryptic, unintelligible command. Also, I write lots of scripts. As a best practice, one should never use an alias in a script. This is because aliases are not guaranteed to always be available. This is illustrated here where I delete the gwmi alias, and then try to use it again. Dude, it won’t work!

PS C:\> Remove-Item -Path alias:gwmi -force
PS C:\> gwmi win32_bios
The term ‘gwmi’ is not recognized as a cmdlet, function, operable 
program, or script file. Verify the term and try again.
At line:1 char:5
+ gwmi  <<<< win32_bios

Before you send an e-mail message to scripter@microsoft.com telling us that you accidentally deleted an alias, all you need to do is close Windows PowerShell and open it back up. It will be just fine. The default aliases are automatically recreated each time you open Windows PowerShell. Why, then, do I recommend against using aliases if they are automatically recreated? Because you can remove them in your profile that would be executed each time you open Windows PowerShell, that’s why.

Oh, but wait, we cheated. Our command does not do the same thing the ReadArgumentsFromText.vbs script does. How would we do that? We can do it like this:

Get-Content -Path C:\fso\Servers.txt | ForEach-Object`
 { (Get-WmiObject -Class Win32_Service -ComputerName $_).count }

The big thing, was putting parentheses around the Get-WmiObject command. We also got rid of the part that printed out the computer names. This is because the ReadArgumentsFromText.vbs script does not display the computer name, only the results of the count. When we run our newly modified command, we obtain the results seen here:

Image of the results of running the newly modified command

 

If we were to shorten the command by using aliases, we could reduce the command to about half of one line. The revised command is seen here.

gc C:\fso\Servers.txt | % {(gwmi win32_service -co $_).count}

Speaking of short commands, the –computername parameter will accept an array. The Get-Content cmdlet produces an array from a text file. This means you could theoretically use the gwmi alias for the Get-WmiObject cmdlet, specify the WMI class name, and use the –co parameter with the gc alias for Get-Content to read the text file and feed it to the –computername parameter. The short version of the command is seen here, followed by the long version:

gwmi win32_bios -co(gc C:\fso\Servers.txt)
get-wmiobject -class win32_bios -computername (get-content -path C:\fso\Servers.txt)

The problem with this exact approach, shown in the image below, is that while the data is returned, the computer names are not returned. Therefore, you do not know which BIOS information belongs to which computer.

Image of the data returned without computer names

 

Well, HM, my cup of tea is getting cold, so I need to get back to it. I hope you have seen some tricks that will be beneficial to you when trying to work with remote computers. This concludes WMI Week, and we still did not write a single script. Join us tomorrow for Quick-Hits Friday. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

0 comments

Discussion is closed.

Feedback usabilla icon