Someone asked a question in the #PowerShell channel on irc.FreeNode.net today about how to use filters, and pasted this an example like this:
The question was: why doesn’t this have any output? Well, the answer is: it can’t have any output. The filter outputs true or false for each item passed into it, but the where scriptblock doesn’t actually pass anything into the filter! Of course we quickly fixed his problem the right way by rewriting the filter to output the items, and getting rid of the where-object call completely … but when I started wondering where he had gotten the bizarre idea to use a filter like that, his answer was: “I read about_filter.” So I went and looked, and sure enough, the example pasted above is straight out of about_filter. And it’s as wrong as it can possibly be.
Let me fix it, and then offer some clarification below. In about_filter there is an example like this: Get-Process | where {$_.processname -like "[a-m]*"} and then a table describing each element of the Where-Object command, and just below it, there is some text about the filter command. Mentally replace that text with this:
You can create a predefined filter by creating a special type of function. When defining the function, you may specify that it is a filter. For the filter script block, you should output items if they pass the test you saw in the preceding example. For instance, the following command creates a filter named process-a-m:
filter process-a-m
{
if( $_.processname -like "[a-m]*" ) { Write-Output $_ }
}You can then use the filter name in place of your Where-Object command to retrieve the filtered data, as shown in the following example:
Get-Process | process-a-mThe values returned by the command are the same as they would have been had you specified the conditions using the Where-Object cmdlet as above, but the filter is much easier to reuse on multiple calls, particularly if the conditions were more complicated.
Of course, a filter function can take parameters, just like any other function, so you could make the range of starting letters more flexible:
filter process-range([char]$a="a",[char]$m="m")
{
if( $_.processname -like "[$a-$m]*" ) { Write-Output $_ }
}Now you can use this filter to select between any two letters quite easily:
Get-Process | process b j
To be clear, functions in PowerShell aren’t like functions in other programming languages, although they can just have a body (and be like other functions) they can instead have pipeline syntax. A function (just like a cmdlet) which is meant to be used in the pipeline has three separate bodies: begin, process, and end, but a filter has only the process part of the body.
Of course, there are other uses for filters than just limiting which objects come through … anything that you could do with foreach-object or where-object, you can basically do with a filter in a much more reusable way. So you don’t have to write the same thing to the output as what came in the input, you could actually add to it, or remove from it. A filter for processes could look use Win32_Process to look up their ‘owner’ and append it as a property, or could return the process path and name as text …
If you’re new to all of this, let me just present an example function to hopefully clear things up:
A function without an internal pipeline block is basically like the begin block of a pipeline-enabled function: the only things present are the parameters to the function (if there are any). Then, process is called once for each item in the pipeline, with the current item set as the $_ variable. Finally, end is called, and you still have the last item in the pipeline present. The scope persists through each part of the function (so you will see that $counter maintains it’s value). The important distinction between this and a function without a process body is that a normal function will only be called once when it is in a pipeline — regardless of how many things are in the pipeline. If you called DemoTheCount with a list of processes, like Get-Process g* | DemoTheCount, you would get something like this (depending on the processes running on your PC):
A filter doesn’t have separate parts, because the whole filter is the process part of the function. If we were to take the process portion of our DemoTheCount function and turn it into a filter, it would only work because the dynamic language would automatically initialize $counter to zero and let you increment it — anything more complex (or where the type couldn’t be deduced) would not. When you create a filter using the filter keyword, it’s pure syntax sugar. The filter process-a-m defined in my correction to the documentation is exactly the same as this function:
You can verify this by creating both of them and then comparing the contents of the resulting functions ((get-content function:\process-a-m) -eq (get-content function:\reprocess-a-m). The filter keyword simply creates a function and with the filter body as the process body for the function.