Filter Using Parameters Instead of Where-Object When Possible

This was a post I intended on writing during/after the 2012 Scripting Games, but just never got around to finishing it up. While it is great to know for the games itself, it also applies to everyday scenarios for folks just coming up into PowerShell and for those that have been doing this for a while.

When writing a one-liner or a script in PowerShell, the recommended practice is to filter as far left as possible before doing anything else to bring down the amount of data to be as small as possible. Typically we use the following process:

Filter | Select/Sort | Format

When writing a script or a function it is important to know the capabilities of existing cmdlets, in this case whether it has the ability to filter for a specific item. And I don’t mean that it has just a –Filter parameter, but other types of parameters that can be used to filter the data being returned. A lot of times the old standby is to take the results of a cmdlet and pipe it into Where-Object to filter for the data that we want.

Most of the time, this is the way to get what you need, but it is also very beneficial to you to spend a short time looking at the parameters on a specific cmdlet just to see if there is something that will help you to get the data you need. While the time difference may not always be that great, it does help the code by taking out the un-needed Where-Object filter. Also it is important to note that times will vary on your system based on resources, bandwidth, etc… as well as what it is you are trying to filter. In other words, you may not see astronomical differences in some commands that you might see in others. Regardless, it still a good practice to get into using what the cmdlet has to offer as it is using the OS APIs to handle the filtering which is especially useful when run against remote systems as all of the filtering is happening on the remote end rather than having to bring all of the data back. On top of bringing all of that data back, you are then using the local system resources for filtering.

Examples

For instance, lets look at using Get-WMIObject with both a Where-Object filter and then using the same command but instead using the builtin –Filter parameter against a simulated 25 remote systems (by simulated, I mean the same remote system 25 times).

For the sake of getting command times, every command will be wrapped in Measure-Command {}.

 Measure-Command {
    1..25 | ForEach {
         Get-WmiObject -Class Win32_Service -ComputerName DC1 | Where {
            $_.State -eq 'running'
         }
    }
}

 

image

13 seconds against 25 systems…not too bad, but lets see what happens with the –Filter parameter.

Measure-Command {
    1..25 | ForEach {
        Get-WmiObject -Class Win32_Service -ComputerName DC1 -Filter "State='running'"
    }
}

image

Now that is better. It went from 13 seconds down to 6 seconds by using the built-in –Filter parameter.

What is happening here is that where the Where-Object will take each complete collection of objects from the remote system and then does the filtering, which is time consuming and a waste of resources. The –Filter will actually perform all of the filtering that is provided on the remote system before bringing the data across the network to the console. Much more efficient which saves on time and resources.

Let’s take a look at another example, this time using Get-ChildItem to show why you might want to check out the parameters prior to filtering with Where-Object for specific files or extensions.

Measure-Command {
    Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue | Where {
        $_.Extension -eq "txt"
    }
}

image

Ok, not too bad with using Where-Object to filter for only .TXT files, but again, lets apply the –Filter parameter to see how much of a jump in performance we can get.

Measure-Command {
    Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue -Filter "*.txt"
}

image

Wow, over a minute difference between between the –Filter parameter being used and using Where-Object to filter the objects for .TXT files. So in this case, we can easily see how much better using a parameter filter is better than piping the objects to Where-Object for filtering.

The last example of this article is looking at Get-WinEvent and using the built-in filter parameters are quicker than using Where-Object.

Measure-Command -Expression {
    Get-WinEvent -LogName application -ErrorAction SilentlyContinue | Where-Object { 
        $_.providername -Like '*msi*' -AND 
        $_.TimeCreated -gt (Get-Date).AddDays(-15) 
    }
}

image

Now for the same cmdlet with some filtering.

Measure-Command {
     Get-WinEvent -ea SilentlyContinue -FilterHashtable @{
        ProviderName= "MsiInstaller"
        LogName = "application"
        StartTime = (Get-Date).AddDays(-15)
    }
}

image

And once again we can see that the built-in filtering with parameters out performs using Where-Object.

As we have seen, sometimes the time difference may not be that great, but other times it is more than enough to show why you want to spend a little time to find out what types of parameters that a cmdlet has. By doing this, you can potentially find a more efficient way of gathering data, especially when you start stacking multiple commands against multiple remote systems. If you remember earlier I mentioned about Filter | Select/Sort | Format, you could probably break it up a little more to be Parameter Filter | Where-Object Filter | Select/Sort | Format.

This entry was posted in powershell and tagged , , , . Bookmark the permalink.

7 Responses to Filter Using Parameters Instead of Where-Object When Possible

  1. Pingback: PowerShell -filter vs whereAdam Akers Blog | Adam Akers Blog

  2. Interesting post and very informative – thanks!

    Small typo in example no. 4 where you illustrate -Filter parameter and speed increase – the *.txt part has a space between the *. and txt so the script doesn’t work using copy-paste for anyone following along unless they remove the redundant space inbetween 🙂

  3. One small correction.
    Measure-Command {1..25 | ForEach {Get-WmiObject -Class Win32_Service -ComputerName DC1 -Filter “State=’running'”}
    13 seconds against 25 systems…not too bad, but lets see what happens with the –Filter parameter.

    >> That’s one command running 25 times against one system dc1, not against 25 systems.
    I love the article overall 🙂

    • Boe Prox says:

      Thanks for the comment! You are correct in that it was against 1 system 25 times, and this was by design. I did mention in the article “simulated 25 remote systems (by simulated, I mean the same remote system 25 times).” but perhaps I could have specified it a little more. 🙂

  4. JB says:

    It’s amazing how much faster things can sometimes finish while using the filter parameters. Thanks for the tip.

  5. Pingback: Gene Laisne's blog

Leave a comment