Skip to content

ValueFromPipelineByPropertyName doesn't work with multiple paramsets. #12619

Closed

Description

Steps to reproduce

I have defined a cmdlet with two parametersets. Each paramset allows parameters to be passed from the pipeline by the propertyName. The cmdlet works fine if I pass the parameters directly. If I pipeline an object, Cmdlet always resolves the parameter set to Default instead of TestParamSet

CmdLet Code:

    [Cmdlet("Get", "SampleCmdlet", DefaultParameterSetName = "Default")]
    [OutputType(typeof(string))]
    public class GetSampleCmdlet : PSCmdlet
    {

        [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Default")]
        [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "TestParamSet")]
        public string ParamOne { get; set; }

        [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "TestParamSet")]
        public string ParamTwo { get; set; }


        protected override void ProcessRecord()
        {
            Console.Out.WriteLine($"ParamSet: {ParameterSetName}");
            Console.Out.WriteLine($"ParamOne: {ParamOne} ParamTwo: {ParamTwo}");

            WriteObject("Hello World!");
        }

    }

Invoking the Cmdlet with parameters:

PS /Users/> Get-SampleCmdlet -ParamOne "One"
ParamSet: Default
ParamOne: One ParamTwo: 
Hello World!

PS /Users/> Get-SampleCmdlet -ParamOne "one" -ParamTwo "two"
ParamSet: TestParamSet
ParamOne: one ParamTwo: two
Hello World!

PS /Users/> skumbham-mac:~ skumbham$ pwsh

Passing the Parameters through the pipeline. Powershell always resolves the parameterset to Default

$test=@{}
$test["ParamOne"]="pipeline-one"
$test["ParamTwo"]="pipeline-two"
$obj = [pscustomobject]$test

$obj | Get-SampleCmdlet

ParamSet: $Default
ParamOne: pipeline-one ParamTwo:
Hello World!

Expected behavior

ParameterSet should be TestParamSet

Actual behavior

ParameterSet defaults to Default paramset.

Environment data

PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.2.4
PSEdition                      Core
GitCommitId                    6.2.4
OS                             Darwin 18.7.0 Darwin Kernel Version 18.7.0: Tue Aug 20 16:57:14 PDT 2019; root:xnu-4903.271.2~2/RELEASE_X86_64
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on May 11, 2020
  2. iSazonov commented on May 11, 2020

    @iSazonov
    Collaborator
  3. alx9r commented on May 12, 2020

    @alx9r

    Here is a repro in PowerShell:

    function Get-SampleCmdlet
    {
        [CmdletBinding(DefaultParameterSetName = "Default")]
    
        param
        (
            [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName = "Default")]
            [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName = "TestParamSet")]
            [string]
            $ParamOne,
    
            [Parameter(Mandatory,ValueFromPipelineByPropertyName, ParameterSetName = "TestParamSet")]
            [string]
            $ParamTwo
        )
        process {
            "ParamSet: $($PSCmdlet.ParameterSetName)"
            "ParamOne: $ParamOne"
            "ParamTwo: $ParamTwo"
        }
    }
    
    [pscustomobject]@{
        ParamOne = 'pipeline-one'
        ParamTwo = 'pipeline-two'
    } |
        Get-SampleCmdlet

    Output:

    ParamSet: Default
    ParamOne: pipeline-one
    ParamTwo: 
    
  4. alx9r commented on May 12, 2020

    @alx9r

    Removing DefaultParameterSetName = "Default" results in

    ParamSet: TestParamSet
    ParamOne: pipeline-one
    ParamTwo: pipeline-two
    

    Changing the call site to

    [pscustomobject]@{
        ParamOne = 'pipeline-one'
        ParamTwo = 'pipeline-two'
    } |
        Get-SampleCmdlet -ParamTwo 'named-two'

    results in

    ParamSet: TestParamSet
    ParamOne: pipeline-one
    ParamTwo: named-two
    
    

    It's not obvious to me that affinity to the Default parameter set in the OP should be considered wrong. It seems reasonable to me that, when there are two viable parameter sets, the binder would choose the default parameter set.

  5. kumbham commented on May 12, 2020

    @kumbham
    Author

    alx9r thanks for your response. It seems like if we remove the DefaultParameterSetName, normal cmdlet invocation seems to be breaking. here's the output after removing DefaultParameterSetName

    PS /Users/> Get-SampleCmdlet
    Get-SampleCmdlet : Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
    At line:1 char:1
    + Get-SampleCmdlet
    + ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-SampleCmdlet], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameterSet,ocipstools_objectstorage.CmdLets.GetSampleCmdlet

    Pipelining the object works fine though:

    PS /Users/> $test=@{}
    PS /Users/> $test["ParamOne"]="pipeline-one"
    PS /Users/> $test["ParamTwo"]="pipeline-two"
    PS /Users/> $obj = [pscustomobject]$test
    PS /Users/> $obj | Get-SampleCmdlet
    ParamSet: TestParamSet
    ParamOne: pipeline-one ParamTwo: pipeline-two
    Hello World!

    Any other suggestions on how to fix this? alx9r Ilya (@iSazonov)

  6. jhoneill commented on May 12, 2020

    @jhoneill

    Here's my suggestion

    function Get-SampleCmdlet
    {
        [CmdletBinding(DefaultParameterSetName = "Default")]
    
        param
        (
            [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
            [string]
            $ParamOne,
    
            [Parameter(Mandatory,ValueFromPipelineByPropertyName, ParameterSetName = "TestParamSet")]
            [string]
            $ParamTwo
        )
        process {
            "ParamSet: $($PSCmdlet.ParameterSetName)"
            "ParamOne: $ParamOne"
            "ParamTwo: $ParamTwo"
        }
    }

    Then we get

    > [pscustomobject]@{ ParamOne = 'pipeline-one' ;  ParamTwo = 'pipeline-two'} |     SampleCmdlet
    
    ParamSet: TestParamSet
    ParamOne: pipeline-one
    ParamTwo: pipeline-two
    

    and with one field but not the second

    [pscustomobject]@{ParamOne = 'pipeline-one'} |    Get-SampleCmdlet
    ParamSet: Default
    ParamOne: pipeline-one
    ParamTwo: 
    

    No input

    >Get-SampleCmdlet
    cmdlet Get-SampleCmdlet at command pipeline position 1
    Supply values for the following parameters:
    ParamOne: aaa
    ParamSet: Default
    ParamOne: aaa`
    

    Optional param piped but mandatory missing

    [pscustomobject]@{    ParamTwo = 'pipeline-two'} |    Get-SampleCmdlet
    Get-SampleCmdlet : The input object cannot be bound because it did not contain the information required to bind all mandatory parameters:  ParamOne
    
    

    Optional piped mandatory on the command line

    [pscustomobject]@{    ParamTwo = 'pipeline-two'} |    Get-SampleCmdlet -ParamOne foo
    ParamSet: TestParamSet
    ParamOne: foo
    ParamTwo: pipeline-two
    
  7. alx9r commented on May 12, 2020

    @alx9r

    Srikanth Reddy (@kumbham)

    It seems like if we remove the DefaultParameterSetName, normal cmdlet invocation seems to be breaking. ...

    PS /Users/> Get-SampleCmdlet
    Get-SampleCmdlet : Parameter set cannot be resolved...
    

    Because each parameter has the Mandatory attribute, the parameter binder considers there to be no viable parameter set.

    I think using another unused parameter set name like None for DefaultParameterSetName might achieve the behavior you are expecting:

    function TwoSets{
        [CmdletBinding(DefaultParameterSetName = 'None')]
        param ( $NoSet,
                [parameter(ParameterSetName = 'A', Mandatory = $true)]$A,
                [parameter(ParameterSetName = 'B', Mandatory = $true)]$B  )
        process { $PSCmdlet.ParameterSetName }
    }
    
    TwoSets        # None
    TwoSets -A 'a' # A
    TwoSets -B 'b' # B

    The matter of creating a parameter set with no mandatory parameters at all was puzzling enough to me that it led to stackoverflow#43701900.

    Note that this part of the parameter binder is the topic of some recent discussion on #11143 and #11237.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.WG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions