Filtering PowerShell

PowerShell is all about working with objects. (If you are fairly new to PowerShell objects, read Introduction to Objects in PowerShell on Petri.) The commands you run in PowerShell such as Get-Service or Get-ADUser are designed to connect to something and create an object that represents something you care about such as a service or user account. Most of the time we don’t care how these commands work, only what the output looks like and what we can do with it. Some commands can return hundreds if not thousands of objects and often you only really need small subset. To get the small subset, you need to perform some type of filtering. For many PowerShell users, that means using the Where-Object cmdlet, which has an alias of where.

There is nothing wrong with using Where-Object as long as you understand the implications. Without going into the intricate details of the PowerShell pipeline, consider that Where-Object can’t really do anything until the preceding command finishes. This means that if the first command takes 30 seconds to complete and then you want to filter, you have to wait. Depending on the preceding command you may not have any choice. But when I train IT pros in PowerShell, I always stress the importance of filtering as early in your pipelined expression as you can. This is often referred to as filter left.

Filtering Parameters

The interesting thing about filtering is that it doesn’t require a –filter parameter like you see with Get-CimInstance or Get-WmiObject.  There are many cmdlets that provide other parameters you can use to limit or restrict output. For example, many cmdlets have a –Name parameter. Sometimes you can even use a wildcard. You might also look for parameters such as –Include or –Exclude. The syntax for these parameters varies depending on the cmdlet so you need to read full cmdlet help and look at the examples. Sometimes it takes old-fashioned trial and error experimentation. But it matters.
In fact, let’s look at a PowerShell example that has nothing to do with our day job so we can focus on the concepts and not get distracted. Here’s a command called Get-Vegetable that writes vegetable type objects to the pipeline.
getting vegetable objects with PowerShell
The vegetable object has a boolean property you don’t see by default called IsRoot to indicate if the item is a root vegetable. Let’s say we wanted to filter out everything that wasn’t a root vegetable. You could run a command like this and it would work just fine.
filtering with Where-Object
This took 7 milliseconds to complete. Granted that isn’t much, but we’re only working with 16 vegetables. Imagine if it were 1600 or 16000 then the time spent starts adding up. In this example, being the pro-active IT pro that you are, you decide to see if there is a better way. Looking at help for Get-Vegetable you discover a few parameters.
looking for filtering parameters
Trying the parameter works and takes less than 2 milliseconds.
filtering with a parameter
However. depending on what you need to get, you may still need to use Where-Object. For example, let’s say we need to get all root vegetables that are still raw.

Get-Vegetable -RootOnly | where {$_.cookedstate -eq 'raw'}

This is preferable, and faster than an expression like this:

Get-Vegetable | where {$_.IsRoot -AND $_.cookedstate -eq 'raw'}

Both commands will give you the same result but the first one will be faster. You’ll hopefully also noticed that the filtering expression used the CookedState property. That’s because the actual property name is CookedState and not Status. Many commands have default formatting that use custom headings. You filter with any property you see with Get-Member.
Viewing properties with Get-Member
Finally, lets consider this with a more real-world example.

Using Get-CimInstance, let’s find all the services that have a start mode of Auto.

get-ciminstance -ClassName win32_service | where {$_.startmode -eq 'Auto'}

On my computer this takes about 200 milliseconds. But WMI has a filtering mechanism that we can use with Get-Ciminstance.

get-ciminstance -ClassName win32_service -filter "Startmode='Auto'"

This was slightly faster at 160 milliseconds. The major difference is that filtering is done at the source or at the point of collection. If you are querying a remote computer that means that the only results that come back across the wire are the filtered results. Contrast this with the first example that has to send all service objects back and then filtering occurs. Now imagine doing this for 50 servers and the performance implications begin to add up.
However, there are always exceptions. Let’s say I want to find all .ps1 files in my Scripts directory that are larger than 50KB. Beginners might be tempted to use an expression like this:

dir c:\scripts -Recurse | where {$_.Extension -eq '.ps1' -AND $_.Length -gt 50KB}

This will work but takes about 850 milliseconds. Based on what I’ve been discussing, you might expect this version to be faster.

dir c:\scripts\ -include *.ps1 -Recurse  | where {$_.Length -gt 50kb}

On my computer it is slower. This version takes 1050 milliseconds to complete. That’s why PowerShell has the Measure-Command cmdlet to test for yourself.

measure-command { dir c:\scripts\ -include *.ps1 -Recurse  | where {$_.Length -gt 50kb} }

Most of the time the differences with different filtering techniques might appear insignificant. But once you gain more experience and begin to manage more and more of your enterprise with PowerShell, you’ll come to appreciate the benefits of filtering early so you might as well get in the habit and do it now.

Here are a few other filtering topics related to this article: More PowerShell Filtering Options and Filtering PowerShell with the Where Method.