Last Update: Sep 04, 2024 | Published: Aug 17, 2016
In my previous article on filtering with PowerShell, I gave you an introduction to the tools and techniques you will commonly use when filtering. But filtering can also be done with more than the Where-Object cmdlet. Although once you understand that cmdlet, there are a few other ways you can use it that might simplify your life.
Starting in later versions of PowerShell, you no longer needed to use a filtering scriptblock with $_. For simple filters you can run an expression like this:
get-service w* | where status -eq 'stopped'
If you think about it, I’m actually filtering in multiple ways. First, I’m only getting services that start with ‘w’ and then I am filtering those where the status property is equal to ‘Stopped’. There was no need to add extra characters and for many people this is easy to read. You can use the concept with any sort of simple comparison.
Get-Process | where workingset -ge 75mb | sort WorkingSet -Descending
Here I’m getting all processes where the working set size is greater or equal to 75MB. Note that I perform the sort after the filtering. This is slightly more efficient because I’m not wasting time sorting objects I’m going to discard.
If you’re filtering needs are more complex then you’ll need to turn back to the scriptblock. What you should not do is something like this:
Get-Process | where workingset -ge 50MB | where VM -ge 1000MB
I see examples of this more often than not. This will work but I can tell that the person who wrote it is just getting started and has much to learn about PowerShell. Instead, use a compound filter.
Get-Process | where {$_.workingset -ge 50MB -AND $_.VM -ge 1000MB}
This get the same results and it runs a little faster.
By the same token, the reason to filter with Where-Object is because the primary cmdlet you want to use doesn’t provide a way to limit results. Here’s another example I often see that indicates a lack of understanding of PowerShell filtering:
Get-CimInstance win32_volume | Where DriveLetter -eq 'c:' | Select SystemName,Caption,Capacity,Freespace,IndexingEnabled,Compressed,FileSystem
Yes, it will work. But Where-Object can’t really filter until all the volumes are returned from Get-CimInstance. On the local machine or when querying a single server, this probably doesn’t matter. But when you start managing at scale and are now querying 100 servers, this type of filtering begins to cost you. I don’t want to get side-tracked into WMI filtering, but Get-CimInstance will accept a WMI filter so learn how to use them.
Get-CimInstance win32_volume -filter "DriveLetter='c:'" -ComputerName $computers | Format-Table -groupby @{Name='Drive';Expression={$_.Caption}} -property SystemName,Capacity,Freespace,IndexingEnabled,Compressed,FileSystem
This is a much better approach, especially when querying many servers. The Get-Ciminstance command will only return objects that match the query, which in this case is any volume object with a driveletter property equal to ‘C:’. That is the only thing that is sent across the network to my desktop. If by chance I was running a filter and on some machines it would return nothing, I’d much rather figure that out “over there” on the server instead of pulling hundreds or thousands of objects across the network, only to discard them because they don’t match my filtering needs.
I will admit that this is not an absolute rule. Based on your filtering needs, you may have no choice but to bring objects through the pipeline and filter with Where-Object. Some of the filtering with the Active Directory cmdlets can be a bit tricky. But even if you have a complex set of filtering criteria, try to use the initial cmdlet to provide as much filtering as it can. That way you are processing as small a set of objects as possible with Where-Object.
If you want to read one more useful filtering technique, read Filtering PowerShell with the Where Method on Petri IT Knowledgebase.