From Svendsen Tech PowerShell Wiki

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

This has been tested with PowerShell versions 2, 3 and 4.

If you want to retrieve and process data from a large number of computers using WMI - this is probably what you're looking for - unless your needs are very simple. The asynchronous version 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.

In hindsight: It is probably best to make the job/runspace count you specify with -InvokeAsync divisible by 2. The default is 32. They usually have a very low memory footprint.

There is some information about runspaces here and here - and other places on the web.

You can expect the script to process somewhere between 100-500 servers/workstations per minute with the default of 32 threads. There might be considerable CPU usage when starting and processing many threads.

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 in those cases 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' }

... or do the necessary processing with Gwmi-Wrapper-Report's parameters (see table).

The scripts support multiple returned WMI property values, an important feature that was lacking in earlier versions. 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. There's a caveat with the -CombineComputerName parameter, which is that it in PowerShell version 2 only works in combination with the -AsCsv switch. In PS v2 you can't return objects directly, you need to use -AsCsv, pipe to Set-Content, and then use Import-Csv. Everything works fine in v3 and v4, and presumably later versions of PowerShell.

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

A little taste of the version before the third major rewrite:





Tweet





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:

Downloads

This project now also lives on GitHub: https://github.com/EliteLoser/Gwmi-Async

Get-WmiObject-Wrapper-Async.ps1.txt (right-click and "save as"). For earlier versions: File:Get-WmiObject-Wrapper-Async.ps1.txt

Get-WmiObject-Wrapper.ps1.txt (right-click and "save as"). For earlier versions: File:Get-WmiObject-Wrapper.ps1.txt.

Gwmi-Wrapper-Report.ps1.txt (right-click and "save as"). For earlier versions: File:Gwmi-Wrapper-Report.ps1.txt.

2014-12-21: Various tweaks for Gwmi-Wrapper-Report. New asynchronous script uploaded. Major rewrite. Progress is displayed using Write-Progress. Alternate credentials are supported.

2014-08-23: Fixed so that Gwmi-Wrapper-Report no longer displays "{System.Object[]}" for values with only one empty string.

2014-06-21: Gwmi-Wrapper-Report now has a (tremendously useful) -CombineComputerName parameter (PS v3 required unless you use -AsCsv).

2014-05-01: Gwmi-Wrapper-Report now supports/handles empty strings as values.

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 (2014-12-21: changed to 32). It uses runspaces in a runspace pool which comes with some possible flaws, but it has been working 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. -CustomWql 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: 32. -Credential Specify alternate credentials using a PSCredentials object (Get-Help Get-Credential). -Domain Specify the target computers' domain for use with alternative credentials specified with -Credential. Both short and long forms should work. If you specify a domain in the credential object passed to -Credential, you can skip specifying it again with this parameter (omit it), since I built in a check for it.

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). -ComputerNameNotMatch Optional. Specify computer or computers to filter out. Computer names matching those you specify here will not be displayed. Really an array of regular expressions (where a complete match is needed). -ClassNotMatch Optional. Specify WMI class or classes to filter out. Classes matching those names you specify here will not be displayed. Really an array of regular expressions (where a complete match is needed). -PropertyNotMatch Optional. Specify property or properties to filter out. Properties matching those names you specify here will not be displayed. Really an array of regular expressions (where a complete match is needed). You often want to pass "Error, NoPing" to this parameter when using -CombineComputerName, so the headers come out correctly on-screen (fixed in PSv4! It'll show all columns aggregated, not just those of the first object). -CombineComputerName Optional. Combine all entries for a computer name on one line. Each class and property will be combined as the new property name, with "Win32_" at the start of the class name omitted to save screen space. Requires PowerShell version 3 or higher, unless you use -AsCsv, which works with v2 as well. -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 or -CombineComputerName. 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

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 -CustomWql Parameter

The default WQL query is "SELECT prop1, prop2 FROM Win32_ClassHere", while the -CustomWql 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' -CustomWql '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