Hey, Scripting Guy! How Do I Use WMI with Windows PowerShell to Query More Than One Piece of Information?

ScriptingGuy1

Hey, Scripting Guy! Question

Hey,  Scripting Guy! I have been trying to get on the Windows PowerShell bandwagon as I feel it is essential to my career as a network administrator who works in the Windows world. At first, it seemed to be confusing, but now I am starting to get it. I am still stuck when it comes to working with Windows Management Instrumentation (WMI). Most of the examples I find work with a single piece of information, and I would like to query two different pieces of information from WMI. Can you help me?

— SP

 

Hey, Scripting Guy! Answer Hello SP,

If you have been following us on Facebook or on Twitter, you probably know that I (Scripting Guy Ed Wilson) was in Seattle all last week because I was speaking at a Microsoft only technology conference called TechReady. TechReady is held twice a year and is the primary training vehicle for Microsoft employees in the premier organization who work with customers on a regular basis. These are people such as consultants, technical account managers, premier field engineers, and so forth. They fly in from all over the world for this big event. This is the first time I have been invited to speak at TechReady, so I was really excited about the prospect. I was also really busy working on my presentation (which, similar to the presentation I gave at TechEd 2009 in Los Angeles, has a number of demos that require extensive preparation). As a result, I decided to forward your question to my good friend, Don Jones. As they say on late night television, heeeeeere’s…Don.

 

First of all, let me say that it is a pleasure to stand in, just this once, for the Scripting Guys. In fact, I would say it is an honor.

Okay, with preliminary things out of the way, let us dive into answering your question, SP. You already know about using the Get-WmiObject cmdlet in Windows PowerShell, which is possibly the easiest way ever invented to grab information from the Windows Management Instrumentation repositoryeven on remote machines:

Get-WmiObject Win32_OperatingSystem –computerName Server2

If you want to query for additional information from a second WMI class such as one that returns information about the BIOS, the easy answer would be to just run the command again. This is seen here:

Get-WmiObject Win32_BIOS –computerName Server2

Such easy answers are for the faint of heart, though, and we Windows administrators are not faint of heart. I propose a better solution, one that actually makes the shell do a ton of work and deliver some really flexible results. First, let us dispense with this idea of querying entire WMI classes and focus on some critical bit of information we want. Let’s say service pack versions, Windows build numbers, and BIOS serial numbersall handy information to have from any computer in the environment. And let’s also add a bit of difficulty: Rather than querying from one computer, we want to query from multiple remote computers. Bwaa-haa-haa! Think of the power we shall command!

Ahem.

The first bit is to get the computer names. For ease of illustration, I am just going to list some computer names in a text file one line at a time. Like this:

Server1
Server2
Server3

I will save the file as C:computers.txt. As you can see here, there is nothing special about the text file:

Image of the computers.txt file

Next, I am going to write a shell function. The idea here is that I want to pipe those computer names into the function. That way, the function could be easily re-used with computer names from other sources. Anywhere you can scare up some computer names is fine; just pipe them into this function:

Function Get-Inventory {

PROCESS {

}

}

The PROCESS scriptblock inside that function does a lot of the hard work. A scriptblock is a Windows PowerShell term that indicates we are going to write script. It does not have to take place in a script; it can live inside the shell. The scriptblock or codeblock or whatever you want to call it is simply code that will be executed. The shell will actually take all the computer names I pipe in and execute that PROCESS scriptblock (and whatever I put inside it) once for each computer name. To make it easy for me to see which computer name I am working with, the shell will stick one name at a time into the special $_ variable. All I need to do is use $_ to grab the current computer name and the rest of my function can be written to just deal with one computer at a time, rather than a whole bunch of them.

Inside the PROCESS scriptblock, I will run two WMI queries. Rather than just dumping their output on the screen, I am going to grab the information I want and attach it to a custom output object. This is seen in the Get-InventoryFunction. I did not call this Get-InventoryFunction.ps1 because we have not saved it out into a script file. You can of course. You can even add the function to your Windows PowerShell profile, which is a subject for a future article that I will let Ed write.

Get-InventoryFunction

Function Get-Inventory {

PROCESS {

$os = Get-WmiObject Win32_OperatingSystem –computer $_

$bios = Get-WmiObject Win32_BIOS –computer $_

$output = New-Object PSObject

$output | Add-Member NoteProperty ComputerName $_

$output | Add-Member NoteProperty SPVer ($os.ServicePackMajorVersion)

$output | Add-Member NoteProperty BuildNo ($os.BuildNumber)

$output | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)

Write-Output $output

}

}

Therefore, for each computer name, I have queried both operating system and BIOS information. I constructed a new, blank object using the New-Object cmdlet. Then, I added information (properties) to that blank object by using the Add-Member cmdlet. Each time I ran Add-Member, I attached a new piece of information to the formerly blank object. Each of those bits of information is stored in a static “note property,” therefore I have to assign a name to each bit of informationComputerName, SPVer, and so forth. When I am done, I write the output object to the pipeline. This means I could pipe this to another cmdlet if I wanted to. This will be seen shortly, so stay with me.

The basic way to use the Get-Inventory function is seen here:

Get-Content c:computers.txt | Get-Inventory

The output is pretty to look at, but that is not why I went through all the hassle of creating a custom object. The reason for creating the custom object is that Windows PowerShell adores objects. Give it a set of objects and it will bend over backward to do stuff with them such as export them to a comma-separated value (CSV) file. By using the native Export-CSV cmdlet, you create a nice inventory file that is easily opened and manipulated in Microsoft Office Excel. The command to create the inventory.csv file is seen here:

Get-Content c:computers.txt | Get-Inventory | Export-CSV c:inventory.csv

The CSV file created by the previous command is seen here:

 

Image of the CSV file created


Using Windows PowerShell, you can easily make an HTML table. This technique is shown here:

Get-Content c:computers.txt | Get-Inventory | ConvertTo-HTML | Out-File c:inventory.html

If you wanted to do so, you could export them to XML. To do this, you use the Export-CliXML cmdlet as seen here:

Get-Content c:computers.txt | Get-Inventory | Export-CliXML c:inventory.xml

You can do just about anything else with the Get-Inventory function that you can imagine. When you put stuff into objects, you get to leverage a ton of additional built-in functionality. You could, for example, narrow the list down to Windows Vista computers that are running Service Pack 1 by using the Where-Object cmdlet. This is seen here:

Get-Content c:computers.txt | Get-Inventory | Where-Object { $_.BuildNo –eq 6000 –and $_.SPVer –eq 1 }

Maybe you want to sort and group the results by operating system. Because the Get-Inventory function creates an object the same way that Windows PowerShell cmdlets do, you are not limited to attempting to parse a lot of text. You can pipe the results from Get-Inventory directly to the Sort-Object cmdlet as seen here:

Get-Content c:computers.txt | Get-Inventory | Sort-Object BuildNo | Format-Table –groupBy BuildNo

I hope that you get the idea here: The shell loves objects. By creating a blank object and then attaching the various pieces of information I care about, I can use the output in a variety of ways without changing my function at all. This approach certainly takes a tiny bit more work up front than other approaches, but the end result is much more flexible and can save you a lot more work in the long run.

Need to combine information from three, four, or more places? Easy! You can attach as many pieces of information as you want to that custom object. So just query whatever information you need, and then merge it together on that custom object.

Now for a quick plug. Come visit me at ConcentratedTech.com. After I am done reading Hey, Scripting Guy! every morning, I go to my Web site and publish my own articles on Windows PowerShell (I’m currently writing about version 2), managing App-V, Mac interop, and much more. You will also find me every month in the pages of TechNet Magazine (technetmagazine.com), where I write aboutyou guessed itWindows PowerShell.

 

Thank you, SG, for your question, and special thanks to Don Jones for his excellent response. Join us tomorrow as we will continue looking at using WMI with Windows PowerShell. If you want to know in advance what that article will be about, follow us on Twitter or Facebook. If you run into problems with your scripts, you can always send e-mail to scripter@microsoft.com, or you can post to the Official Scripting Guys Forum. Until tomorrow, take care.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

0 comments

Discussion is closed.

Feedback usabilla icon