1

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?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Purclot
  • 483
  • 7
  • 22
  • bypropertyname and byvalue are different. – js2010 Apr 14 '20 at 17:03
  • `"notepad"` is _not_ `ByPropertyName`, it is `ByValue`. The term `ByValue` may be confusing, but all it says is: bind the input object _as a whole_, not by one of its _property values_ (which is `ByPropertyName`). – mklement0 Apr 14 '20 at 21:09

3 Answers3

5

If you want to bind an object's value to a parameter by property name, either:

  1. Pass an object with an appropriately named property:
[pscustomobject]@{Name='notepad'} |Stop-Process
# or, for older versions of powershell:
'notepad' |Select @{Name='Name';Expression={$_}} |Stop-Process
  1. Explicitly bind a pipeline expression to the named parameter:
'notepad' |Stop-Process -Name {$_}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • does it mean: ByValue= I'm supplying a string (as "alg"), PowerShell finds a property "Name" and stops the service. ByPropertyName= I'm supplying an object, which must have a property "name"? – Purclot Apr 14 '20 at 15:41
  • `ByValue` = "bind any object of the correct type as-is to this parameter", `ByPropertyName` = "bind the value of a property with the same _name and type_ (if found) to this parameter". PowerShell is a bit more liberal with type conversions than, say C#, but this is generally the principle – Mathias R. Jessen Apr 14 '20 at 15:45
  • ok, so I still don't get it: stop-process accepts -name ByPropertyName, right? "notepad" | stop-process gets "name" as a string, why it doesn't work? – Purclot Apr 14 '20 at 15:47
  • 2
    Because the `[string]` "notepad" _doesn't_ have any `Name` property – Mathias R. Jessen Apr 14 '20 at 15:49
  • 1
    The `ByPropertyName` binding means that the parameter looks for a property with that name in the input object. The `String` object doesn't have a `Name` property. – Bill_Stewart Apr 14 '20 at 15:53
  • ok, but using: "alg" | stop-service, "alg" is also supplied ByPropertyName, it's also a string and it doesn't have a property named "name"? – Purclot Apr 14 '20 at 15:58
  • 1
    @Purclot That's because the `Name` parameter on `Stop-Service` is marked with _both_ - it will accept input by property name, and, failing that, it falls back to binding by value – Mathias R. Jessen Apr 14 '20 at 16:15
  • 1. does it say: "alg" | stop-service "alg" is definitely delivered as ByValue (it's an object, a string, with a property "Name")? 2. ByValue is the standard parameter anyway? – Purclot Apr 14 '20 at 16:58
  • 1
    @mklement0 I'm writing up an explanation for the "how does this even work" part of the question, but it's quite long-winded – Mathias R. Jessen Apr 14 '20 at 18:10
2
  • 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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

"stop-service -name" can be piped byvalue (string), so it uses that. Stop-process fails to convert a string to the process type for -inputobject, which is pipe byvalue. "Stop-process -name" is bypropertyname only.

There's a little more nuance going on here. A string can be coerced to type servicecontroller, but not to type process. So a string could theoretically be piped in as -inputobject for stop-service, but not for stop-process. "stop-service -inputobject alg" works as well, but "stop-process -inputobject notepad" does not. The inconsistency between these two commands can be confusing.

[System.ServiceProcess.ServiceController]'alg'

Status   Name               DisplayName
------   ----               -----------
Stopped  alg                Application Layer Gateway Service


[system.diagnostics.process]'notepad'
InvalidArgument: Cannot convert the "notepad" value of type "System.String" to type "System.Diagnostics.Process".

If you really want to see the bindings:

trace-command parameterbinding {'alg' | stop-service} -pshost
trace-command parameterbinding {'notepad' | stop-process} -pshost
js2010
  • 23,033
  • 6
  • 64
  • 66
  • hmm, now I'm a bit confused. Stay by Start-Service (or stop-Service): ByValue=the input is a "whole" object of type: [-InputObject] , so using: "alg" | start(stop)-Service, "alg" is definitely NOT an object of type: ServiceController NOR an object of any kind with a property "name", means "alg" will be (must be?) casted to an object of type ServiceController. And as far as I understand the help: ByValue will be allways used in first place? – Purclot Apr 14 '20 at 18:33
  • 1
    I think because -name is string type, 'alg' gets matched to it first. And "stop-service -name" can by byvalue, but "stop-process -name" cannot be byvalue. – js2010 Apr 14 '20 at 18:41
  • Thanks for updating, js2010, but the paragraph about a "little more nuance" is still misleading; by contrast, the tip about tracing parameter binding is helpful. – mklement0 Apr 14 '20 at 19:00