Why can't I use command substitution instead of piping when using a powershell command such as Get-Member?

So I'm trying to get the list of members of a process using the following command which doesn't work:

Get-Member $(Get-Process -Name chrome)

The resulting error:

Get-Member : You must specify an object for the Get-Member cmdlet.

However, this works:

Get-Process -Name chrome | Get-Member

So the command substitution isn't generating an object type output? Can some one help me understand the difference?

Thanks

1 answer

  • answered 2019-09-14 23:31 mklement0

    In order to pass input to Get-Member by argument (as opposed to via the pipeline), you must use the -InputObject parameter explicitly:

    Get-Member -InputObject (Get-Process -Name chrome)
    

    Also note that (...) is sufficient here - no need for $(...).[1]

    However, an important caveat is that if Get-Process returns multiple processes, they'll be collected in an array, and Get-Member will then report the array's properties, not that of its elements.

    It is only via the pipeline that the inputs are considered individually, which in Get-Member's case means that the distinct set of input types is reported, and with Get-Process input there's by definition just one distinct type, [System.Diagnostics.Process].

    While in the case of Get-Member the distinction between pipeline and argument input is helpful - it allows you to inspect the properties of collection types themselves if you use the
    -InputObject parameter - in most cases it is useless and a source of confusion
    , given that most cmdlets only meaningfully operate on the individual elements of a collection - which typically doesn't work if you pass a collection via -InputObject; see this GitHub issue.


    As for what you tried:

    If you don't use -InputObject explicitly with Get-Member, your argument is bound to the -Name parameter (which looks for specific type members), which means that Get-Member is then lacking input objects, and that's why it complains.

    (Note that it is up to each cmdlet how to bind positional arguments to its parameters, and a cmdlet may even choose not to support positional arguments at all.)

    You can tell that Get-Member binds the first positional argument to -Name by looking at its syntax diagram, as shown by Get-Member -? or Get-Command -Syntax Get-Member:

    PS> Get-Command -Syntax Get-Member
    
    Get-Member [[-Name] <String[]>] [-InputObject <psobject>] ...
    

    The [...] around parameter name -Name indicates that specifying the parameter name is optional, i.e. that the first positional argument given is bound to it.
    By contrast, the -InputObject parameter name is not enclosed in [...], indicating that it cannot be positionally bound and that an argument to be bound to it must be preceded by the parameter name.

    The -InputObject parameter is - usually and also in this case - also bound by pipeline input, and providing input via the pipeline is more typical (and often the only meaningful way, as discussed above).

    Note that the syntax diagram does not indicate which parameters (there can be multiple) accept pipeline input; changing that is the subject of this GitHub feature request.

    For more information about how to read PowerShell's syntax diagrams, see about_Command_Syntax.


    [1] PowerShell doesn't have Bash-style command substitutions. You can use commands an expressions as arguments by simply enclosing them in (...), and the resulting object(s) are used as-is (no Bash-style shell expansions are performed); you need $(...), the so-called subexpression operator only if you want to pass the output from multiple statements, or inside expandable strings ("...") or to send a compound statement's output (e.g., foreach (...) { ... }) through the pipeline.