Get-WMIObject – WMI PowerShell Tricks for Windows Server Management

Introduction

IT Pros responsible for Windows Servers are hopefully already familiar with Windows Management Instrumentation, or WMI. This technology has been a staple in Microsoft operating systems since the days of Windows 2000. Using WMI we can identify all types of management and system information from the BIOS to logical disks to the operating system. In the past, if we wanted to access this treasure trove of information we typically used VBScript. But, with the arrival of Windows PowerShell, working with WMI information is much easier, flexible and downright fun.

The primary cmdlet is Get-WMIObject. I have a few tricks I’ll show you in a bit for using this command. But I also have a few other WMI PowerShell tricks that you might use to get system information or to help develop your own WMI scripts.

WMI datetime

First up is converting those ugly WMI datetime strings like this: 20110128161223.000000-300. In the VBScript days that meant a bit of string parsing. However, PowerShell offers a more convenient approach. Here is an example of the ugly part.

​PS S:\> get-wmiobject win32_operatingsystem | select InstallDate,Caption
InstallDate                             Caption
-----------                             -------
20110128161223.000000-300               Microsoft Windows 7 Ultimate

It would be much nicer if the install date was more user-friendly. Fortunately the PowerShell team thought the same thing and every WMI class in PowerShell has a method called ConvertToDateTime(). Pass a WMI datetime object as the method parameter and you get a friendly date time object back. The easiest way to invoke it is to use a hash table with Select-Object.

​PS S:\> get-wmiobject win32_operatingsystem |
>> select @{Name="Installed";
>> Expression={$_.ConvertToDateTime($_.InstallDate)}},
>> Caption
>>
Installed                               Caption
---------                               -------
1/28/2011 4:12:23 PM                    Microsoft Windows 7 Ultimate

In the hash table I created a new property called Installed. The value will come from the Expression scriptblock which is invoking the ConvertToDateTime() method from the current object ($_) and converting the current object’s InstallDate property. I use this technique all the time.
Normally with Get-WMIObject we get all instances. But, if you know the instance you want you can get it using the [WMI] type accelerator.

​PS S:\> [wmi]$c="win32_logicaldisk.DeviceID='C:'"
PS S:\> $c
DeviceID     : C:
DriveType    : 3
ProviderName :
FreeSpace    : 39053930496
Size         : 487439986688
VolumeName   :

The assumption is that you are connecting to a root\cimv2 namespace class. The key you use, the part after the period, is the same key you would see when running a query in WBEMTest. It must be a unique identifier. This technique works with remote computers as well. Simply specify the full WMI path.

​PS S:\> [wmi]$c="\\quark\root\cimv2:win32_logicaldisk.DeviceID='C:'"
PS S:\> $c
DeviceID     : C:
DriveType    : 3
ProviderName :
FreeSpace    : 131695562752
Size         : 201504845824
VolumeName   :

This is quick and easy, although it doesn’t support alternate credentials.

WMIClass

Another WMI accelerator you can use is [WMIClass]. Suppose you want to discover what properties or methods are available? You could run a Get-WMIObject expression and pipe it to Get-Member. Unfortunately this is hardly practical for a long running query. Instead do something like this:

​PS S:\> [wmiclass]$product="Win32_Product"

As before, you can also query a remote computer.

​PS S:\> [wmiclass]$product="\\quark\root\cimv2:Win32_Product"

This is very handy when querying a class this doesn’t exist locally. Now we can see methods and properties.

​PS S:\> $product.methods | select Name
Name
----
Install
Admin
Advertise
Reinstall
Upgrade
Configure
Uninstall
PS S:\> $product.Properties | select Name
Name
----
AssignmentType
Caption
Description
HelpLink
HelpTelephone
IdentifyingNumber
InstallDate
...

Once I have identified some properties I can create a more effective PowerShell expression.

​PS S:\> $props="Caption","Description","InstallDate","Version"
PS S:\> get-wmiobject win32_product -Property $props –asjob

This command has two tips. First, you can speed up your WMI query a bit by using the –Property parameter. If you don’t want all the properties, specify the ones you want. The Win32_Product class has many properties so by limiting my query I get a faster response. I’m also taking advantage of the –AsJob parameter to create a background job. I know that this particular class is slow to query to I can kick off a job and get the results later.
In fact, when I retrieve the results I also get the system properties like __CLASS, which I typically don’t need. These are easy to filter out by piping to Select-Object and specify the array of property names a second time.

​PS S:\> receive-job 1 -keep | select $props

Let’s take this to another level and bring in splatting.

Splatting

With splatting you can take a hash table of parameter values and pass it to the cmdlet. Here’s a sample hash table.

​PS S:\> $h=@{Class="win32_computersystem";
>> Property="Manufacturer","Model","Name","TotalPhysicalMemory";
>> Computername=$env:computername
>> }

The hash table keys correspond to the cmdlet parameter names. You pass the hash table by its name using the @ to indicate splatting. I can even re-use the hash table to filter out the WMI system properties.

​PS S:\> get-wmiobject @h | select $h.property
Manufacturer        : TOSHIBA
Model               : Qosmio X505
Name                : SERENITY
TotalPhysicalMemory : 8577855488

This is a very powerful automation technique. The core command can remain the same and relatively simply while you update the hash table as needed.

Retrieving WMI Information From Multiple Computers

The last tip I want to offer will matter to you when attempting to manage or retrieve WMI information from multiple computers. A common technique is to process a list of computer names and pass each name to Get-WMIObject.

​PS S:\> $h=@{Class="win32_service";
>> Property="Name","Displayname","StartMode","StartName","State","PathName";}
>>
PS S:\> Get-Content computers.txt | foreach { get-wmiobject @h -comp $_}

There’s nothing wrong with this approach, especially if the list is small and you can guarantee no connection problems. But this is about 4X as slow (in my testing anyway) as using this approach.

​PS S:\> get-wmiobject @h -comp (Get-content computers.txt)

In both these scenarios, each computer is processed sequentially which means you get results back sequentially. The other alternative is to take advantage of PowerShell remoting and use Invoke-Command.

​PS S:\> invoke-command {Param([hashtable]$h) get-wmiobject @h} -arg $h -comp (get-content computers.txt)

Performance-wise this is about in the middle, primarily due to the overhead of setting up the remote connections. If you use pre-existing sessions it would be faster. What I like about this approach is that you get parallel processing and any connection errors don’t slow down the overall command. If you have a large number of computers, I would include the –AsJob parameter. If there are problems, the job will look like it failed, but go ahead and receive the job. If a single child job fails, the parent job also shows as failed, even though the other child jobs most likely completed successfully.

Conclusion

You will want to test all of these techniques out in your own test environment. With a little experience, you’ll find managing 1000 Window servers as easy as managing 10 using WMI and Windows PowerShell.