Compound Filtering with WMI and PowerShell

During the course of the last few articles, we’ve been looking at how to use PowerShell and WMI to query disk information for the %SYSTEMDRIVE% even when you don’t know what that value might be. In this article, I want to introduce a new requirement, which will offer the opportunity to explore some new concepts. If you are just joining us, take a few minutes to get caught up or you won’t understand everything in this article.

When I left you, I had some PowerShell code to query a list of computers using Get-WMIObject. To improve performance, we did a quick ping test to only use WMI for computers that we verified online. For today’s article, I have 11 computers in my list and some of them are unavailable. The new requirement is that need to know which servers have 5 GB or less of free space on the system drive. I don’t need to see any other servers.
If you are new to PowerShell, you might be inclined to modify the last code example and pipe results to Where-Object.

Filtering for freespace (Image Credit: Jeff Hicks)
Filtering for freespace (Image Credit: Jeff Hicks)
You can see that it works, and it took almost 20 seconds to execute in my network. Because I am writing an object to the pipeline with a custom property, FreeGB, I need to use that property in the Where-Object filtering scriptblock. When you use Where-Object like this, it is an example of late filtering. In other words, no filtering is done until the initial collection of objects, in this case disk objects, is complete. There's nothing necessarily wrong with late filtering. Sometimes that is your only option. But if you have an opportunity to filter at the point of collection you typically see some performance gains. This is referred to as early filtering. We are already filtering the Win32_LogicalDisk class by its deviceID to limit the query to the system drive. We can easily add another criteria to the filter using the AND operator. I know it gets confusing but remember that we use the legacy operators in a WMI query not the PowerShell ones. Here's a simple proof of concept.
Testing early filtering (Image Credit: Jeff Hicks)
Testing early filtering (Image Credit: Jeff Hicks)
If the system drive's freespace is greater than 5 GB, then nothing will be written to the pipeline, so all I get are those servers that meet my criteria. Note that because the freespace value is in bytes, I need to specify the comparison value in bytes. I got that number by simply typing 5 GB and a prompt and copying that value. But you can save yourself a step with a subexpression.
With this knowledge, let's revise our code to apply an early filter.
I get the same results as I did with late filtering, but this only took about 17 seconds. Remember some of that time is taken up by pinging each computer as I go along. If I limit my command to computers that I've already verified are online, this gets better.
Using $pinged instead of piping $computers to Test-Connection ran 6.2 seconds using late filtering and around 4.5 seconds using early filtering. Although I also moved the Select-Object expression back inside and that seemed to improve times as well.
$pinged | foreach {
#define a named variable for the computername so that it can be used the Catch
$computer = $_
Try {
  $os = Get-WmiObject win32_OperatingSystem -computername $computer -ErrorAction Stop
  #continue if there were no errors
  Get-WMIObject Win32_Logicaldisk -filter "deviceid='$($os.systemdrive)' AND freespace <=$(5GB)" -ComputerName $computer |
  Select PSComputername,DeviceID,
  @{Name="SizeGB";Expression={$_.Size/1GB -as [int]}},
} #Try
Catch {
  #$_ is the error object
  Write-Warning "Failed to get OperatingSystem information from $computer. $($_.Exception.Message)"
} | Format-Table –AutoSize

There are several mitigating factors surrounding performance with this solution. First, I’m making two WMI connections to the remote computer. I am also limited by the DCOM and RPC network connection to each computer in $pinged. Next time we’ll see what options we have to make this even better. In the meantime, I strongly encourage you to get in the habit of filtering as early in your PowerShell expression as you can. Look for parameters that will help limit what is written to the pipeline to only what you need. In addition to the obvious filter parameter, look for parameters like Name, Include and Exclude as be sure to read cmdlet help to learn how to use them effectively.