Accessing WMI From Windows PowerShell

 

 

When people first hear about Windows PowerShell they inevitably have two questions: what is Windows PowerShell, and – more important – what can I do with it?

 

We aren’t going to deal with the first question (“What is Windows PowerShell?”) in this article; if you aren’t sure what Windows PowerShell is then you might want to check out our interview with Jeffrey Snover , the chief architect for Windows PowerShell. However, we aregoing to deal at least in part with question two: what can I do with Windows PowerShell? In addition, we’re going look at this from the perspective of a system administrator. Although there are other Windows PowerShell resources available, most of them are aimed at developers rather than system administrators. This article should give you a better idea of what youcan do with Windows PowerShell.

 

You Were Saying?

 

Oh; right. So what canyou do with Windows PowerShell? Well, for one thing, you can use Windows PowerShell to access WMI and WMI data. Let’s take a look at a script you’re probably familiar with, a VBScript script that retrieves information from the Win32_Process class and then echoes back the Name and WorkingSetSize for each item in the collection:

 

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer &"\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_Process")

For Each objItem in colItems
    Wscript.Echo objItem.Name, objItem.WorkingSetSize
Next

 

OK, you can stop yawning now: we knowthat you’ve seen this script (or something similar) a million times before. But that’s the point: this is something you’re comfortable with. Now let’s take a look at a Windows PowerShell script that does the very same thing:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" -computername $strComputer

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Obviously there are some syntax differences, which we’ll discuss shortly. Despite those differences, however, you can probably still glance at this script and figure out what it does. Here’s another example. What does thisscript do?

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Service" -namespace "root\cimv2" -computername $strComputer

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.State
}

 

You got it: this Windows PowerShell script echoes back the Name and State for each service (that is, each instance of the Win32_Service class) installed on the local computer. As you can see for yourself, it’s actually pretty easy to figure out the gist of a Windows PowerShell ad script; things like the WMI class, namespace, and computername are spelled out for you (literally).

 

Of course, it’s one thing to be able to read a script and quite another to be able to write one: after all, lots of people can read books but very few people can write them. (Not that this always stops them, of course.) Needless to say, before you can write your own Windows PowerShell scripts to retrieve WMI data you’re going to have to understand at least someof the details involved with the Windows PowerShell scripting language. But don’t worry about that; after all, that’s why the Scripting Guys are here. (See: we do have somepurpose in life!)

 

Details, Details, Details

 

Let’s take care of the easy one first. In our opening line of Windows PowerShell code we assign the value dot (.) to a variable named $strComputer:

 

$strComputer = "."

 

This is almost exactly like the first line of code in our VBScript script; the only difference is that the variable name begins with a dollar sign ($). Why? Well, no reason other than the fact that this happens to be a Windows PowerShell requirement: variable names must begin with a $. Admittedly, that’s a convention that might take a little getting used to. If it’s any help, though, there is at least one advantage to requiring variable names to start with a $: any time you glance through a script it makes it very easy to pick out the variables.

 

Now let’s get to the good stuff: the get-wmiobjectCmdlet (we’ll explain what Cmdlets are in a minute). As the name implies, get-wmiobject is designed to access WMI and WMI data. In doing so, the Cmdlet tries to shield scriptwriters from some of the complexities behind making WMI connections. This is done by masking the more obtuse parts of the winmgmts: moniker and leaving you with a fill-in-the-blanks approach to writing WMI scripts:

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" -computername $strComputer

 

As you can see, it’s the same information found in a VBScript script that uses WMI: what Windows PowerShell has done is remove some of the boilerplate verbiage and make it a little clearer where (and how) to specify such things as the WMI class and namespace. What we’re doing here, of course, is retrieving a collection of WMI data and storing that information in a variable named $colItems. To do that we call get-wmiobject and pass it the following parameters:

 

Parameter

Description

-class

The name of the WMI class we want to access. In sample script, that’s Win32_Process.

-namespace

The WMI namespace where the class resides. If the class lives in root\cimv2 then you can leave out the –namespace parameter; that’s because root\cimv2 is the default value.

-computername

The computer we want to connect to. If the –computername parameter is not present then the script will retrieve information from the local machine. Here we’re using the value stored in the variable $strComputer; we could also hard-code in a computer name like so:

 

-computername "atl-dc-01"

 

Incidentally, there are additional parameters that can be used in conjunction with get-wmiobject. We’ll talk about one of those parameters  –  -filter– later in this article.

 

See, that’s not so scary. Do you want to access the StdRegProvclass (found in the root\defaultnamespace) on the remote computer atl-fs-99? Then fill in the blanks accordingly:

 

$colItems = get-wmiobject -class "StdRegProv" -namespace "root\default" -computername "atl-fs-99"

 

It can’t get much easier than that.

 

Let’s move on. In a VBScript script we retrieve a collection of data and then set up a For Each loop to walk through that collection. We’re going to do the same thing with our Windows PowerShell script. The code for looping through the collection looks like this:

 

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Don’t let the syntax throw you: although this is obviously different than VBScript it’s not necessarily harderthan VBScript; it’s just, well, different. The loop starts out with the foreachcommand followed by the For Each criteria in parentheses. In VBScript we’d write this out something like this:

 

For Each objItem in colItems

 

In Windows PowerShell, we write it out like this:

 

foreach ($objItem in $colItems)

 

Here’s another minor difference. In VBScript we start our loop with For Each and then end the loop with Next:

 

For Each objItem in colItems
    Wscript.Echo objItem.Name, objItem.WorkingSetSize
Next

 

That’s not how we do things in Windows PowerShell, however. In Windows PowerShell there is no Next command; instead, the code we want to execute within the loop is contained within curly braces:

 

{
    write-host $objItem.Name, $objItem.WorkingSetSize
}

 

In this case all we’re doing is echoing back the values of the Name and WorkingSetSize properties. We do this by calling write-host, a Cmdlet that writes data to the command window in much the same way that Wscript.Echo does. (The write-host Cmdlet does have a few capabilities that Wscript.Echo lacks; for one such example see the article Do Scripters Dream of Magenta-Colored Text? ) In this sample script we echo back the property values without any accompanying labels. If we wanted to use labels we could add them like this:

 

write-host "Name: " $objItem.Name
write-host "Working Set Size: "  $objItem.WorkingSetSize

 

Pretty easy, huh? And notice that we didn’t have to concatenate the items. In a VBScript script we’d have to use the ampersand in order to echo back a label and a property value on the same line:

 

Wscript.Echo "Name: " & objItem.Name

 

In Windows PowerShell no ampersand is required. Here’s a perfectly valid Windows PowerShell script:

 

$Number = 4
write-host "1" 2 "3" $Number 5

 

We’re echoing back strings, numbers, and a variable by using a single write-host call, and without any ampersands or other concatenation characters. And here’s what we get when we run the script:

 

1 2 3 4 5

 

This is just what we were hoping to get. And Windows PowerShell is flexible, too. For example, if you’re more comfortable using a comma to delineate individual items, well, then use a comma to delineate individual items:

 

$Number = 4
write-host "1", 2, "3", $Number, 5

 

It’s up to you.

 

Being able to leave out the ampersand is a nice little feature. But, to be honest, for this sample script the difference between Windows PowerShell and VBScript is about the same as the difference between American English and British English. You’ll never mistake American English for British English, and there will always be words and phrases that are used (or spelled) differently. But a native speaker of American English can understand almost everything a native speaker of British English says. Likewise, a VBScript scripter can understand nearly everything that takes place in a Windows PowerShell script. And that leads to an obvious question.

 

So What’s the Big Deal?

 

Actually, that’s a very goodquestion: if a Windows PowerShell script that gets information from WMI works exactly the same way that a VBScript script works, why bother with Windows PowerShell? The truth is, other than a few syntax changes there’s no difference between the Windows PowerShell script we showed you and the VBScript script we showed you: they get the same information and they display the exact same results. Granted, the Windows PowerShell script is a tiny bit smaller, but the savings are negligible; for that matter, we could cut down the amount of code used in the VBScript script if we were willing to sacrifice a little readability.

 

In other words, for simple WMI scripts there really is no compelling reason to switch to Windows PowerShell. If you like using Windows PowerShell then use Windows PowerShell; if you’re more comfortable using VBScript then stick with VBScript. It makes no difference.

 

In fact, you won’t find much of a difference until you decide to add a few fancy flourishes to your scripts; it’s at that point that Windows PowerShell really begins to shine. For example, by default WMI always returns data sorted by the key property (a property that uniquely identifies an instance of a class). With the Win32_Process class, Handle is the key property; that means process information alwayscomes back sorted by Handle. Want to sort that data alphabetically, or maybe sort it by working set size? Sorry: you can’t do that using WMI alone.

 

To make matters worse, VBScript doesn’t have a built-in method for sorting data, either. That doesn’t mean you can’t sort process information alphabetically or by working set size; it just means that you need to employ another scripting technology in order to do so. Here’s a sample VBScript script that uses ActiveX Data Objects (ADO) and a disconnected recordset to sort process information by working set size:

 

Const adVarChar = 200
Const MaxCharacters = 255
Const adFldIsNullable = 32
Const adInteger = 3

Set DataList = CreateObject("ADOR.Recordset")
DataList.Fields.Append "ProcessName", adVarChar, MaxCharacters, adFldIsNullable
DataList.Fields.Append "WorkingSetSize", adInteger, adFldIsNullable
DataList.Open

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("Select * From Win32_Process")
For Each objProcess in colProcesses
    DataList.AddNew
    DataList("ProcessName") = objProcess.Name
    DataList("WorkingSetSize") = objProcess.WorkingSetSize
    DataList.Update
Next

DataList.Sort ="WorkingSetSize"
DataList.MoveFirst
Do Until DataList.EOF
    Wscript.Echo DataList.Fields.Item("ProcessName"),_
        DataList.Fields.Item("WorkingSetSize")
    DataList.MoveNext
Loop

 

It’s a little cryptic and a little cumbersome, but it works: you cansort WMI data in a VBScript script. Now, what about Windows PowerShell: how does Windows PowerShell handle the problem of sorting WMI data? Funny you should ask ….

 

Sorting Data

 

Without COM objects (applications that make their functionality available to script writers) VBScript wouldn’t be too terribly useful; in fact, you wouldn’t be able to do much more than calculate the tangent of an angle or determine the absolute value of -545. That’s because – with the exception of methods such as Tan (tangent) and Abs (absolute value) – VBScript doesn’t have a lot of functions and methods built in. If you want to do something truly useful with VBScript you need to connect to and take advantage of COM objects. In our preceding example, the one that sorts WMI data using a disconnected recordset, we use two different COM objects: WMI and ADO .

 

Windows PowerShell can also make use of COM objects; after all, the get-wmiobject is simply connecting to the WMI service and asking WMI to retrieve information. In addition to COM objects, however, Windows PowerShell also ships with a number of Cmdlets. Cmdlets are programming modules that do the same sort of thing COM objects do: they make their functionality available to script writers. However, Cmdlets are more tightly-integrated with Windows PowerShell than COM objects are; because of that you can easily “pipeline” objects and data from one Cmdlet to another.

 

What does that mean? Let’s show you. Here’s a Windows PowerShell script that sorts process information by working set size:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer | sort "WorkingSetSize"

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Note. In Windows PowerShell the grave-accent character (`) is used to indicate a line break. In that respect it is equivalent to the underscore character (_) in WSH and VBScript.

 

Believe it or not, that’s the entire script. And no, you’re not overlooking the code where we created and used a disconnected recordset; with Windows PowerShell we don’t needto create a disconnected recordset. That’s because there’s a Cmdlet named sort-object(or sort) that we can use to sort data. Take a careful look at the code here:

 

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer | sort "WorkingSetSize"

 

As you can see, at the tail end of our query we have this funny-looking construction: | sort "WorkingSetSize". The | is the pipeline character. All that means is that we want to take everything that’s on the left-hand side of the character and “pipe” it over to the Cmdlet that’s on the right-hand side of the character. In this case, we want to take the process information returned by the WMI query and hand it off to the sort Cmdlet.

 

And what will sort do with that information? Well – surprise, surprise – it’s going to sort our data, and sort it by whatever property we specify (in this case, WorkingSetSize). And that’s all we have to do. The sort Cmdlet sorts the data in our collection ($colItems); when we echo back that data it’s going to look something like this:

 

 

 

There’s our process information, sorted by WorkingSetSize.

 

Here’s a nifty little trick. By default the sort Cmdlet sorts data in ascending order; in this case that means the process with the smallest working set size will be listed first. What if you wanted to use descending order, with the process that has the largestworking set size listed first? No problem; just add the –descendingparameter:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer | sort -descending "WorkingSetSize"

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

The Scripting Guys are huge fans of VBScript; we think it’s a very nice programming language. But we have to admit, the ability to sort data just by adding | sort -descending "WorkingSetSize"to a script is pretty cool.

 

Limiting Returned Data

 

Here’s another scenario that pops up from time-to-time. Suppose you’d like to see which of your five processes have the largest working set size. (If you’re a SQL aficionado, that’s equivalent to a Select Top 5query.) Can we do that using VBScript?

 

You bet we can ... as long as we use a disconnected recordset and as long as we add some additional code that lets us know when we’ve echoed back the first five records. Here’s a sample script that does just that:

 

Const adVarChar = 200
Const MaxCharacters = 255
Const adFldIsNullable = 32
Const adInteger = 3

Set DataList = CreateObject("ADOR.Recordset")
DataList.Fields.Append "ProcessName", adVarChar, MaxCharacters, adFldIsNullable
DataList.Fields.Append "WorkingSetSize", adInteger, adFldIsNullable
DataList.Open

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process")
For Each objProcess in colProcesses
    DataList.AddNew
    DataList("ProcessName") = objProcess.Name
    DataList("WorkingSetSize") = objProcess.WorkingSetSize
    DataList.Update
Next

DataList.Sort ="WorkingSetSize DESC"
DataList.MoveFirst

i = 1
Do Until i = 6
    Wscript.Echo DataList.Fields.Item("ProcessName"),_
        DataList.Fields.Item("WorkingSetSize")
    DataList.MoveNext
    i = i + 1
Loop

 

Again, it’s a bit crazy, but it works. Now here’s the Windows PowerShell equivalent:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer | sort –descending "WorkingSetSize" | select-object –first 5

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Why is the Windows PowerShell script so much smaller? You guessed it: Windows PowerShell has a built-in Cmdlet – select-object– that lets us select a subset of objects in the collection (or, in Windows PowerShell terms, in the pipeline). Notice that this time around we have a pair of pipelines: after sorting the data we pass that sorted data through a second pipeline. In that second pipeline we use select-object to select only the first five items in the collection: select-object –first 5. It’s that easy. What if we wanted to know which three processes had the smallestworking set size? One way to do that would be to select the last three items in the sorted collection; in that case, the script would look like this:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer | sort –descending "WorkingSetSize" | select-object –last 3

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Cool.

 

Filtering Data

 

One last thing we wanted to mention is the ability of Windows PowerShell to filter data. Windows PowerShell isn’t any better (or worse) at filtering data than VBScript; it’s just important to know that you caninclude code that does things such as limit returned data to those processes that have a working set size greater than 3,000,000 bytes.

 

In VBScript you accomplish this feat by adding a Where clause to your WQL query. For example, if you want information only about processes with a working set size greater than 3,000,000 bytes then your WQL query would look something like this:

 

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where WorkingSetSize > 3000000")

 

The Windows PowerShell equivalent of a Where clause is the –filterparameter, a parameter added to get-wmiobject:

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer –filter "WorkingSetSize > 3000000"

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

As you can see, this is just like a WQL Where clause, only without the keyword Where: –filter "WorkingSetSize > 3000000". There’s nothing particularly hard (or magical) about it.

 

You can also use the AND and OR operators within a –filter parameter. For example, suppose you’re interested only in instances of Microsoft Word that have a working set size greater than 3,000,000 bytes. In VBScript you’d write a WQL query that looked like this:

 

Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where WorkingSetSize > 3000000 AND Name ='winword.exe'")

 

And here’s what its Windows PowerShell counterpart might look like. (Note that, as a string value, winword.exemust be enclosed in single quotes, just like you’d have to do in a Where clause.)

 

$strComputer = "."

$colItems = get-wmiobject -class "Win32_Process" -namespace "root\cimv2" `
-computername $strComputer –filter "WorkingSetSize > 3000000 AND Name='winword.exe'"

foreach ($objItem in $colItems) {
      write-host $objItem.Name, $objItem.WorkingSetSize
}

 

Once again it’s all just fill-in-the-blanks: you can return filtered WMI data from any WMI class in any WMI namespace on any computer just by replacing the Xs with the appropriate values:

 

$colItems = get-wmiobject -class "XXXXXXXXXX" -namespace "XXXXXXXXXX" `
-computername "XXXXXXXXXX" –filter "XXXXXXXXXX"

 

Want a list of all the stopped services on a computer? Okey-doke:

 

$colItems = get-wmiobject -class "Win32_Service" -namespace "root\cimv2" `
-computername "atl-ws-01" –filter "State='Stopped'"

 

That’s all we have time for today, but we’ll periodically update the Script Center with additional articles on using Windows PowerShell as a tool for system administration. If there’s a topic you’d like to see us cover then email us at scripter@microsoft.com ; we’ll see what we can do.