Search code examples
powershellparameters

Powershell Parameter binding ByPropertyName and ByValue


I want to stop a service named "ALG" so I use: "alg" | stop-service

It works.

Get-help stop-service -parameter name says:

Pipeline input:true(ByPropertyName, ByValue) and "alg" is "ByPropertyValue" right?

I want to stop a process named notepad so I use: "notepad" | stop-process and I get an error.

Get-help stop-process -parameter name

says: Pipeline input true(ByPropertyName) and "notepad" is "ByPropertyName"?

Why this error?


Solution

    • Mathias R. Jessen's answer provides a solution for piping strings (process names) to Stop-Process.

    • js2010's answer has the correct explanation for why piping strings to Stop-Process doesn't work (without extra effort), and also offers a helpful technique for tracing parameter binding, but - as of this writing - the answer contains incidental information that confuses the issue a bit.

    Let me offer a more detailed explanation:


    Stop-Process, unlike Stop-Service, is not designed to accept strings (process names) as pipeline input.

    While string input in the abstract can still work, namely if the strings can automatically be converted to one of the data types expected by a command's ByValue (whole-object) pipeline-binding parameters, this is not the case with Stop-Process, because a process name (string) cannot (automatically) be converted to a System.Diagnostics.Process instance (-InputObject)[1].

    • When PowerShell considers binding pipeline input to parameter -Name during a call, it looks for an object with a Name property, because the parameter declaration specifies that pipeline input is accepted only if the input object has a property named for the parameter:

      • In help topics, such as the one for Stop-Process, this is expressed as ByPropertyName.

      • In code, it is expressed as the Boolean ValueFromPipelineByPropertyName property of the System.Management.Automation.ParameterAttribute type; that is, expressed in PowerShell code, the parameter declaration looks something like: Note that ValueFromPipelineByPropertyName is short for ValueFromPipelineByPropertyName = $true
        [Parameter(ValueFromPipelineByPropertyName)] [string[]] $Name

    • A [string] (System.String) instance such as "alg" doesn't have a Name property - it is itself the name.

    • Therefore, in the absence of an automatic conversion[1] to the System.Diagnostics.Process type of the only ByValue parameter, -InputObject, and in the absence of Name and Id properties for the ByPropertyValue parameters, invocation fails with the following error message, which in essence tells you that the pipeline input is invalid (cannot be bound to any parameters):

      • The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

    Stop-Service, by contrast, is designed to accept string input, because its -Name parameter is (also) declared as accepting strings directly as input objects, as a whole.

    • In help topics, such as the one for Stop-Service, this is expressed as ByValue.

    • In PowerShell code, it is expressed as ValueFromPipeline:
      [Parameter(ValueFromPipeline)] [string[]] $Name

    Note:

    • While a given parameter can be both ByValue and ByPropertyValue - which is indeed the case for Stop-Service's -Name parameter - that isn't typical.

    • Typically, pipeline-binding parameters are declared as scalars rather than arrays (e.g., for Sort-Object, -InputObject <PSObject> rather than -InputObject <PSObject[]>), which means that passing multiple arguments is only supported via the pipeline, not by direct argument - see GitHub issue #4242 for background information.


    Examining pipeline-binding parameters:

    • A command's given parameter:
    PS> Get-Help Stop-Process -Parameter Name
    
    -Name <String[]>
        Specifies the process names of the processes to stop. You can type multiple process names, separated by commas, or use wildcard characters.
        
        Required?                    true
        Position?                    named
        Default value                None
        Accept pipeline input?       True (ByPropertyName)
        Accept wildcard characters?  true
    

    Note the Accept pipeline input? line; for a non-pipeline-binding parameter, you'd see False in the second column.

    • All pipeline-binding parameters supported by a given command:
    PS> Get-Help Stop-Process -Parameter * | Where pipelineInput -like True*
    
    -Id <Int32[]>
        Specifies the process IDs of the processes to stop. To specify multiple IDs, use commas to separate the IDs. To find the PID of a process, type 
        `Get-Process`.
        
        Required?                    true
        Position?                    0
        Default value                None
        Accept pipeline input?       True (ByPropertyName)
        Accept wildcard characters?  false
        
    
    -InputObject <Process[]>
        Specifies the process objects to stop. Enter a variable that contains the objects, or type a command or expression that gets the objects.
        
        Required?                    true
        Position?                    0
        Default value                None
        Accept pipeline input?       True (ByValue)
        Accept wildcard characters?  false
        
    
    -Name <String[]>
        Specifies the process names of the processes to stop. You can type multiple process names, separated by commas, or use wildcard characters.
        
        Required?                    true
        Position?                    named
        Default value                None
        Accept pipeline input?       True (ByPropertyName)
        Accept wildcard characters?  false
    

    Note: The above technique gleans the parameter information from the MAML-based help file that may accompany a given cmdlet (most built-in cmdlets do come with such help files). While the information in the help file should correctly reflect the cmdlet's actual parameter definitions and typically does, it isn't guaranteed to. When in doubt, use the following technique instead, which directly examines a cmdlet's actual definition:

    # Lists all pipeline-binding parameters defined by the given cmdlet,
    # by examining the actual cmdlet definition.
    (Get-Command Stop-Process).ParameterSets.Parameters | 
      Where-Object { $_.ValueFromPipeline -or $_.ValueFromPipelineByPropertyName} |
        Select-Object -Unique Name, Aliases, ParameterType, @{
          Name = 'Accepts pipeline input'
          Expression = { 
            'True ({0})' -f  ($(
               if ($_.ValueFromPipeline) { 'ByValue'}
               if ($_.ValueFromPipelineByPropertyName) { 'ByPropertyName' }
            ) -join ', ')
          }
        } | Sort-Object Name
    

    [1] Unless a parameter is declared to support a custom type conversion via a System.Management.Automation.ArgumentTransformationAttribute-derived attribute (which is uncommon), PowerShell's usual conversion rules apply here, which employ several techniques, discussed in this answer. In the case of System.Diagnostics.Process, conversion from a string isn't possible, because the target type neither has a single-argument constructor that has a string, nor does it have a static .Parse() method. A quick test for convertibility is to attempt a cast: [System.Diagnostics.Process] 'notepad' fails. By contrast, [System.ServiceProcess.ServiceController] 'alg' works, because that type does have a single-parameter constructor that accepts a string, but note that this conversion does not come into play during parameter binding in a call to Stop-Service such as 'alg' | Stop-Service - there, the string is bound as-is to the ByValue -Name parameter.