Where-Object and the Pipeline
Warming up to ForEach
October 31, 2013
In "PowerShell Cmdlets for DNS," I promised I'd cover ForEach-Object, an essential PowerShell power tool. But that's a big topic, so this month I want to warm up to ForEach by looking at the Powershell "pipeline." I've touched on this in the past, but it needs a little more elaboration. A great way to see the power of the pipeline is with a great tool named Where-Object.
Where-Object, which has the aliases where and ?—yes, that’s just a question mark—is a general-purpose filter that you can use with just about any get-something cmdlet to pick out a subset of its output. For example, suppose you wanted to find all the disabled users in a domain named bigfirm.com. As I’ve explained in previous articles, Active Directory (AD) user objects have dozens of built-in properties, one of which is named Enabled and is of type Boolean (computer-speak for “can only have the values that PowerShell names $true or $false"). You've already seen that I can list all the disabled users—users for whom the value of their Enabled property is $false—with the command
get-aduser -filter {Enabled -eq $false}
It’s nice that get-aduser has its own built-in filter tool. But not every get-something cmdlet has a -filter-ish option. For example, when you run get-process, PowerShell will show you a nice Task Manager–like listing of the processes you’re running. But what if you want to see only processes with more than, say, 1,500 open handles? Recall from previous articles that you can see a list of the properties provided by get-something by piping it into get-member (alias gm). Therefore, you could use
get-process | gm
Try that, and you’ll see this line in the results:
HandleCount Property int HandleCount {get;}
So, the output of get-process has a property named HandleCount that reports the number of open handles for a given process. It lacks a -filter, though, so here’s the syntax for the most common use of Where:
get-something | where {some expression that returns "true" or "false"}
More specifically, you're looking for something like
get-process | where {HandleCount of the processes dropped into the pipeline > 1500}
That’s the basic idea, although it’s not PowerShell syntax. First of all, the caret bracket (>) isn’t how PowerShell indicates greater than; it’s -gt. (The other numeric comparison operators are -ge, -lt, -le, -eq, and -ne.) Second, PowerShell refers to the items in the pipeline as $_. Picking that apart, recall from previous articles that variables—places in RAM where PowerShell stores temporary “scratch pad” sorts of data—all have names that start with $. PowerShell has a bunch of predefined variables. (You’ve already met $false and $true, and if I ran the zoo, there would be one named $pipeline, a variable that automatically contains whatever is in the pipeline.) For historical reasons, however, the PowerShell authorities decided to call the variable $_. (An underscore looks kind of like a pipe lying on the ground, see?)
Getting closer to legal PowerShell syntax, then, you're looking for
get-process | where {(HandleCount of $_) -gt 1500}
In the past, I’ve referred to PowerShell as "object-oriented," which means that whereas many older programs and shells can understand only simple bits of data (e.g., HandleCount, ProcessName, PagedMemorySize)—separate facts about a process, in this case—a newer object-oriented system such as PowerShell can understand more holistic, complex pieces of data called objects. In this case, get-process emits objects of a type called Process and they have properties such as HandleCount, ProcessName, PagedMemorySize, and more; it’s a hierarchy of data in which the object is the highest level and its properties sit below it. (Those properties can be objects themselves, as you’ll see when you delve further into PowerShell.)
So you want to refer not to the whole process object in the pipeline—$—but instead to just the HandleCount property on it. In fairly standard “object talk,” you'd write that as $_.HandleCount (the object name, a period, then the property name). Now you can write your “one-liner”:
get-process | where {$_.HandleCount -gt 1500}
Here is the great power of Where and the built-in pipeline variable $_: It lets you take a cmdlet that wasn’t built with a -filter parameter and add one after the fact, with no programming required. And here’s one more quick example. Suppose get-aduser lacked a working -filter parameter. If that were true, here’s how you'd get PowerShell to show you just the disabled user accounts:
get-aduser –filter * | where {$_.Enabled –eq $false}
Even if you never progress to caring about ForEach, you’ll find Where useful—I guarantee it.
About the Author
You May Also Like