Understanding Trace-Command Output

As always, I’m running through an exercise on one of Don Jones’ books (Learn PowerShell Toolmaking In A Month Of Lunches, in this case). Below is the command that I’m running as part of the exercise.

Trace-Command -Name ParameterBinding -PSHost -Expression {Import-Csv C:\Data\computers.txt | Invoke-Command -ScriptBlock {Get-Service}}

To keep it simple, I have just the following in my computers.txt file.

ComputerName
localhost

From the debug output of the Trace-Command cmdlet, I see that the Import-Csv objects get piped to the Invoke-Command cmdlet as PSCustomObject. Invoke-Command has the -InputObject parameter which will take PSObject objects ByValue. The details I’ve read so far on that parameter aren’t really clear, but I see that the PSCustomObject objects then get passed to the Get-Service cmdlet inside the script block. In the end, I see that the objects get bound to the -Name parameter for the Get-Service cmdlet. Here is the part I am confused on.

After attempting to bind to the -Name parameter ByValue…

DEBUG: ParameterBinding Information: 0 :     Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [@{ComputerName=notonline}] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         Binding collection parameter Name: argument type [PSObject], parameter type [System.String[]], collection type Array, element type [S
ystem.String], no coerceElementType
DEBUG: ParameterBinding Information: 0 :         Creating array with element type [System.String] and 1 elements
DEBUG: ParameterBinding Information: 0 :         Argument type PSObject is not IList, treating this as scalar
DEBUG: ParameterBinding Information: 0 :         BIND arg [@{ComputerName=notonline}] to param [Name] SKIPPED

…which naturally would fail, I see a successful binding to the -ComputerName parameter.

DEBUG: ParameterBinding Information: 0 :     Parameter [ComputerName] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [notonline] to parameter [ComputerName]
DEBUG: ParameterBinding Information: 0 :         Binding collection parameter ComputerName: argument type [String], parameter type [System.String[]], collection type Array, element t
ype [System.String], no coerceElementType
DEBUG: ParameterBinding Information: 0 :         Creating array with element type [System.String] and 1 elements
DEBUG: ParameterBinding Information: 0 :         Argument type String is not IList, treating this as scalar
DEBUG: ParameterBinding Information: 0 :         Adding scalar element of type String to array position 0
DEBUG: ParameterBinding Information: 0 :         Executing VALIDATION metadata: [System.Management.Automation.ValidateNotNullOrEmptyAttribute]
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.String[]] to param [ComputerName] SUCCESSFUL

Despite the successful binding to the -ComputerName parameter, PowerShell then successfully attempts to bind to the -Name parameter.

DEBUG: ParameterBinding Information: 0 :     BIND arg [@{ComputerName=notonline}] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         COERCE arg to [System.String[]]
DEBUG: ParameterBinding Information: 0 :             Trying to convert argument value from System.Management.Automation.PSObject to System.String[]
DEBUG: ParameterBinding Information: 0 :             ENCODING arg into collection
DEBUG: ParameterBinding Information: 0 :             Binding collection parameter Name: argument type [PSObject], parameter type [System.String[]], collection type Array, element typ
e [System.String], coerceElementType
DEBUG: ParameterBinding Information: 0 :             Creating array with element type [System.String] and 1 elements
DEBUG: ParameterBinding Information: 0 :             Argument type PSObject is not IList, treating this as scalar
DEBUG: ParameterBinding Information: 0 :             COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 :                 Trying to convert argument value from System.Management.Automation.PSObject to System.String
DEBUG: ParameterBinding Information: 0 :                 CONVERT arg type to param type using LanguagePrimitives.ConvertTo
DEBUG: ParameterBinding Information: 0 :                 CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [@{ComputerName=notonline}]
DEBUG: ParameterBinding Information: 0 :             Adding scalar element of type String to array position 0
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.String[]] to param [Name] SUCCESSFUL

After this occurs, the mandatory validation, and some other stuff, occurs and the error occurs saying:

Get-Service : Cannot find any service with service name '@{ComputerName=notonline}'.
At line:1 char:123
+ ... sv C:\Data\computers.txt | Invoke-Command -ScriptBlock {Get-Service}}
+                                                             ~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (@{ComputerName=notonline}:String) [Get-Service], ServiceCommandException
    + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand

So, here is my question. Why after the successful binding to the -ComputerName parameter, of the Get-Service cmdlet, do we then ultimately bind to the -Name parameter? Both the -ComputerName & -Name parameters take string objects. Is it because -Name is a positional parameter (position 0) and because we didn’t specify any parameters for Get-Service, in the script block? If so, then why did PowerShell first bind to the -ComputerName parameter?

Also, if anyone can explain more about how the -InputObject parameter of the Invoke-Command cmdlet works in this scenario, that would be nice. Thanks.

Where it says “notonline”, that should say “localhost”. I actually had both server names in my computers.txt file when I ran the code.

Take a look to Get-Service syntax:

PS C:\> Get-Help Get-Service -Full

NAME
    Get-Service

SYNOPSIS
    Gets the services on a local or remote computer.


SYNTAX
    Get-Service [-ComputerName {String[]}] [-DependentServices] -DisplayName {String[]} [-Exclude {String[]}] [-Include {String[]}] [-RequiredServices] [{CommonParameters}]

    Get-Service [-ComputerName {String[]}] [-DependentServices] [-Exclude {String[]}] [-Include {String[]}] [-InputObject {ServiceController[]}] [-RequiredServices] [{CommonParameters}]

    Get-Service [[-Name] {String[]}] [-ComputerName {String[]}] [-DependentServices] [-Exclude {String[]}] [-Include {String[]}] [-RequiredServices] [{CommonParameters}]

-InputObject 
    Specifies ServiceController objects representing the services to be retrieved. Enter a variable that contains the objects
    , or type a command or expression that gets the objects. You can also pipe a service object to this cmdlet.

    Required?                    false
    Position?                    named
    Default value                None
    Accept pipeline input?       True (ByValue)
    Accept wildcard characters?  false

-Name 
    Specifies the service names of services to be retrieved. Wildcards are permitted. By default, this cmdlet gets all of the
     services on the computer.

    Required?                    false
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName, ByValue)
    Accept wildcard characters?  false

There are 3 variants of a set of parameters. You do not use any of the named parameters, therefore PS should determine the necessary set of parameters by yourself.
Each of them has the parameter -ComputerName, so it can not be the determinant in which a set of parameters should be used.
There are only two Pipeline / ByValue: -InputObject and -Name parameters
-InputObject must be a ServiceController type, and you send a different type that can not be converted to a ServiceController
-Name must be String, and your type can be converted to a String, so it is used as a qualifier

and (finally) may be just the DefaultParameterSet is a third set?

Thanks, Max. I’m still curious though why we try to bind to the -Name parameter ByValue (we fail, as expected), then to the -ComputerName parameter (we succeed), before going back to the -Name parameter (we succeed).

I think this is because -Computername -non-qualifier parameter and -Name - is qualifier for parameterset.
and (may be) ByValue have precedence before ByPropertyName (when all other equal)

I know we try ByValue first, for sure.