Powershell: Pipelining a Simple object to Get-Process is broken

Created on 5 Jul 2020  Â·  10Comments  Â·  Source: PowerShell/PowerShell

Steps to reproduce

$ob1 = @{
    ProcessName = 'pwsh'
}

$ob1 | Get-Process

Expected behavior

returns running pwsh Processes

Actual behavior


Get-Process: 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.

Environment data


Name                           Value
----                           -----
PSVersion                      7.0.2
PSEdition                      Core
GitCommitId                    7.0.2
OS                             Microsoft Windows 10.0.20161
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0


Area-Cmdlets-Management Issue-Question

Most helpful comment

Note that changing this would change the behavior of existing code:

function Test-Function {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Name,

        [Parameter(ValueFromPipeline)]
        [hashtable] $Hashtable
    )
    process {
        if ($PSBoundParameters.ContainsKey('Name')) {
            $Name
        } else {
            $Hashtable
        }
    }
}

[pscustomobject]@{ Name = 'test' } | Test-Function
@{ Name = 'test' } | Test-Function

Currently returns:

test

Name                           Value
----                           -----
Name                           test

But after the changes would return:

test
test

@kilasuit It's hard to tell how many years of experience someone has from a pseudonym and an avatar. Though I definitely agree that a brief description would have helped the message come off as slightly less terse, there was a nicer way to say that as well.

All 10 comments

This is fundamentally the same issue as https://github.com/PowerShell/PowerShell/issues/13103 -- it has more to do with the parameter binder itself than the individual cmdlets, as far as I can tell?

I think it may be in both the parameter binder & also the cmdlets themselves

Can you elaborate? 😅

While it's possible for the cmdlets to take a hashtable as pipeline input, and this could be individually added to them, it's not a thing that broadly works across most cmdlets I can think of at the moment. Are there cmdlets you're thinking of that seem to allow this specific kind of input? 🤔

@kilasuit

#$ob1 = @{
$hash = @{
    ProcessName = 'pwsh'
}

$ob1 = [pscustomobject]@{
    ProcessName = 'pwsh'
}

$ob1 | Get-Process

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     59    38,62      70,49       1,28    9228   1 pwsh

Ya @scriptingstudio, that's known as per the linked issue (see some of the other comments) and was well aware of that anyway

However, a PSCustomObject is not a Simple object like a basic hashtable is

And also requires actual knowledge of PowerShell to understand that it doesn't just work which for newcomers will be a pain point.

Also fyi, that kind of response, isn't all that friendly, at least preface it "Hey did you know about this?“ which if you'd also read the linked issue too you'd have seen basically the same comment from @vexx32 and seen your comment wasn't all that useful.

Also further FYI, 7 going on 8 years PowerShell experience here (& teacher too), I was already aware that this worked with PSCustomObjects, also as highlighted in the linked issue.

Note that changing this would change the behavior of existing code:

function Test-Function {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Name,

        [Parameter(ValueFromPipeline)]
        [hashtable] $Hashtable
    )
    process {
        if ($PSBoundParameters.ContainsKey('Name')) {
            $Name
        } else {
            $Hashtable
        }
    }
}

[pscustomobject]@{ Name = 'test' } | Test-Function
@{ Name = 'test' } | Test-Function

Currently returns:

test

Name                           Value
----                           -----
Name                           test

But after the changes would return:

test
test

@kilasuit It's hard to tell how many years of experience someone has from a pseudonym and an avatar. Though I definitely agree that a brief description would have helped the message come off as slightly less terse, there was a nicer way to say that as well.

In terms of terminology, @kilasuit, I recommend talking about _dictionaries_ or _hash tables_, not _simple objects_, because we want to distinguish objects whose _properties_ (from a .NET type perspective) to operate on from dictionary-like types that happen to use .NET properties to store a collection of _key-value pair_ entries. I also suggest removing the reference to Get-Process from the title, and to close the effective duplicate, #13103, now that we've established that the behavior applies to the parameter binder in general, not to specific cmdlets.


Generally speaking, it makes sense to treat objects with bona fide _properties_ and _dictionaries_ (hash tables) _interchangeably_, as is already the case in many contexts; e.g., we've implemented that recently for Select-Object; similarly, ConvertTo-Json supports hash-table input, and #10999 suggests adding such support to Export-Csv / ConvertTo-Csv.


@SeeminglyScience, while with your specific code the behavior would indeed change, note that $Hashtable would still _also_ be bound, and - as seems more likely - if you put the two parameters into _separate parameter sets_ and use $PSCmdlet.ParameterSetName, things will work as before, because the type-as-a-whole ValueFromPipeline binding (sensibly) takes precedence:

class Foo {
  [string] $Name = 'Property'
  [string] ToString() { return 'Whole object' }
}

function Test-Function {
  [CmdletBinding()]
  param(
      [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='Property')]
      # [Parameter(ValueFromPipelineByPropertyName)]
      [string] $Name,

      [Parameter(ValueFromPipeline, ParameterSetName='WholeObj')]
      # [Parameter(ValueFromPipeline)]
      [Foo] $WholeObj
  )
  process {
      "Parameter set: $($PSCmdlet.ParameterSetName)"
      if ($PSBoundParameters.ContainsKey('Name')) {
          $Name.ToString()
      }
      if ($PSBoundParameters.ContainsKey('WholeObj')) {
        $WholeObj.ToString()
      }
  }
}

[Foo]::new() | Test-Function

The above yields:

Parameter set: WholeObj
Whole object

If you _don't_ use distinct parameters sets (use the commented-out [Parameter(...)] lines instead), you'll see that _both_ parameters are bound:

Parameter set: __AllParameterSets
Property
Whole object

True, the proposed change would then also bind $Name in cases where it didn't before, but, overall, I think this proposal is worth considering, and the technically breaking change strikes me as falling into Bucket 3: Unlikely Grey Area.

Hah that's what I get for trying to use minimal code to demo huh? I wouldn't have bet it would actually assign both 🙂

That's wild, learn something new every day. Good catch @mklement0!

@SeeminglyScience - totally agree with you & perhaps I could have put it across kinder too, something for me to work on. My Bad.

In terms of education around terminology @mklement0 we need to be mindful of those coming to PowerShell at all ages & skill sets, because, for example, I fully intend to teach my ten year old son all about PowerShell & will look forward to the day where he teaches me about it instead of me teaching him.

Even so, this is starting to feel very much like it may actually have been an unfortunate design flaw in the cmdlet that may possibly even date back to v1 days

@kilasuit

We need to be mindful of terminology that is _accurate (enough)_, which comes with unavoidable intrinsic complexity, if important underlying distinctions are to be reflected.
My recommendation (_dictionaries_ (hash tables) vs. (non-dictionary) _objects_) reflects this approach, whereas the term _simple object_ invites confusion, because it erases the fundamental distinction described in my previous comment.

starting to feel very much like it may actually have been an unfortunate design flaw in the cmdlet that may possibly even date back to v1 days

Again: this behavior isn't specific to any specific cmdlet, it is built into the parameter binder, and therefore applies to any cmdlet with by-property pipeline-binding parameters.

Using the _entries_ of _dictionaries_ (hash tables) _as if they were object properties_ - allowing for interchangeable use of non-dictionary objects based on their bona fide properties and dictionaries based on their entries (which are implemented _via_ bona fide properties) - is _inconsistently_ supported: ConvertTo-Json and (now) Select-Object already do, but Export-Csv / ConvertTo-Csv do not (yet) - and https://github.com/PowerShell/PowerShell/issues/10999#issuecomment-552109741 shows the useless behavior that results if you treat a dictionary based on its bona fide properties only.

Given the close conceptual relationship between (custom) objects and dictionaries, it makes sense to support their interchangeable use more widely:

  • 10999 is a step in that direction (as is #11027 in the _opposite_ direction: supporting dictionary _output_ as an alternative to custom objects, as only supported by _some_ cmdlets)

  • similarly, _this_ proposal could be another step in that direction - but in order for this to go anywhere, it needs to be framed properly: as an enhancement to the parameter binder, not as an issue with an individual cmdlet, with a discussion of the potential ramifications.

Was this page helpful?
0 / 5 - 0 ratings