Windows PowerShell

What the Heck Is $_?

Don Jones

When I'm doing a Windows PowerShell class or conference lecture, I love to trot out a one-liner like this:

Import-CSV c:\users.csv | ForEach-Object { New-QADUser -logonname $_.username -name $_.Name }

In fact, at a recent TechMentor Conference, I started my sessions with something like that, and I used a longer version in a talk at Tech-Ed. This one-liner imports a list of user information from a .CSV file and turns that information into new users in Active Directory. It's a great demo because it demonstrates that Windows PowerShell can accomplish quite a lot by just running commands rather than having to program complex scripts.

But long after I've moved on to other demonstrations, there's one thing that's still making people's eyes cross: $_. In hands-on classes, I'll see students trying to play with $_on the command line, without much luck. In online discussion forums, I'll see people asking questions about it, trying to understand what it means. Fortunately, the answer is straightforward, if not exactly simple.

Passing the Buck ... er, Object

Remember that everything in Windows PowerShell is an object. Usually, you can safely ignore that fact, which is nice because objects are among the software-developer issues that can easily make for a confusing discussion. I'll try to avoid the most confusing aspects with this simple example:

Get-Service

This cmdlet produces a bunch of service objects. That simply means that you can pipe them to other cmdlets that accept service objects as input:

Get-Service | Stop-Service

But don't actually run that unless you're interested in stopping every service on your computer -- which will probably crash it, if not shut it down unexpectedly. Basically, Stop-Service can accept service objects, among other things, as input, telling it which services to stop. Some cmdlets are more broadly accepting and can take almost any kind of object as input:

Get-Service | Sort-Object Name
Get-Process | Sort-Object Name

In these examples, Sort-Object is perfectly happy to work with either service objects or process objects, putting them in order by whatever object property -- in both cases, the Name property -- that you specify.

Technically speaking, Name is being accepted by the cmdlet's -property parameter, meaning that I could have written a more complete command line, such as this:

Get-Process | Sort-Object -property Name

That means, "Take these objects and sort them on the Name property." 
Every cmdlet has one or more parameters that customize the cmdlet's behavior in some fashion. Sort-Object, for example, has another parameter that lets me reverse the order of the sort:

Get-Process | Sort-Object -property Name -descending

Regardless, Sort-Object is taking the input objects, putting them into a different order and outputting those same objects in their new sequence.

Super Parameters: Scriptblocks

Some especially powerful cmdlets can have entire scripts given to them as parameters. Windows PowerShell usually refers to these as scriptblocks, because -- well, because each one is a block of script code. More frequently, these consist of a block of commands rather than a script involving detailed programming, but you can put pretty much anything into a scriptblock.

One such cmdlet is Where-Object. It accepts a bunch of objects as input -- any type of object will do -- and then it executes a scriptblock once for each object that was input.

If that scriptblock outputs the Boolean value True, then the piped-in object is piped out; if the scriptblock outputs False, then the piped-in object is discarded. In this fashion, you can use Where-Object to filter objects based on some criteria. For example:

Get-WmiObject -class Win32_Service | Where-Object -filterscript { $_.State -eq "Running" }

There's that $_ again. Actually, both the -class and -filterscript parameters are positional, meaning that as long as you pass a value in the first position, you don't need the actual parameter name. That means you'll usually see an example like this written as:

Get-WmiObject Win32_Service | Where-Object { $_.State -eq "Running" }

But it's the same thing. The curly braces are the shell's standardized way of enclosing a scriptblock. In this case, the scriptblock isn't much of a script at all; it's a fairly simple comparison. And that's where $_ comes in: Remember that the scriptblock will execute one time for each piped-in object. When the scriptblock executes, $_ is replaced with the current input object. In other words, $_ is a placeholder for the current pipeline object. $_ lets you compare that object's properties -- such as the State property -- with values such as "Running."

What's in a Name?

I've always thought that $_ was an odd piece of syntax. At various times, I've thought that an easier-to-read name such as $piped-in or $inputobject might make more sense, but $_ is at least easy to type. I'm told that $_ got its name because the underscore character resembles a pipeline character (|) lying on its side, implying that it contains whatever was piped in.

The person who told me this was in a position to know such things, and was quite serious -- but my leg still feels slightly tugged-on. I've decided that, for me, $_ represents a blank, as in, "fill in the blank." It's a blank that gets filled in automatically by the shell when the scriptblock is executed.

And therein lies the key that gets most people confused: $_ is only valid and usable in specific locations where Windows PowerShell is explicitly looking for it and is prepared to replace it with some input object. One such place is the scriptblock used for Where-Object and ForEach-Object cmdlets (such as the one at the beginning of this article).

Another place is inside the PROCESS block of certain kinds of Windows PowerShell functions. But, of course, you can't just type $_.Status on the shell's command line and expect it to work. The shell isn't looking for $_ at that point, so $_ doesn't get replaced with anything; it's essentially an undefined value.

Nests: Not Just for Birds

Another confusing situation can occur when you've got nested scriptblocks. Consider this example, which reads a list of computer names from a file (one name per line) and attempts to restart them all by using Windows Management Instrumentation (WMI):

Get-Content c:\names.txt | ForEach-Object { Get-WmiObject -computername $_ -class Win32_OperatingSystem | ForEach-Object { $_.Reboot() } }

This can get a bit easier to read if I stop treating this as a single command line and instead apply some human-friendly formatting:

Get-Content c:\names.txt | ForEach-Object { Get-WmiObject -computername $_ -class Win32_OperatingSystem | ForEach-Object { $_.Reboot() } }

 

Here, it's easier to figure out that there are nested ForEach-Object cmdlets. Each one has a scriptblock and each scriptblock uses $_. The trick is that $_ actually changes its meaning over the course of this command line, but it always contains one of the objects that was piped to that ForEach-Object cmdlet.

So the first ForEach-Object is receiving computer names from Get-Content; therefore, the first $_ contains computer names, which are being given to the -computername parameter.

The second ForEach-Object is receiving input from Get-WmiObject, so its $_ contains those WMI objects -- specifically, instances of the Win32_OperatingSystem class, which have a convenient Reboot() method.

So here's a trick to find out what $_ contains: Remember that it will always contain objects produced by the next cmdlet to the left of the command line.

When you get deeply nested situations like this, being able to trace backward can help you figure out what's going on more easily.

It's Always the Gotchas

I've always felt that the difference between being a Windows PowerShell newbie and a guru isn't memorizing every available command or being able to write complex scripts. Instead, it's being able to master the few "gotchas," or odd pieces of syntax, that every shell or language comes with. $_ is one of those. It's hard to figure out just by looking at it what $_ does.

But now that you know, you can begin using it in more of your own commands. As you do, you'll master it -- and you'll move that much closer to being a

Windows PowerShell guru.

 

Don Jones is one of the nation's most experienced Windows PowerShell trainers and writers. He blogs weekly Windows PowerShell tips at ConcentratedTech.com; you may also contact him or ask him questions there.