PowerShell with a Purpose Blog

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.
Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish