Filtering PowerShell with the Where Method

PowerShell-Text-Purple-hero
Over the course of a few articles, I have demonstrated a variety of filtering techniques and strategies. I want to wrap up our exploration with a filtering method you may not know about as it is not well documented. And it is just that, a method. There Where() method was introduced in PowerShell 4.0 for Desired State Configuration (DSC) but you can use it in your everyday PowerShell work. Everything you know about the PowerShell Where-Object cmdlet still applies.

You normally would think about running a command like this:

get-service  | where status -eq 'stopped'

But the Where() method belongs to any object collection.

(get-service).where({$_.status -eq 'stopped'})

This produces the same result in a fraction of the time. The syntax looks like:

<a collection objects>.where({<filtering script with $_})

If you think you might need to so do several filtering operations, you can save the initial results to a variable first.

$p = Get-Process

Then you can can filter like this:

$p.where({$_.ws -ge 80mb})

Although most of the time (at least the way that I work) we know in advance that filtering will take place.  By wrapping the command in parentheses, we’re telling PowerShell to hold the results as kind of an in-memory variable. This Where method really shows its worth with large collections.

(dir c:\scripts -file -Recurse).Where({$_.length -ge 5MB})

I could run this with the traditional Where-Object and get the same result but this is slightly faster.
But there’s more than mere performance here. Take a look at this:

$a,$b = (Get-Service).Where({$_.status -eq 'stopped'},"Split")

Not only am I filtering but I’m also telling the Where() method to split the results. This selection mode will filter accordingly and write the commands that match the filter to the pipeline first. It will then pipe everything that didn’t match to the pipeline. My PowerShell syntax will thus have all stopped services in $a and everything else in $b.
You can also get the first or last matching filtered object.

(Get-Service).where({$_.status -eq 'stopped'},"First",1)
filtering and selecting with Where()
Filtering and selecting with Where() (Image Credit: Jeff Hicks)

This is equivalent to this PowerShell expression:

Get-Service | where {$_.status -eq 'stopped'} | Select -first 1

But not nearly as much fun!

Filtering for last with PowerShell
Filtering for last with PowerShell (Image Credit: Jeff Hicks)


In addition to Split, you can specify an operation mode of Until, Skip and SkipUntil. Frankly, I have not had much of a need for these and they are best demonstrated with a simple collection of numbers.

(1..10).where({$_ -eq 5},"until")
Filtering UNTIL
Filtering UNTIL (Image Credit: Jeff Hicks)

Hopefully that is self explanatory.

(1..10).where({$_ -eq 5},"skip")
Filtering SKIP
Filtering SKIP (Image Credit: Jeff Hicks)
(1..10).where({$_ -eq 5},"skipuntil")
Filter SKIPUNTIL
Filter SKIPUNTIL (Image Credit: Jeff Hicks)

The utility of these options I’m assuming depends on what you are filtering. And to cover everything, there is yet a third parameter to the Where() method which I have only made a passing reference up to now. This is the more complete syntax.

<a collection objects>.where({<filtering script with $_},[split|skip|skipuntil|until].[number of items to return])

The default is to return all objects but you have fine control.

(1..10).Where({$_ -gt 5},"skip",1)

If you try this out, and I hope you will, PowerShell writes 6 to the pipeline. Or in other words, I told PowerShell to “find all the numbers greater than 5 and display the first one that matches the criteria.” As I mentioned, I haven’t found many practical examples for these Where() method parameters, but maybe you have something in mind. If so, I’d love to hear about it.

The Where() method is a smart addition to your PowerShell scripts when working with a large collection of objects. I think you’ll see a noticeable improvement in performance. Remember this requires PowerShell 4 and later so you should add a #Requires -version 4.0 comment to your script file.
In the mean time, give all of this a spin and let me know what you think.