Get-wmiobject wrapper

From Svendsen Tech Powershell Wiki

Jump to: navigation, search

Get-WmiObject-Wrapper.ps1, available for download below, is a "wrapper" around Get-WmiObject. It is designed to retrieve and collect data from a list of computers. You also get an XML file parser based on the schema used, that simplifies creating custom PowerShell objects or CSV data that you can process.

This has been tested with PowerShell version 2 and version 3.

If you want to retrieve data from a large number of computers using WMI - this is probably what you're looking for - unless your needs are very simple. Now there's even an asynchronous version here that uses the very efficient runspaces in PowerShell. My test case against about 2000 computers (where 1300 were offline and 40 had errors) went from about 45 minutes with completely sequential processing to about 5 minutes with 50 jobs and 10 minutes with 10 jobs. There are some drawbacks to runspaces which you can read a bit about here and here - and other places on the web.

During another test of the asynchronous script against 16,000 computers, from a server with two virtual CPUs, it said the query failed on all of them when I tried using 50 concurrent jobs, while the data collection completed successfully when I used 10. No other changes. I also experienced that the script hung for hours, until I cancelled it, while trying to close the runspace pool against these 16,000 computers. Due to this, I made the runspace pool closing optional and non-default.

These scripts will also allow you to perform complex operations on collected data afterwards. The collected data is stored in a hash that's exposed globally to the calling shell / host environment as $WmiData and can be processed in normal ways, but normally using Gwmi-Wrapper-Report is easier. It's much faster to process objects in memory than creating them from the XML file each time, so you might want to do:

$WmiObjects = .\Gwmi-Wrapper-Report.ps1 xmlfile.xml

... And then later process it with something like:

$WmiObjects | Where { $_.Property -eq 'foo' }

The scripts support multiple returned WMI property values, a feature that was lacking in the old version. Now you also deal with objects mostly, instead of text, unless you want to deal with text, in which case you have the -AsCsv parameter to the report script.

I decided to store the retrieved data using XML since PowerShell v2 has decent support for it, as do many other tools.

I built in support for a WMI timeout parameter, which is missing from Get-WmiObject. However, during testing it appears that the timeout is not in effect on certain computers that "get stuck", which is really the problem I set out to solve with the timeout parameter. See the aforementioned article for further details.

A little taste:

Image:Get-WmiObject-Wrapper-Sample.png

Contents






How It Works

You specify a list of computers and the WMI class or classes and the property or properties you want to retrieve from each class, from these computers. This will be stored in an XML structure that supports this type of data. I demonstrate how to parse the XML with a ready-made script in the examples below. This will save you time if you want to create reports or otherwise parse the data using PowerShell, but you can also parse it "manually" by inspecting and iterating on the exported $WmiData object after having run Get-WmiObject-Wrapper.ps1.

You specify WMI classes and the properties you want from each class with a special string in this format:

"wmi_class1: prop1,prop2 | wmi_class2:prop1 | wmi_class3: prop1, prop2 , prop3"

Rules:

  • First you specify the class, followed by a colon, then a property or properties separated by commas.
  • Multiple class/property groups are separated by pipes.
  • You can have whitespace around pipes and colons - and between commas - for increased readability.

An example string in this format is:

"Win32_OperatingSystem:Version | Win32_ComputerSystem:TotalPhysicalMemory, Model"

The resulting XML looks like this, and you can now use the exposed $WmiData object to look at the data. However, the report generator, Gwmi-Wrapper-Report.ps1, is probably best suited to extract data from the XML file in the form of custom PowerShell objects, or produce CSV reports, or otherwise parse/process the data. There are also features in Gwmi-Wrapper-Report that make creating CSV data extra easy.

Here is an example of resulting XML data:

Image:Get-WmiObject-Wrapper-Xml-Sample.png

Downloads

2013-12-07: Changed Gwmi-Wrapper-Report.ps1's produced object titles and CSV headers from "Computer" (non-standard) to "ComputerName".

2013-07-30: Enhanced Gwmi-Wrapper-Report so it no longer shows the computers as a needless array of single elements.

2013-07-28: Bug fixes. No longer breaks the Gwmi-Wrapper-Report parser when there are no values. Tweaked how progress is displayed in PowerShell ISE

2013-04-23: Uploaded an -Async version. It has an -InvokeAsync parameter that takes an integer specifying the number of concurrent "jobs". I set the default to 5. It uses runspaces in a runspace pool which comes with some possible flaws, but it has been working quite well during testing. I have tested it quite a bit on virtual servers myself; one with two cores against about 2000 target computers, and it didn't explode. Also against 16,000 computers on another network. The powershell.exe process used about 125 MB of RAM in the former case (collecting five properties from two classes).


Parameters

-ComputerName Required. Array of strings with computer names.
-OutputFile Required. Output file. You will be prompted to overwrite it unless you specify -Clobber.
-MultiClassProperty Required. You specify WMI classes and the properties you want from each class with a special string in this format: wmi_class1:prop1,prop2|wmi_class2:prop1|wmi_class3:prop1,prop2,prop3.
-Timeout Optional. A time span object. The default is "0:0:10", which is 10 seconds.
-NoPingTest Optional. Specify this if you do NOT want to skip computers that do not respond to ICMP echo (ping).
-Clobber Optional. Overwrite the specified output file without prompting if it already exists.
-Scope Optional. The WMI management scope. Usually not necessary. By default "\\${Computer}\root\cimv2" will be used, while this lets you replace the part "root\cimv2" with what you specify instead.
-CustomSql The default WQL query is "SELECT prop1, prop2 FROM Win32_ClassHere", while this parameter lets you append something like: WHERE DriveType="3". This "custom WQL" will be used in all the queries, so if the property/condition doesn't apply to other classes, you will see errors.

Parameters specific to the -Async version

-InvokeAsync Sets the number of concurrent jobs. Default: 5.
-CloseRunspacePool Tries to properly close the runspace pool after processing the jobs. During testing this sometimes hangs, so I made it optional, and not the default behavior.

Access The Data In The XML From The Command Line

I figure if anyone's going to use this, you need a convenient way of retrieving data from the XML, so I wrote a simple, but, I dare say, highly flexible, report generator for you. In addition you can process the data structure found in $WmiData in the shell/host after having run Get-WmiObject-Wrapper.ps1, but you probably want to use the parser because it makes things easier.

With Gwmi-Wrapper-Report.ps1 you can easily parse the XML file and filter on (multiple) computer names, classes or properties. The parameters you specify for the filtering options: -ComputerName, -Class, and -Property are really regular expressions (where a complete match is needed, so pad with the "regex wildcard" .* if necessary). You might want to read more about text processing with regular expressions here, but it's really not necessary in order to use the report generator.

Gwmi-Wrapper-Report Parameters

The parameters are:

-XmlFile Required. The XML file to parse.
-ComputerName Optional. Default "all". Specify computer or computers to filter on. Only computer names matching those you specify here will be displayed. Really an array of regular expressions (where a complete match is needed).
-Class Optional. Default "all". Specify WMI class or classes to filter on. Only classes matching those names you specify here will be displayed. Really an array of regular expressions (where a complete match is needed).
-Property Optional. Default "all". Specify property or properties to filter on. Only properties matching those names you specify here will be displayed. Really an array of regular expressions (where a complete match is needed).
-AsCsv Optional. Get output as CSV strings rather than custom PowerShell objects. Pipe to Set-Content.
-CsvDelimiter Optional. Only in use with -AsCsv. Specify a custom CSV delimiter string. The default is a comma.
-ValueDelimiter Optional. Only in use with -AsCsv. A custom multi-value join string. Default is a semicolon.
-NoHeaders Optional. Only in use with -AsCsv. Do not print the default headers "Computer, Class, Property, Value".

Example Use

Image:Get-WmiObject-Wrapper-Sample.png


Dumping All Data

To dump everything, you can use this command:

> .\Gwmi-Wrapper-Report.ps1 -XmlFile .\wmi.xml

Example output:

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -XmlFile foo4.xml | ft -a

ComputerName   Class                 Property Value
------------   -----                 -------- -----
{2008r2esxi}   win32_computersystem  model    {VMware Virtual Platform}
{server2012}   win32_computersystem  model    {VMware Virtual Platform}

Filtering On A Computer Name

Let's filter on the computer name "server2008":

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Computer server2008 | ft -a

ComputerName     Class                  Property    Value
------------     -----                  --------    -----
{SERVER2008}     Win32_ComputerSystem   Model       {VMware Virtual Platform}
{SERVER2008}     Win32_OperatingSystem  Caption     {Microsoft® Windows Server® 2008 Standard }
{SERVER2008}     Win32_OperatingSystem  Version     {6.0.6002}
{SERVER2008}     Win32_Volume           DriveLetter {C: D:}

Since the filtering parameters take regular expressions (that require complete matches; pad with .*), you can also use something like this to get all the computers that start with "win8":

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Computer 'win8.*' | ft -a

ComputerName   Class                  Property    Value
------------   -----                  --------    -----
{WIN8ESXI}     Win32_ComputerSystem   Model       {VMware Virtual Platform}
{WIN8ESXI}     Win32_OperatingSystem  Caption     {Microsoft Windows 8 Pro}
{WIN8ESXI}     Win32_OperatingSystem  Version     {6.2.9200}
{WIN8ESXI}     Win32_Volume           DriveLetter {- C: D:}
{WIN8VM}       Win32_ComputerSystem   NoPing      {No ping reply}
{WIN8VM}       Win32_OperatingSystem  NoPing      {No ping reply}
{WIN8VM}       Win32_Volume           NoPing      {No ping reply}

Or as CSV strings ready to be piped to Set-Content and then later processed with Import-Csv:

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Computer server2008 -AsCsv
"ComputerName","Class","Property","Value"
"SERVER2008","Win32_ComputerSystem","Model","VMware Virtual Platform"
"SERVER2008","Win32_OperatingSystem ","Caption","Microsoft® Windows Server® 2008 Standard "
"SERVER2008","Win32_OperatingSystem ","Version","6.0.6002"
"SERVER2008","Win32_Volume","DriveLetter","C:;D:"

Filtering On A Property

Why not retrieve only the OS caption for all computers:

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Property Caption |
>> select Computer, Value | Format-Table -AutoSize
>>

ComputerName    Value
------------    -----
{2008R2ESXI}    {Microsoft Windows Server 2008 R2 Standard }
{2008R2ESXI2}   {Microsoft Windows Server 2008 R2 Standard }
{SERVER2008}    {Microsoft® Windows Server® 2008 Standard }
{SERVER2012}    {Microsoft Windows Server 2012 Standard}
{SIEMENS}       {Microsoft Windows XP Professional}
{SS-WIN7}       {Microsoft Windows 7 Professional }
{VMWAREWIN7}    {Microsoft Windows 7 Professional }
{WIN8ESXI}      {Microsoft Windows 8 Pro}
{WINXPSSD}      {Microsoft Windows XP Professional}

A side-effect here is that the computers with errors and no ping reply are filtered out, because the properties are named "Error" and "NoPing". There's only one Error or NoPing entry per class. To list errors, use:

> .\Gwmi-Wrapper-Report.ps1 -XmlFile .\wmi.xml -Property error

To list computers that did not respond to ping, use:

> .\Gwmi-Wrapper-Report.ps1 -XmlFile .\wmi.xml -Property NoPing

Creating A CSV Report Using -AsCsv

Use the -AsCsv parameter and pipe to Set-Content to create a CSV file easily. You can use the -CsvDelimiter parameter to set a different delimiter for the CSV data. The default is a comma. You can use the -ValueDelimiter parameter to set a different delimiter for properties with multiple values. The default is a semicolon.

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo3.xml -Prop model -AsCsv |
>> Set-Content -Encoding utf8 models.csv
>>
PS C:\prog> Import-Csv .\models.csv | ft -a

ComputerName  Class                Property Value
------------  -----                -------- -----
2008R2ESXI    win32_computersystem model    VMware Virtual Platform
2008R2ESXI2   win32_computersystem model    VMware Virtual Platform
SERVER2008    win32_computersystem model    VMware Virtual Platform
SERVER2012    win32_computersystem model    VMware Virtual Platform
SIEMENS       win32_computersystem model    LIFEBOOK S7010
SS-WIN7       win32_computersystem model    VMware Virtual Platform
VMWAREWIN7    win32_computersystem model    VMware Virtual Platform
WIN8ESXI      win32_computersystem model    VMware Virtual Platform
WINXPSSD      win32_computersystem model    VMware Virtual Platform

And the actual CSV file/data looks like this:

PS C:\prog> Get-Content .\models.csv
"ComputerName","Class","Property","Value"
"2008R2ESXI","win32_computersystem","model","VMware Virtual Platform"
"2008R2ESXI2","win32_computersystem","model","VMware Virtual Platform"
"SERVER2008","win32_computersystem","model","VMware Virtual Platform"
"SERVER2012","win32_computersystem","model","VMware Virtual Platform"
"SIEMENS","win32_computersystem","model","LIFEBOOK S7010"
"SS-WIN7","win32_computersystem","model","VMware Virtual Platform"
"VMWAREWIN7","win32_computersystem","model","VMware Virtual Platform"
"WIN8ESXI","win32_computersystem","model","VMware Virtual Platform"
"WINXPSSD","win32_computersystem","model","VMware Virtual Platform"

Filtering On A Class

Let's get the Win32_ComputerSystem class stuff - in this case the property "Model":

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Class Win32_ComputerSystem |
>> Select -First 6 | Format-Table -AutoSize
>>

ComputerName  Class                Property Value
------------  -----                -------- -----
{2008R2ESXI}  Win32_ComputerSystem Model    {VMware Virtual Platform}
{2008R2ESXI2} Win32_ComputerSystem Model    {VMware Virtual Platform}
{esxi}        Win32_ComputerSystem NoPing   {No ping reply}
{SERVER2003}  Win32_ComputerSystem NoPing   {No ping reply}
{SERVER2008}  Win32_ComputerSystem Model    {VMware Virtual Platform}
{SERVER2012}  Win32_ComputerSystem Model    {VMware Virtual Platform}

Notice how you can get "NoPing" and "Error" results this way. We can pull out a trick which is using "-Property Model". In which case the class specification is useless since the property name doesn't exist in any other class, but that's specific to the example data.

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Class Win32_ComputerSystem -Prop Model |
>> Select -First 6 | Format-Table -AutoSize
>>

ComputerName  Class                Property Value
------------  -----                -------- -----
{2008R2ESXI}  Win32_ComputerSystem Model    {VMware Virtual Platform}
{2008R2ESXI2} Win32_ComputerSystem Model    {VMware Virtual Platform}
{SERVER2008}  Win32_ComputerSystem Model    {VMware Virtual Platform}
{SERVER2012}  Win32_ComputerSystem Model    {VMware Virtual Platform}
{SIEMENS}     Win32_ComputerSystem Model    {LIFEBOOK S7010}
{SS-WIN7}     Win32_ComputerSystem Model    {VMware Virtual Platform}

Or as CSV data ready to be piped to Set-Content:

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -Xml foo.xml -Class Win32_ComputerSystem -Property Model -AsCsv |
>> Select -First 5
>>
"ComputerName","Class","Property","Value"
"2008R2ESXI","Win32_ComputerSystem","Model","VMware Virtual Platform"
"2008R2ESXI2","Win32_ComputerSystem","Model","VMware Virtual Platform"
"SERVER2008","Win32_ComputerSystem","Model","VMware Virtual Platform"
"SERVER2012","Win32_ComputerSystem","Model","VMware Virtual Platform"

Using The -CustomSql Parameter

The default WQL query is "SELECT prop1, prop2 FROM Win32_ClassHere", while the -CustomSql parameter lets you append something like: WHERE DriveType="3". This custom or extra WQL will be used in all the queries, so if the property/condition doesn't apply to other classes, you will see errors in those other classes

Here's an example where I filter on the condition I mentioned above (drive type is "3").

PS C:\prog> .\Get-WmiObject-Wrapper.ps1 -Comp $Servers[0..3]
-MultiClassProperty 'Win32_Volume:DriveLetter,DriveType'
-CustomSql 'WHERE DriveType="3"' -OutputFile .\foo6.xml -Clobber

Script start time: 04/20/2013 05:17:38
Processing server2008...
Successfully saved '.\foo6.xml'
Script start time: 04/20/2013 05:17:38
Script end time:   04/20/2013 05:17:39

Exposed data hash as $Global:WmiData.
Access it with "$WmiData.GetEnumerator()" from the shell.

PS C:\prog> .\Gwmi-Wrapper-Report.ps1 -XmlFile .\foo6.xml | ft -a

ComputerName     Class         Property     Value
------------     -----         --------     -----
{2008R2ESXI}     Win32_Volume  DriveLetter  { C:}
{2008R2ESXI}     Win32_Volume  DriveType    {3 3}
{2008R2ESXI2}    Win32_Volume  DriveLetter  { C:}
{2008R2ESXI2}    Win32_Volume  DriveType    {3 3}
{server2008}     Win32_Volume  DriveLetter  {C:}
{server2008}     Win32_Volume  DriveType    {3}
{SRV2003R2ESXI}  Win32_Volume  NoPing       {No ping reply}

Accessing The Exported $WmiData Object

In case you need to do something Gwmi-Wrapper-Report doesn't support, I expose the $WmiData hash.

So we run something like this to get data:

PS C:\prog> .\Get-WmiObject-Wrapper.ps1 -Comp $Servers -MultiClassProperty
   'Win32_ComputerSystem:Model | Win32_Volume:DriveLetter |
    Win32_OperatingSystem : Caption, Version' -OutputFile \\server2012\prog\foo.xml -Clobber

Script start time: 04/20/2013 02:18:31
Processing WIN8ESXI...
Successfully saved '\\server2012\prog\foo.xml'
Script start time: 04/20/2013 02:18:31
Script end time:   04/20/2013 02:20:33

Exposed data hash as $Global:WmiData.
Access it with "$WmiData.GetEnumerator()" from the shell.

And we can see that it is available, as stated, in the shell:

PS C:\prog> $WmiData.GetType().FullName
System.Collections.Hashtable

PS C:\prog> $WmiData.GetEnumerator() | Select -First 5

Name              Value
----              -----
SERVER2008        @{Win32_OperatingSystem =; Win32_ComputerSystem=; Win32_Volume=}
WINXPSSD          @{Win32_OperatingSystem =; Win32_ComputerSystem=; Win32_Volume=}
WIN8ESXI          @{Win32_OperatingSystem =; Win32_ComputerSystem=; Win32_Volume=}
esxi              @{Win32_OperatingSystem =; Win32_ComputerSystem=; Win32_Volume=}
WINXPESXI         @{Win32_OperatingSystem =; Win32_ComputerSystem=; Win32_Volume=}

I think using the Gwmi-Wrapper-Report.ps1 script is usually better, but I'll briefly demonstrate how you'd access the data. To limit output, I'll index a key (computer name) directly so I only get one computer's results (one I know had successful data retrieval), but you'll usually be working in a ForEach-Object pipeline construct that iterates all the computers.

Let's take a look at what's there.

PS C:\prog> $WmiData.server2008 | Format-List

Win32_OperatingSystem  : @{Caption=; Version=}
Win32_ComputerSystem   : @{Model=}
Win32_Volume           : @{DriveLetter=}

Let's look at the Win32_OperatingSystem class. Here I show two ways of getting at the same data. The first is for use in pipelines (the one you're likely to use) while the other one is for brevity, and I'll use this in the later examples. By the way, in PowerShell v3, you can use the brief syntax also in pipelines. In v2 you will need to "select -ExpandProperty".

PS C:\prog> $WmiData.server2008 | select -exp Win32_OperatingSystem

Caption                                                     Version
-------                                                     -------
@{0=Microsoft® Windows Server® 2008 Standard }              @{0=6.0.6002}

This is the shorter version:

PS C:\prog> $WmiData.server2008.Win32_OperatingSystem

Caption                                                     Version
-------                                                     -------
@{0=Microsoft® Windows Server® 2008 Standard }              @{0=6.0.6002}

You might notice the note property name "0" (zero), which is there to support multiple-value property data.

PS C:\prog> $WmiData.server2008.Win32_OperatingSystem.Caption

0
-
Microsoft® Windows Server® 2008 Standard

PS C:\prog> $WmiData.server2008.Win32_OperatingSystem.Caption.0
Microsoft® Windows Server® 2008 Standard

Multiple values will be numbered starting from zero.

PS C:\prog> $WmiData.server2008.Win32_Volume.DriveLetter

0                                       1
-                                       -
C:                                      D:

Get the count of properties in a way you might be likely to use it:

PS C:\prog> $WmiData.server2008.Win32_Volume.DriveLetter |
>> %{ $Count = ($_ | gm -type noteproperty).Count; $Count }
>>
2
Personal tools