Payette_00

By Richard Siddaway and Bruce Payette

PowerShell V4 introduced two new operators for working with collections, Where() and ForEach(). In this article, excerpted from Windows PowerShell in Action, Third Edition, we’ll tell you about them.

PowerShell V4 introduced two new operators for working with collections. While their syntax is identical to method invocation syntax, they are called “operators” since they aren’t actually implemented as methods on the target object. The Where() and ForEach() methods work in a manner similar to the Where-Object and Foreach-Object cmdlets. We’re including them with the operators because of the way they are used.


Where method

The Where() method provides a way to filter collections using a condensed syntax. In all cases, using the Where() method is faster (up to 10 times faster) than using Where-Object, but consumes more memory. This is because the cmdlet goes through the parameter binder which is very complex. The method binder is much simpler and therefore faster. The foreach loop, however, is still the fastest way to iterate over a collection. The syntax will be more familiar to programmers than administrators so we’ll explain it with some examples.

Consider a standard use of Where-Object:

Get-Process | where Handles -gt 1000   

The collection of processes is filtered and only those processes with more than 1000 handles are returned. You can use the Where() method to achieve the same result:

(Get-Process).where({$_.Handles -gt 1000})
 (Get-Process).where({$psitem.Handles -gt 1000})

You must use either $_ or $psitem to with the property on which you are filtering. The () are optional but we recommend you use them to make the syntax more obvious when you come to review it – or you’re writing for others to use. We’ll use the () in the rest of this section to make the syntax more obvious but as an example of not using them you can write the previous two examples as:

(Get-Process).where({$_.Handles -gt 1000}) (Get-Process).where({$psitem.Handles -gt 1000})

Note Bruce made a change to the parser in PowerShell v4 to allow any methods that takes a single scriptblock as an argument to be written without parms around the scriptblock literal. The change was made for these methods but works with any method. Also a note: these methods were added to simplify node selection in the DSC node statement.

Qualifiers can be applied to display the first or last member of the collection:

(Get-Process).where({$_.Handles -gt 1000}, 'First')
 (Get-Process).where({$_.Handles -gt 1000}, 'Last')

This can be extended to the first or last N members:

PS (6) > (Get-Process).where({$_.Handles -gt 1000}, 'First', 3)
 (Get-Process).where({$_.Handles -gt 1000}, 'Last', 3)

There is an option to split the results:

$proc = (Get-Process).where({$_.Handles -gt 1000}, 'Split')    

$proc is a collection – the first member contains the processes that match the filter and the second member those that don’t.

Note It’s actually an instance of [System.Collections.ObjectModel.Collection[PSObject]. The fact that it is a collection matters since you can add members to a collection but not to an array. A secondary aspect is that, because it’s always the same type, you can write additional extension methods on this type to do Linq-like collection operations on the result of the type.

You can further filter the results using Until and SkipUntil:

(Get-Process | sort Handles).where({$_.Handles -gt 1000}, 'Until')

Using Until will display all results until you reach results that match the filter defined in the scriptblock. If you only want to display the results that match the filter then use SkipUntil:

(Get-Process | sort Handles).where({$_.Handles -gt 1000}, 'SkipUntil')

If you don’t sort the members of the collection SkipUntil will display everything after the first match irrespective of whether it matches the filter or not.


ForEach method

The ForEach() method is a bit simpler than the Where() method you’ve just seen. Again the using this method is best achieved by some examples. First, create an array of integers:

$data = 1,2,3,4,5

You can execute a script block within the ForEach() method:

($data).ForEach({$_ * 2})
 $data.ForEach({$_ * 2})

When the data is already an array you don’t need to wrap it in (). If you need to change the type of the objects in the collection:

$data | Get-Member
 $data.ForEach([double]) | Get-Member

Values for a particular property can be displayed.

(Get-Process).foreach('Name')

If the objects within the collection have methods, they can be invoked:

(Get-Process -Name notepad).foreach('Name')
 (Get-Process -Name notepad).foreach('Kill')

You can also pass arguments into the method if required.