Windows PowerShellBuilding Your Own Software Inventory Tool

Don Jones

Contents

Finding the Information
Prototyping
Reading Computer Names
Modularizing
Pipeline Functions

In this installment of the Windows Power-Shell column, I am going to demonstrate a very practical use: I will build a tool that inventories the operating system build number (one of the best ways to determine the operating system version) and service pack version number from a list of comput­ers. But I'm not just going to give you the solution outright. I'm going to walk you through the process I use to develop a script like this.

While this is obviously a useful tool, I think the process by which I develop the tool is even more important. Once you understand and can reuse this development process for your own tasks, you'll be well on your way to solving nearly any administrative problem using Windows PowerShell. (As the old saying goes, if you teach a man to build a tool …) So let's get started.

Video (no longer available)

Watch Don Jones build a Windows PowerShell-based inventory tool and demonstrate the process that goes into building such a solution.

Finding the Information

The first—and often hardest—task is to find out where you can actually find the operating system and service pack version numbers. You might be tempted to go spelunking in the registry. The registry is sometimes the answer for information such as this, but it's usually my option of last resort because the registry can be a little bit tricky to work with.

The words "retrieve" and "information" immediately trigger a possible answer in my mind: Windows Management Instrumentation (WMI). Another keyword that makes me think of WMI is "remote," since in version 1 of Windows PowerShell, WMI is about your only option for performing any kind of remote information inventory or management operation.

Unfortunately, there are tens of thousands of WMI classes in most Windows systems, and this can make it difficult to find the one that contains the information you need. I usually start with a Web search engine and punch in a phrase such as "wmi service pack version number." Note that you'll need a fairly long search phrase like that in order to generate more specific results.

You may even need to try some varying search terms, but don't be tempted to abbreviate ("wmi sp version" won't provide very useful results). Consider alternative terms. For example, what you may call "patches," others call "hotfixes," and still others call them "quick fix engineering" or "qfe patches." Trying all of those terms may be necessary to find the right results.

If you're searching on something related to computer hardware or the core Windows OS, adding "Win32" to your search phrase can help, since most of the relevant WMI classes start with a "Win32_" prefix. Adding "Win32" to my current search definitely yields the best results. I see several results with "Win32_OperatingSystem" in the titles—that's a WMI class name.

Now, the important trick is to refrain from clicking on any of the search results. (That way lies madness.) I first want to find the actual documentation pages for the class, so I start over with a new search using just the class name I discovered. Doing so will generally yield a link to the msdn.microsoft.com Web site in the first couple of results, and that should take you to somewhat directly to the class documentation page.

Figure 1 shows a portion of that page. I've scrolled to a particularly important table on that page, which lists the OS versions with which the class will work. I can't tell you how many times I've labored over something, trying to get it to work, only to discover that what I was trying to do didn't actually exist in the version of Windows I was using. So now I have the habit of first checking this table.

fig01.gif

Figure 1 Looking up information about the WMI class (Click the image for a larger view)

Scrolling up the page a bit, I see that there are two properties I am interested in: BuildNumber and ServicePack­MajorVersion. In fact, ServicePackMinorVersion might be useful, too, although I've never seen a 2.1 service pack number from Microsoft. Nonetheless, it probably deserves to be checked if only to be thorough.

Cmdlet of the Month: Export-CliXML and Import-CliXML

Windows PowerShell has the ability to store a static snapshot of objects in a special XML format, allowing the information in those objects to be persisted in a file and loaded into memory for later examination. You can simply pipe objects to Export-CliXML to save those objects to a file:

Get-Process | Export-CliXML c:\processes.xml

When you import those objects into the shell later on, you can examine them just like any other object. They're not "real" objects, but they do enable more reporting options. Suppose you've scheduled a script that exports all the processes on a given server at 3:00 A.M. when some maintenance task is running. When you come to work, you can load those processes and look at them, perhaps sorting them on their virtual memory consumption, like so:

Import-CliXML c:\processes.xml | Sort VM -descending

Prototyping

I don't want to go any further without making sure these properties do what I expect. Windows PowerShell makes it very easy to do this. I start by checking this information on my local computer:

Get-WmiObject Win32_OperatingSystem | Select BuildNumber,ServicePackMajorVersion,ServicePack­MinorVersion

OK, the minor version was zero, which was what I expected, so I'm going to forget about it. The other information is also what I expected—a build number of 6001 for my Windows Server 2008 computer and a service pack version of 1.

Now I will do a similar test on a remote computer—one that I know I'm an administrator on:

Get-WmiObject Win32_OperatingSystem –computer Server2 | Select BuildNumber,ServicePackMajorVersion

If that test doesn't work, I need to stop and figure out why before I can go any further. Any problems are likely related to connectivity, firewalls, or permissions, which are all outside the scope of Windows PowerShell itself. Once I've got everything working properly, I can move on to the next step in my problem: how to get a bunch of computer names out of a file.

Reading Computer Names

Assuming my list of computer names is in a text file and there is one computer name listed on each line, the simplest way to do this is to use the Get-Content cmdlet. (Don't fret if your computer names are not in a text file or listed one per line—I'll cover techniques for dealing with different circumstances in a follow-up column.)

Each name is returned as an independent string object. And a handy feature of the Get-WmiObjectcmdlet is that its –computerName parameter accepts a collection of computer names, so this should do the trick:

$names = Get-Content c:\computernames.txt Get-WmiObject Win32_OperatingSystem –comp $names | Select BuildNumber,ServicePackMajorVersion

The problem now is that my output is a list of numbers, with no indication of which number goes with which computer. Fortunately, the Win32_OperatingSystem class happens to have another property, CSName, that includes the computer name. So I can add that property to my output and I get a nice inventory:

$names = Get-Content c:\computernames.txt Get-WmiObject Win32_OperatingSystem –comp $names | Select CSName,BuildNumber,ServicePackMajorVersion

Modularizing

All of that might be a bit too much for a less experienced technician to use, so the final step is to modularize all of this into a function. One way is to write a function that accepts a filename and have the function do all the work:

Function Get-SPInventory ([string]$filename) { $names = Get-Content $filename Get-WmiObject Win32_OperatingSystem –comp $names | Select CSName,BuildNumber,ServicePackMajorVersion }

As you can see, I've simply wrapped my working code in a function named Get-SPInventory. I've defined it with an input parameter named $filename, so it can just be called like this:

Get-SPInventory c:\computernames.txt

The results can still be piped to a CSV file, converted to HTML, or formatted differently, using normal Windows PowerShell commands, like so:

Get-SPInventory c:\computernames.txt | Export-CSV SPInventory.csv

This still isn't perfect, though. What if I also wanted to inventory some information that wasn't included in the Win32_OperatingSystem class, such as the BIOS serial number for each computer? That would be useful since many Configuration Management Databases (CMDBs) use the BIOS serial number as the unique identifier for computers.

I might also want the output of the function to be a bit more flexible, perhaps allowing me to sort or filter the results more easily. Then, for example, I could choose to include only Windows Server 2003 computers with an older service pack version in the final output to create a list of computers I need to update.

Pipeline Functions

Now I want to rewrite my function to be a better player in the Windows PowerShell pipeline. Specifically, I want the function to directly accept computer names from the pipeline—that way, I can decide where I want to get the computer names from each time I use the function. The computer names may be in a file or they may be in Active Directory, and I want the function to work well either way. So, here's the function rewritten for the pipeline:

Function Get-SPInventory { PROCESS { $wmi = Get-WmiObject Win32_OperatingSystem –comp $_ | Select CSName,BuildNumber,ServicePackMajorVersion Write-Output $wmi } }

This special type of function uses a PROCESS script block, which will execute once for each pipeline object I pipe into the function. (Dedicated readers will remember that I discussed the PROCESS script block in the July 2008 installment of this column, available at technet.microsoft.com/magazine/cc644947.aspx.) The special $_ variable will automatically populate with the pipeline input—as long as I'm piping in computer names, it'll work fine. The new function is used like this:

Get-Content c:\computernames.txt | Get-SPInventory

As you can see, this offers more flexibility for getting computer names from different sources. I just replace the Get-Content portion of the command with whatever command is necessary to retrieve the computer names. Note that I've also set it up so that I can create more robust output by querying different WMI classes (or any other data sources) before constructing my output.

But that's not the end of the discussion. Next month I'll expand this function to include the BIOS serial number in the output.

Don Jones is a cofounder of Concentrated Technology and author of numerous IT books. Read his weekly Windows PowerShell tips or contact him at www.ConcentratedTech.com.