Use PowerShell to Simplify Access to WMI Data

Use PowerShell to Simplify Access to WMI Data

  • Comments 3
  • Likes

Summary: Microsoft Windows PowerShell MVP, Richard Siddaway, teaches how to use Windows PowerShell to simplify access to WMI data.

Microsoft Scripting Guy, Ed Wilson, here. I am really excited about the idea I had for this week, and I hope you will be too. I asked Candace Gillhoolley at Manning Press about posting some sample works from some of the Manning Press library of books. She responded enthusiastically and shared five samples that we will post this week. Today we have Richard Siddaway and PowerShell and WMI.

PowerShell and WMI

Image of book cover

By Richard Siddaway

WMI isn’t a perfect technology and we need to know how to overcome these imperfections. In this article based on chapter 4 of PowerShell and WMI, author Richard Siddaway explains, among other things, how to properly configure systems to ensure WMI works correctly, how to control access to resources, and how to reduce the amount of data to make scripts more efficient. To save 35% on your next purchase use Promotional Code siddaway20435 when you check out at http://www.manning.com/.

manning

Optimizing WMI Use

WMI isn’t a perfect technology and we need to know how to overcome these imperfections. In this article, we will look at the ones most likely to cause problems and how we can overcome them.

We need to make a number of configuration settings on our systems to ensure that WMI will work correctly. We also need to consider how Windows PowerShell remoting impacts WMI.

Controlling access to resources is an important part of system administration. WMI is no exception. The cmdlets will negotiate authentication whenever possible, but, occasionally, we have to override the defaults.

The final parts of this article cover data filtering and conversions. Reducing the amount of data we work with will make our scripts more efficient. WMI doesn’t always return data in the best of formats and we need to know how to convert the results into a format we can work with.

Issues

Two major issues stand out when working with WMI:

  • Documentation
  • Number of classes available

Some classes are not documented or the documentation hasn’t been brought up to date as changes have been made. The primary issue with WMI is one of documentation; for instance, the ‘root\cimv2’ namespace is well documented but the classes in the ‘root\wmi’ namespace are undocumented. For example, I haven’t been able to discover any documentation on the BatteryStatus class. Attempting to read the class description isn’t very helpful.

Get-WmiObject -Namespace 'root\wmi' -List BatteryStatus `

-Amended).Qualifiers |

Format-Table Name, value -AutoSize -Wrap

This will return the class description, among other information, but, unfortunately, all we get is the rather terse BatteryStatus. We can make some guesses based on the class name and the property names we can find using

Get-WmiObject -Namespace 'root\wmi' -Class BatteryStatus |

Get-Member

An Internet search doesn’t reveal much more useful information. There appears to be a number of potentially useful classes in this namespace but our usage is hampered by the lack of documentation.

The available classes aren’t always well publicized. Did you know that there is a set of WMI classes for working with failover clusters? They are still present in Windows Server 2008 R2 even though we have Windows PowerShell cmdlets for working with clusters as well! On your nearest cluster try this:

Get-WmiObject –Namespace ‘root\cluster’ –List

The cluster properties can be discovered with

Get-WmiObject –Namespace ‘root\cluster’ –Class MSCluster_cluster

If you have any clusters in your organization, it’s worth checking them out.

Another issue is that some WMI classes don’t return data. PowerShell has one habit that I find annoying and confusing—this lack of return messages. If a PowerShell statement is wrong, sometimes there is no return message. If a WMI class doesn’t return data there is no return message. How do I know if my PowerShell statement has failed or there isn’t any data to return? All you can do is try other ways of formatting the statement.

The second major issue with WMI is the sheer number of available classes. No one can know them all. WMI functionality is constantly changing as Microsoft products evolve. Some of the details though do not evolve as quickly. The list of supported devices in Win32_OnBoardDevice (devices attached to the mother board) hasn’t been updated recently so one of the machines I was testing against had a device that wasn’t included in the list.

WMI works out of the box on the local system, as long as the Winmgmt service is running, but we need to do a little bit of work to enable access to remote systems.

WMI settings

There are a number of settings we need to check and configure before we start working with WMI:

  • Required services are running.
  • Windows firewall is configured for remote access to WMI.
  • PowerShell remoting is enabled.

I would recommend that installing and configuring these items becomes a part of the setup procedure for all new servers.

The first thing we need to do is make sure that the services we need are running. This includes the WMI service.

Get-WmiObject -Class Win32_Service -Filter "Name LIKE 'win%'" |

select Name, state

We are interested in the Winmgmt (WMI) and WinRM services. If they are running we are in good shape. On machines that will be accessed remotely, also check that the DCOM Launcher is running because WMI uses DCOM for remote access.

The introduction of the Windows firewall in the more recent server versions has impacted WMI. We need to open the firewall to allow WMI access. This can be achieved through the GUI or by using the following Netsh commands.

Netsh firewall set service RemoteAdmin

Netsh advfirewall set currentprofile settings remotemanagement enabled

I haven’t found a way to configure the firewall using PowerShell directly yet. This gives us a route to connect to remote machines using WMI. We can now work with our remote machines whether or not they have PowerShell installed.

If we were to perform a significant number of actions on the remote machine, we would frequently be accessing the machine or its Windows Server 2008 R2 with Windows PowerShell 2.0 installed by default, and, therefore, we should enable PowerShell remoting.

This is a simple case of ensuring the WinRM service is running on both machines and that we have run the Enable-PSRemoting cmdlet to perform the configuration. We will also need the WinRm service when we use WSMAN directly.

If a PowerShell WMI cmdlet cannot connect to a remote server, it will time out eventually. This can take a significant amount of time and will add a significant delay to getting results if you are testing a number of remote machines. This process can be accelerated by testing the connection to the remote machine first.

$computer = "server02"

if (Test-Connection -ComputerName $computer -Count 1) {

 Get-WmiObject -Class Win32_Service -Filter "Name LIKE 'win%'" |

 select Name, state

}

else {Write-Host "Cannot connect to $computer"}

We can use the Test-Connection cmdlet, which uses Win32_PingStatus, to test if our machine can be reached. If it is, we perform our actions; otherwise, we write a message stating that we couldn’t contact the machine.

The next step is to make sure that we have the required permissions to access the WMI data on the remote machine.

Authentication

Windows REMOTE ONLY WMI only needs credentials for remote machines. The WMI cmdlets won’t accept a credential when accessing the local system Windows PowerShell 2.0 introduced a number of new parameters for Get-WmiObject, one of which was the Authentication parameter. This parameter is available on the four cmdlets we use with WMI:

  • Get-WmiObject
  • Invoke-WmiMethod
  • Remove-WmiObject
  • Set-WmiInstance

It may seem confusing because there is also a Credential parameter available on these cmdlets. The Credential parameter deals with passing a userid and password for access to the remote system. If credentials aren’t supplied, the cmdlets use the user’s credentials.

Remote only WMI only needs credentials for remote machines. The WMI cmdlets won’t accept a credential when accessing the local system.

Creating the credential is best done using Get-Credential rather than using the Credential parameter, which has been known to cause errors.

$cred = Get-Credential

Get-WmiObject Win32_ComputerSystem `

-Credential $cred –ComputerName dc02

The Authentication parameter deals with DCOM authentication. WMI is COM based and uses DCOM to access remote systems. There are a number of possible settings, as shown in table 1.

Table 1 WMI authentication

Value

Meaning

–1

Unchanged—authentication remains the same.

0

Default COM authentication level. Authentication is negotiated. WMI uses default Windows Authentication setting. An authentication level of 0 (None) will never be negotiated.

1

None. No COM authentication in performed.

2

Connect. COM authentication is performed only when the client establishes a relationship with the server. No further checks are performed.

3

Call. COM authentication is performed only at the beginning of each call when the server receives the request. Only packet headers are signed. No data is encrypted.

4

Packet. COM authentication is performed on all the data that is received from the client. Only packet headers are signed. No data is encrypted

5

PacketIntegrity. All of the data that is transferred between the client and the application is authenticated and verified. All packets are signed. No data is encrypted.

6

PacketPrivacy. The properties of the other authentication levels are used, and all of the data is encrypted.

Under normal circumstances, this parameter doesn’t need to be specified. The default value is applied and negotiates the correct level of authentication. There aren’t many WMI providers that require an authentication level above default. The one you are most likely to meet is the ISS provider, which requires Packet Privacy.

Once we are authenticated to the remote server we can retrieve our data.

Data filtering

We have already touched on the fact that WMI can return a lot of data. This can slow down our scripts, especially if we are returning data from remote machines. It may not impact us if we are running locally but it will do if we are running against a significant number of remote machines.

We have a number of ways to filter our data. All three of the following code snippets will return the same results.

Get-WmiObject -Class Win32_Service |

where {$_.ExitCode -ne '0'} |

select Name, ExitCode, StartMode, State

 

Get-WmiObject -Query "SELECT Name, ExitCode, StartMode, State FROM Win32_Service WHERE ExitCode <> 0" 

 

Get-WmiObject -Class Win32_Service -Filter "ExitCode <> '0'" | select Name, ExitCode, StartMode, State

Our first option uses the Win32_Service class and then filters with Where-Object and Select-Object to restrict the output. This works but it has the drawback of returning all of the Win32_Service objects before we start filtering. When we are working remotely, it means we extract the all of the data, return it to base, and then start filtering.

Technically, the second option will return a bit more because it also returns the system properties; however, all of the filtering is performed as we extract the data. This means we only transport the objects and properties we are interested in (plus the overhead of the system properties). We could perform another select once we get the data back to filter out the system properties.

Our third approach uses a WMI filter to only return the services showing the exit codes that aren’t zero. We then use Select-Object to filter down to the properties of interest.

Which is the best approach? As always, it depends. I would probably go for option 3 because it filters out the objects of no interest and is more easily modified than option 2. I also don’t need to worry about the system properties getting in the way.

Returning just the subset of data we need is good, but we will find that some of the data isn’t in a format that we can easily use.

Data conversions

One of the things about computer systems that stops us getting bored is that every technology we come across seems to have at least a couple of quirks in the way data is stored and handled. WMI is no exception. Most sizes such as disk capacities are measured in bytes and dates are stored in a format that is readable but difficult to work with for example, 20101020191535.848200+060.

Both of these can cause problems but we have fairly simple ways to cope with them starting with sizes.

Sizes

There are a number of places we need to deal with sizes including memory, file and folder sizes and disk sizes. WMI nearly always returns sizes in bytes which isn’t the most understandable of formats. If you are presented with a disk that has 118530666496 bytes of free space, how quickly can you work out what that means? A lot slower than understanding 110.39 GB I’ll bet.

Windows PowerShell understands the same sizes from kilobytes to petabytes as we do as administrators. We can illustrate the point by running the following code.

1kb, 1mb, 1gb, 1tb, 1pb |

foreach {"{0,16}" -f $_ }

We pipe a collection of size constants into a foreach cmdlet, where we display the value in right-justified fields using .NET string formatting and the –f operator. Notice that we can’t just use kb etc. we have put a numeric value in front of the constant. The value doesn’t have to be an integer; 1.5 Mb is understood as 1572864.

A calculated field can be used to display the result of changing a value in bytes to one in another unit such as gigabytes. Compare the output produced by the two options presented in the following snippets.

Get-WmiObject -Class Win32_ComputerSystem |

format-list Manufacturer, Model, TotalPhysicalMemory

Option 1 uses the Win32_ComputerSystem class and produces a list including manufacturer, model, and the total amount of memory. This is in bytes. If we are testing machines to ensure they have sufficient RAM for a new piece of software, we need to compare against the vendor specifications which will be in megabytes or gigabytes.

Get-WmiObject -Class Win32_ComputerSystem |

format-list Manufacturer, Model,

@{Name="RAM(GB)";

Expression={$([math]::round(($_.TotalPhysicalMemory / 1gb),2))}}

Option 2 provides a more usable display. The manufacturer and model are displayed as before. This time we use a calculated field to divide the memory by 1GB and round the result to two decimal places. Much easier to understand and compare against the specifications. I’m in the middle of such an exercise to check the specifications on 150 machines as I write this section and I’m using the approach shown in option 2. It does work!

The other area where WMI causes problems with data is when we consider dates.

Dates

WMI has its own format for dealing with dates. This can be seen in figure 1, where we use the Win32_OperatingSystem class to determine the last time the machine was booted. There are other ways of measuring the time a machine is started, including checking when the event log service was started, but I find this one more accurate. The date is returned as year, month, day, hours, and seconds. This is followed by a value indicating the number of microseconds and the time zone.

I can read it and figure it out, but I can’t easily work with it.

Image of command output

Figure 1 Using the WMI date conversion methods

Use Get-Member on the Win32_OperatingSystem class and you will find two script methods added to the object (they are also present on all WMI objects that return dates). These are:

  • ConvertToDateTime
  • ConvertFromDateTime

We will be converting WMI values to “normal” date formats most, so we will start with that. The second line of code in figure 1 shows how this works.

Get-WmiObject Win32_OperatingSystem |

select @{Name="BootTime";

 Expression={$_.ConvertToDateTime($_.LastBootUpTime)}}

Call the method on the object on the pipeline and use the object’s LastBootUpTime as the argument. It looks a bit messy but, if it is something you are going to use a lot, put it into a function that is loaded into PowerShell from your profile.

I use the ConvertToDateTime method much more than the ConvertFromDateTime method. Since the method is available, it would be rude not to check how it works.

$date = [datetime]"8 October 2010 19:46:00"

Get-WmiObject -Class CIM_DataFile -Filter "Path='\\Test\\'" |

where {$_.LastModified -gt ($_.ConvertFromDateTime($date))}

A .NET DateTime object is created. The format used to present the date and time is usable in any locale, which makes it the safest if your code is likely to be used in different countries. If you post it to the Internet, expect it to be used in different countries.

The CIM_DataFile class is used to retrieve the files from the c:\test folder. Note how we have to use a \\ to represent a single \ in the folder path. This is because the \ symbol is an escape character in WMI. A PowerShell filter is used to test the LastModified parameter against our date. We have to convert the date into WMI format for each test.

Filtering and formatting Combining the information about data filtering and data formatting leads to a simple rule of thumb: filter as soon as possible and format as late as possible.

Summary

WMI is a huge piece of very powerful technology. It still has some rough edges which we need to know how to deal with to get the most from WMI.

We need to address the following key issues and solutions:

  • WMI documentation may be out of date or nonexistent.
  • It can be difficult to discover what classes are available.
  • The Windows firewall must be configured to allow WMI access.
  • Use Test-Connection rather than wait on multiple timeouts.
  • Some providers such as IIS and Microsoft Clustering require authentication for remote access.
  • Filter early and format late.
  • Size and date conversions are simple with PowerShell.

The ideas and techniques presented in this article will help you get the most out of WMI. The only issue that isn’t in our control is the level of documentation, and that can be overcome as people experiment and discover how to use these classes. If you do work out how to use one of the undocumented classes, please share it with the Windows PowerShell community so that we can all benefit.

Thank you Richard. Join us tomorrow for a sample from Rod Colledge as Guest Writer’s week continues.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • <p>Nice article, but there was a typo Get-WmiObject –Namespace ‘root\cluster’ –List should be Get-WmiObject -Namespace &quot;root\mscluster&quot; -List</p>

  • <p>Good article.</p> <p>I believe the BatterStaus class only exists if you are running a WIndows supported UPS.</p> <p>See:</p> <p><a rel="nofollow" target="_new" href="http://blogs.technet.com/b/heyscriptingguy/archive/2007/04/09/how-can-i-tell-whether-a-laptop-computer-is-running-off-batteries.aspx">blogs.technet.com/.../how-can-i-tell-whether-a-laptop-computer-is-running-off-batteries.aspx</a></p>

  • <p>Sorry - I jumped without thinking. &nbsp;It&#39;s not UPS - I should have typed that it is available on a laptop. &nbsp;The link is still valid.</p> <p>(thinking of too many things at once)</p>