Deciphering my PowerShell FAIL...

So, I've been prepping some new materials for thisUSB flash drive training kit that I offer at conferences and classes I teach at, and I constructed a statement like this:

Import-Module ActiveDirectory
Get-ADComputer -filter * -searchbase 'ou=domain controllers,dc=company,dc=pri'
Select-Object @{'Label'='ComputerName';Expression={$_.Name}}

The practical upshot of this is to retrieve all the domain controllers from the domain, and then remap their "Name" property to be a "ComputerName" property. Why? Well, because cmdlets like Invoke-Command can bind pipeline input ByPropertyName, meaning that - I thought - if I piped in objects having a ComputerName property, they'd attach themselves to the cmdlet's -computerName parameter. So this:

Import-Module ActiveDirectory
Get-ADComputer -filter * -searchbase 'ou=domain controllers,dc=company,dc=pri' |
Select-Object @{'Label'='ComputerName';Expression={$_.Name}} |
Invoke-Command -script { Get-Service }

Would invoke the Get-Service command on every domain controller in the domain. Or at least, it would in my perfect little world. As-is, it spit out a bunch of errors.

Well, turns out there's a trick to parameter binding that you should be aware of. True, Invoke-Command does bind pipeline input to -computerName ByPropertyName - so as far as it goes, my theory was right. But it also binds the -inputObject parameter ByValue, accepting a value of Object. The -inputObject parameter accepts stuff that you want passed into whatever command/script you're executing.

In plain English? My computer objects were being piped to Invoke-Command, but because ByValue binding happens first, my objects were being snatched up by -inputObject and being sent to that Get-Service cmdlet as its pipeline input. Get-Service didn't really know what to do with them, so it all exploded on me. Rats.

So I'm still wrapping my head around these facts: 
  1. ByValue parameter binding always happens before ByPropertyName binding.
  2. -inputObject binds pipeline input ByValue, accepting anything of the Object type.
  3. All objects derive from the Object type, so -inputObject will bind anything.
So it seems as if -inputObject will always grab whatever you pipe to Invoke-Command. Now, here's the trick: Provided those objects can be safely used by whatever command you're invoking, those objects can also contain other miscellaneous properties that will bind, ByPropertyName, to Invoke-Command's parameters. For example, Get-Service will bind a Name property. So if I piped in an object having both a Name property and a ComputerName property, then something seems to work:

Import-Module ActiveDirectory
Get-ADComputer -filter * -searchbase 'ou=domain controllers,dc=company,dc=pri' |
Select-Object Name,@{'Label'='ComputerName';Expression={$_.Name}} |
Invoke-Command -script { Get-Service }

This still weirds be out a little. It seems to negate the value of having Invoke-Command's -computerName parameter bind input at all, since anything piped in will bind to -inputObject first. But surely someone thought of that, so it's obviously something I'm missing.

Needless to say, I'm tabling this particular example for right now until I can wrap my head around it. I have e-mails out to some of the other PowerShell MVPs, so we'll see what they say. I'm interested in your input, too - what am I missing? How can I successfully use pipeline binding with the -computerName parameter of Invoke-Command? Drop a comment if you know the trick - and if I discover it, I'll be sure to follow-up.

Discuss this Blog Entry 3

Bartek B (not verified)
on Apr 21, 2010
Oops, looks like I simply got response from my PC. ;)
Anyway: I played with it a bit and it looks like all parameters go rather to scriptblock than to Invoke-Command itself. In other words: your command would probably try to look for Service named DC_NAME on pc DC_NAME. At least that what I've received when playing with my custom object. When I added $obj.Name with value '*' I got list of all services. When I changed that to 'computername' it all failed. And because I've used no-v2 PC as test machine it was more like:
Invoke-Command { Get-Service -ComputerName 'computername' -Name '*' }
When I tried to move -ComputerName parameter outside scriptblock it failed. Not really helpful, because there are many commands that simply don't understand -computername... :(

Bartek B (not verified)
on Apr 21, 2010
If I understand everything correctly: shouldn't it be in foreach-object? I've tried to create custom object with "computername" property only and it worked fine:

$obj = New-Object PSObject
$obj | Add-Member NoteProperty ComputerName 'WAR000183'
$obj | % { Invoke-Command -ScriptBlock { Get-Service }}

on Apr 21, 2010
Yup, Bartek, that's the problem: Invoke-Command's -inputObject parameter is grabbing everything and passing it into that scriptblock, which makes the rest of the parameter binding kinda superfluous. It's not so much a "how to I accomplish this task" thing for me, it's more "how do I make the darn -computerName parameter bind pipeline input when -inputObject is snatching everything first."

Please or Register to post comments.

What's PowerShell with a Purpose Blog?

Don Jones demystifies Windows PowerShell.

Blog Archive

Sponsored Introduction Continue on to (or wait seconds) ×