Tuesday, July 30, 2013

Passing Pipeline Objects byPropertyName Fails

In class this week, the text had the following example of a Powershell command that should work:
Get-ADComputer -Filter * | Select-Object @{name='ComputerName';expression={$_.name}} | Get-Service
The text claimed this would work and I initially thought it would. Here was my theory.....

Get-ADComputer retrieves the computer objects from Active Directory. Those computer objects have a property 'Name' that we can convert to a property named 'ComputerName'. Then the list of ComputerName property values is passed to Get-Service which has a ComputerName parameter that accepts pipeline input by property name.

However, like all good students, my class wanted to see it work. So, we tried it out, and it didn't work.

It failed with the following error:
Get-Service : Cannot find any service with service name '@{ComputerName=VAN-DC1}'

Further in there was a clue:
ObjectNotFound: (@{ComputerName=VAN-DC1}:String)

The problem relates to how parameters are passed through the pipeline. The preferred choice by PowerShell is to pass byValue. This means that values are matched to parameters based on the type of object that is being passed. Note that PowerShell will also convert data types to match a parameter that accepts pipeline data byValue if it can. Only if all options for binding based on value fail will it kick over and attempt to match byPropertyName.

In this case, the ComputerName property that we added (technically a PropertyNote) was being converted to a string. The Get-Service cmdlet accepts strings as input for the -Name parameter which expects the value to be a service name.

So, the end result is that it was a poorly constructed way to get the list of services from all computers. This example is perhaps easier and works. However, it doesn't showcase passing values along the pipeline.

Get-ADComputer -Filter * | Foreach {Get-Service -ComputerName $_.Name}

3 comments:

  1. It is frustrating that ByValue comes before BypropertyName in the order of pipeline binding. I would have expected that explicitly specifying names would have priority.

    However, there is a way to get your first command working. If you specify a value for the -Name switch then it isn't involved in pipeline binding.

    Get-ADComputer -Filter * | Select-Object @{name='ComputerName';expression={$_.name}} | Get-Service -Name *

    ReplyDelete
  2. Unfortunately that fix does *not* work: for every computer you specify you retrieve the services of the *local* machine. So you get the same list (say) 20 times. You can easily check this by starting or stopping a specific service on your local machine.

    This strange behavior occurs whenever you have a wildcard in your name parameter. If you instead put:
    ... | Get-Service wuauserv
    you will retrieve the windows update service for every machine, as required.

    But to see multiple services you will need to resort to a foreach construct.

    (Eric, Netherlands, MCT)

    ReplyDelete