And At Long Last – Square Pegs in Round Holes

Let’s review what we’ve seen about PowerShell pipeline behavior before we get to the grand finale of this extended discussion.

  1. Many cmdlets accept pipeline input ‘ByValue’, meaning that they can consume whole objects of a given type. You can look at the datatype associated with the –InputObject parameter of a cmdlet to figure out which type of object is ‘eaten’ by that cmdlet (the ‘INPUTS’ section of the –full version of a cmdlet’s help file gives you an even better idea).
  2. Most cmdlets also have other parameters that accept pipeline input but with a twist: these parameters aren’t looking for specific types of objects, but instead they expect to find incoming objects which contain a property that has the same name as that parameter. So the –name parameter of the Get-Process cmdlet hopes incoming objects have a name property.

These abilities provide rich pipelining functionality. I can pipe many kinds of cmdlet output to many other cmdlets and have a very good intuition that the pipelining will work the way I expect. Until, of course, the time when it doesn’t (cue ominous music here…)

So what happens when I have output from one cmdlet that I want to feed to a second cmdlet, and neither of the above scenarios is applicable? For example, Get-Foo produces objects of type System.GKTrainingRocks.Foo, and Set-Bar expects input objects to be of type System.GKTrainingRocks.Bar (those are all imaginary, by the way — I’ve been hanging around UNIX users too much). Worse, what if none of the names of the properties of the output match the names of the parameters on the next cmdlet in the pipeline? Crazy talk? Actually, no.

Just imagine: while you busily plan your company’s upgrade to SQL Server codename “Denali”, your boss drops by your desk and wants a list of all the services running on all the corporate print servers. The PowerShell portion of your brain wakes up and starts spinning its wheels: “Hmm, listing services — there’s Get-Service for that — okay — how about getting a list of computers — I could query Active Directory for those — Get-ADComputer sounds about right — maybe I can hook those two cmdlets together…”

So you try to make it work with something like this: Get-ADComputer –filter {Name –like “PrintServer*”} | Get-Service

Eagerly anticipating success, your mouth drops open when you see a list of errors:

Get-Service : Cannot validate argument on parameter ‘ComputerName’.  The argument is null, empty, or an element of the argument collection contains a null value.  Supply a collection that does not…..


What went wrong? Let’s take each of our main pipelining scenarios one at a time.

What kind of object does Get-ADComputer generate? Check the help file (or simply retrieve a computer object using the cmdlet and pipe it to Get-Member). Here’s the help-file snippet (using Get-Help Get-ADComputer –full).

It produces Microsoft.ActiveDirectory.Management.ADUser objects. So far so good. Next question: what kind of objects does Get-Service accept as input on the pipeline?

Aha! Get-Service expects System.ServiceProcess.ServiceController objects. It has no clue what to do with a Microsoft.ActiveDirectory.Management.ADUser.

No need to panic yet — it’s time to try plan B. Does Get-Service allow pipeline interaction with objects using parameters that have names I can find on my incoming ADUser objects? Remarkably enough, yes it does! There’s a Name property on my ADUser object — that’s the name of the computer I got from AD. And there’s a –name parameter on Get-Service and it…

Uh oh. That’s not good. The –name parameter for Get-Service wants the name of the service about which I want to retrieve data — not the name of the computer from which I want to get it. What does Get-Service use to identify the target computer? There it is — it’s the –computername parameter. That creates a challenge. I can’t use the Name property of my object because it doesn’t mean the same thing as the –name property of the cmdlet. I can’t use the –computername parameter of the cmdlet because I don’t have a matching ComputerName property  on my ADUser object. I’ve got a square peg and a round hole. Now what do I do?

Our essential problem is the name mismatch. If there was a way to take an incoming ADUser object with its underloved Name property and rename it to have the name ComputerName instead, we could do well. It turns out there’s a way to do that, and the help comes from an unlikely place — the humble Select-Object cmdlet.

A little noticed part of the help file for Select-Object reads like this:

-Property <Object[]>

Specifies the properties to select. Wildcards are permitted.

The value of the Property parameter can be a new calculated property. To create a calculated property, use a hash table. Valid keys are:

— Name (or Label) <string>

— Expression <string> or <script block>

What does this mean to us? In short, it means we can relabel a property of an object with a new name. The technique requires a hashtable, an unordered list of pairs of values. This particular hash table identifies the Label on our new property, as well as an Expression that describes the value that the new property is to have. So:

Select-Object –Property @{Label=”ComputerName”;Expression=”$_.Name”}

This piece of code can receive an object with a Name property and emit an object that has a ComputerName property instead! If we squeeze that in between a Get-ADComputer and a Get-Service, we should be all set! Our finished square-peg/round-hole solution looks like this:

Get-ADComputer –filter {Name –like “PrintServer*”} | Select-Object –Property @{Label=”ComputerName”;Expression=”$_.Name”} | Get-Service

PowerShell is optimized to allow many predictable, common-sense cmdlet interactions to happen automatically. A great many day-to-day tasks are accounted for in the design of PowerShell’s constellation of cmdlets. It’s comforting to know that even when the default behavior of our favorite cmdlets doesn’t allow for our desired results, we can add just a little bit of code to assemble the pieces into a satisfying solution.

In this article

Join the Conversation

1 comment

  1. Mike Hammond Reply

    Comments, as always, are welcome!