Feeding Get-Command output to itself via the pipeline - useful if you want to force loading of all modules in the context of reflection - doesn't work as expected.
It looks like a new parameter set with a System.Management.Automation.CommandInfo-typed parameter would have to be introduced to support that.
# OK
('Add-Content', 'Get-Content' | Get-Command).Name | Should -Be 'Add-Content', 'Get-Content'
# Fails
(Get-Command 'Add-Content', 'Get-Content' | Get-Command).Name | Should -Be 'Add-Content', 'Get-Content'
Both tests should succeed.
The 2nd test fails, because the only CommandInfo object output is the one for Add-Content
Expected @('Add-Content', 'Get-Content'), but got 'Add-Content'
PowerShell Core 7.0.0-preview.4
This should just work today and if you use a scriptblock parameter, it does:
PSCore (2:41) > (Get-Command 'Add-Content', 'Get-Content' | Get-Command -name {$_.Name})
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Add-Content 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Get-Content 7.0.0.0 Microsoft.PowerShell.Management
For some reason, the Name parameter which is marked ValueFromPipelineByPropertyName = true, is not automatically binding to the Name property on the CommandInfo object. What we need is to figure out why it isn't working. I see no reason to add another parameter.
If I recall correctly, this type of issue has come up before somewhere... perhaps with one of the *-Process cmdlets? That issue was closed as "by design" if I recall correctly, but the symptom was similar. I think it had to do with piping Get-Process objects to Stop-Process, but the default pipeline property was -Id on Stop-Process and wasn't binding as expected to the property. Something like that, anyway.
If memory serves, the pipeline binder has some odd behaviour when it comes to binding input objects when there is not a directly-bindable -InputObject-style parameter. Basically, ValueFromPipelineByPropertyName only works as expected when you have a parameter that can bind the _entire_ object (i.e., is ValueFromPipeline and is of the correct type to bind the entire object). There is no handling for a case like this when piping an object into the cmdlet would need the main object to be _discarded_ so that you can only apply ValueFromPipelineByPropertyName binding.
So we either have to treat _that_ problem as described, or we have to treat the problem from the command itself, which would involve adding an -InputObject style parameter to accept the entire object (or modifying -Name to accept entire objects, possibly by applying a parameter transformation attribute, though that would result in input objects being stripped down to their Name property, then retrieved _again_ which would be a noticeable performance penalty in many cases).
With that in mind, I _think_ the most appropriate fix is for the binder to be more careful about squashing the entire object into a single ValueFromPipeline parameter and try to be aware of the fact that sometimes binding all the object's properties will be preferable to stripping them away and blindly preferring ValueFromPipeline parameters regardless of the input object's type.
Thanks, @vexx32, good points.
My - cursory - examination suggests that the problem may actually be that _multiple_ parameters are bound, as shown in the following example:
PS> [pscustomobject] @{ Name = 'Add-Content'; Noun = 'Foo' } | Get-Command
# !! NO output
Conversely:
PS> [pscustomobject] @{ Name = 'Add-Content'; Noun = 'Content' } | Get-Command
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Add-Content 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Clear-Content 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Get-Content 7.0.0.0 Microsoft.PowerShell.Management
Cmdlet Set-Content 7.0.0.0 Microsoft.PowerShell.Management
It kind of looks like Name just gets utterly ignored .... Tracing that command's binding is interesting:
DEBUG: 2019-10-21 18:15:43.6544 ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Command]
DEBUG: 2019-10-21 18:15:43.6553 ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-Command]
DEBUG: 2019-10-21 18:15:43.6560 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Command] DEBUG: 2019-10-21 18:15:43.6567 ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: 2019-10-21 18:15:43.6573 ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Get-Command] DEBUG: 2019-10-21 18:15:43.6579 ParameterBinding Information: 0 : PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
DEBUG: 2019-10-21 18:15:43.6586 ParameterBinding Information: 0 : RESTORING pipeline parameter's original values DEBUG: 2019-10-21 18:15:43.6592 ParameterBinding Information: 0 : Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: 2019-10-21 18:15:43.6602 ParameterBinding Information: 0 : BIND arg [@{Name=Add-Content; Noun=Content}] to parameter [Name]
DEBUG: 2019-10-21 18:15:43.6634 ParameterBinding Information: 0 : Binding collection parameter Name: argument type [PSObject], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: 2019-10-21 18:15:43.6656 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
DEBUG: 2019-10-21 18:15:43.6670 ParameterBinding Information: 0 : Argument type PSObject is not IList, treating this as scalar
DEBUG: 2019-10-21 18:15:43.6682 ParameterBinding Information: 0 : BIND arg [@{Name=Add-Content; Noun=Content}] to param [Name] SKIPPED
DEBUG: 2019-10-21 18:15:43.6691 ParameterBinding Information: 0 : Parameter [ListImported] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6705 ParameterBinding Information: 0 : Parameter [All] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6714 ParameterBinding Information: 0 : Parameter [Syntax] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6723 ParameterBinding Information: 0 : Parameter [TotalCount] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6732 ParameterBinding Information: 0 : Parameter [FullyQualifiedModule] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6740 ParameterBinding Information: 0 : Parameter [Module] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6748 ParameterBinding Information: 0 : Parameter [Noun] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6755 ParameterBinding Information: 0 : BIND arg [Content] to parameter [Noun]
DEBUG: 2019-10-21 18:15:43.6763 ParameterBinding Information: 0 : Binding collection parameter Noun: argument type [String], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementTypeDEBUG: 2019-10-21 18:15:43.6772 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
DEBUG: 2019-10-21 18:15:43.6805 ParameterBinding Information: 0 : Argument type String is not IList, treating this as scalar
DEBUG: 2019-10-21 18:15:43.6814 ParameterBinding Information: 0 : Adding scalar element of type String to array position 0
DEBUG: 2019-10-21 18:15:43.6832 ParameterBinding Information: 0 : BIND arg [System.String[]] to param [Noun] SUCCESSFUL
DEBUG: 2019-10-21 18:15:43.6841 ParameterBinding Information: 0 : Parameter [Verb] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2019-10-21 18:15:43.6852 ParameterBinding Information: 0 : Parameter [ListImported] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6870 ParameterBinding Information: 0 : Parameter [All] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6878 ParameterBinding Information: 0 : Parameter [Syntax] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6884 ParameterBinding Information: 0 : Parameter [TotalCount] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6892 ParameterBinding Information: 0 : Parameter [FullyQualifiedModule] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6899 ParameterBinding Information: 0 : Parameter [Module] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6907 ParameterBinding Information: 0 : Parameter [Verb] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6933 ParameterBinding Information: 0 : Parameter [ListImported] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6943 ParameterBinding Information: 0 : Parameter [All] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6953 ParameterBinding Information: 0 : Parameter [Syntax] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6964 ParameterBinding Information: 0 : Parameter [TotalCount] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6974 ParameterBinding Information: 0 : Parameter [FullyQualifiedModule] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.6988 ParameterBinding Information: 0 : Parameter [Module] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2019-10-21 18:15:43.7001 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Command]
DEBUG: 2019-10-21 18:15:43.7804 ParameterBinding Information: 0 : CALLING EndProcessing
Most interesting point being:
DEBUG: 2019-10-21 18:15:43.6602 ParameterBinding Information: 0 : BIND arg [@{Name=Add-Content; Noun=Content}] to parameter [Name] DEBUG: 2019-10-21 18:15:43.6634 ParameterBinding Information: 0 : Binding collection parameter Name: argument type [PSObject], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType DEBUG: 2019-10-21 18:15:43.6656 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements DEBUG: 2019-10-21 18:15:43.6670 ParameterBinding Information: 0 : Argument type PSObject is not IList, treating this as scalar DEBUG: 2019-10-21 18:15:43.6682 ParameterBinding Information: 0 : BIND arg [@{Name=Add-Content; Noun=Content}] to param [Name] SKIPPED
In other words... yes, the binder _only_ attempts to bind ValueFromPipeline and ignores the fact that a parameter may be declaring both. I hope that illustrates the problem here well! 馃檪
The problem seems to be that -Name is in the AllCommandSet and the default parameter set is CmdletSet. -Noun is in the CmdletSet (along with -Verb) which is default, so Noun gets bound. Since that parameter is bound, it doesn't try to bind Name anymore. So we can fix this by introducing a new parameter of type CommandInfo so it's not a breaking change. Or we can change the default parameterset to AllCommandSet which will then bind to Name but is a breaking change.
It seems that it may be ok to simply change the default parameter set to fix this. So then the behavior is that Name would be bound before Verb and Noun if it exists. Seems like bucket 3 in being unlikely to affect anyone.
From what I can see it seems pretty clear that the binder _is already_ trying and failing to bind -Name ByValue before it's attempting anything else. Am I missing something in the trace log? :slightly_smiling_face:
You are correct that happens, but since it fails, it then tries to bind ValueFromPipelineByPropertyName where (in the above example), it finds Noun which is in the default parameter set, binds and since the Noun is nonsense doesn't return any results. In the example where there is both Name and Noun and Noun is valid, you get results but Name is not bound. Had to step through the debugger to observe all this.
Thanks for digging deeper.
So we can fix this by introducing a new parameter of type
CommandInfoso it's not a breaking change.
That seems like the most obvious and robust fix, if the intent is to recognize CommandInfo input as such - I think it should be.
Binding to -Name alone can be ambiguous.
@PowerShell/powershell-committee reviewed this. There's two distinct issues. One is passing Name and having it bound to Noun is by-design as that is the default parameterset. The other issue of piping CommandInfo to Get-Command I debugged and have a solution.
:tada:This issue was addressed in #10929, which has now been successfully released as v7.0.0-preview.6.:tada:
Handy links:
Most helpful comment
You are correct that happens, but since it fails, it then tries to bind ValueFromPipelineByPropertyName where (in the above example), it finds Noun which is in the default parameter set, binds and since the Noun is nonsense doesn't return any results. In the example where there is both
NameandNounandNounis valid, you get results butNameis not bound. Had to step through the debugger to observe all this.