You've already seen how using the pipeline (|) to string together a few PowerShell cmdlets can let you create a simple report, like the one I've discussed several times:

get-aduser –f * -pr lastlogondate | select samaccountname,lastlogondate | sort lastlogondate

That's a nice example of combining several separate PowerShell cmdlets (get the users, grab just their names and lastlogondates, and sort them by those lastlogondates), but it's probably a little more than you really want. You don't want to see all users and their last logon dates; you just want to see the ones who haven't logged on since some unusually long-ago date.

For that, you need a fourth PowerShell cmdlet, where-object. Almost no one calls it that, though; most people use one of its shorter names, where or simply the question mark (?). In past columns (such as in “Doubling Up Active Directory PowerShell Cmdlets” and “3 PowerShell Account Tweaks”), I've talked about using two-part cmdlets in PowerShell—the "filter" cmdlet (which finds just the user accounts that meet some criterion) and the "hammer" cmdlet (which does something to that discovered subset of AD users). You might also recall that I’ve discussed a handful of "filter" cmdlets: get-aduser, search-adaccount, and a few others.

Well, where-object is the filter of filters. Its synax is a bit strange, however, so let's start with an example. To see all users who last logged on before January 1, 2013, you could type

get-aduser -f * | where {$_.lastlogondate -le "1 January 2013"}

First, get-aduser collects all the domain's user accounts, as you've seen before, and then it sends them down the pipeline, as you've also seen before. Then, where-object takes over. Its job is to examine every object emitted by the pipeline to see if that object meets some criterion. This criterion is written between the braces and yes, it's ugly, so let’s pick it apart and make sense of it.

You’ll remember that -le means "is less than or equal to," and the date on the right is obvious, but what is $_.lastlogondate? Why, it's the key to making a lot of one-liners work!

I've already said that where-object's job is to test things arriving to it from the pipeline, and I think it's easy to see that a statement like (something) -le "1 January 2013" is a reasonable way for PowerShell to let you test (something) against a given date. But that (something) has to be whatever is in the pipeline at the moment, so you need some way to specify whatever is in the pipeline at the moment.

That (something) is called $_. Yes, it's strange-looking, but here's the reasoning. I've written several times in other articles that PowerShell has variables, which let you store transient information in the computer's memory, and that you can immediately recognize that something is a variable because the first character in its name is a dollar sign ($). Thus far, my examples of variables have been variables that I've created on the fly, but PowerShell—like most scripting environments—also includes a number of built-in variables. For example, there's $true and $false, built-in variables that store the values for true and false. Now, you'd think that PowerShell would store the current contents of the pipeline in a variable named $pipeline, but it instead uses the name $_. In this case, $_ contains an entire user account object.

But this article’s sample criterion doesn't ask if the user is less than January 1, 2013, it asks if the lastlogondate property of that user is before that date. So, you need to be able to extract just the lastlogondate value, and that's why the criterion refers to $_.lastlogondate. Adding a period and a property name pulls out just the part that you need. Here's what your one-liner would look like with where-object refining its output:

get-aduser –f * -pr lastlogondate | ? {$_.lastlogondate -le "1 January 2012"} | select samaccountname,lastlogondate | sort lastlogondate

You can use where-object and $_ to build all kinds of filters. For example, to see all users whose samaccountname starts with an F, you could type

get-aduser -f * | where {$_.samaccountname -like "f*"}

So why did I spend so much time talking about the -filter option in get-aduser? Why not just make all one-liners look like

get-aduser -f * | where {$_.<something> -like "<somepattern>"}

you might ask? From a technical point of view, there's nothing wrong with that, except for one thing: It's probably a massive waste of bandwidth and server time. The get-aduser cmdlet with a filter sends a command to a domain controller (DC) that allows a DC to return just a small subset of AD; get-aduser -f * piped into a where-object cmdlet tells the DC to deliver all the user accounts and then has the local CPU filter out the desired ones. Where-object, then, is a great general-purpose tool, but you should avoid it when there's a filter built in to your initial get-whatever cmdlet.