Trouble with Dynamic Parameter Positioning in ParameterSets

Thanks again to Rohn Edwards, who helped me with my first DynamicParam problem. Maybe he or someone can help me with what I hope is my last issue…

Having a hard time positioning an established Dynamic Parameter (Set1C in this example) in the right location. The code at the bottom, which starts with everything in the same ParameterSetName gets me this:

PS G:\> help ParamTest

NAME
    ParamTest
    
SYNTAX
    ParamTest [-A] (string)  [-Set1] [-Set2] [-Set1B] (string) [-Set1C] (string) {Option1,Option2,Option3} [-Set2B] (string)  [CommonParameters]
    

ALIASES
    None
    

REMARKS
    None

I’m trying to modify it so that the syntax shows like this:

PS G:\> help ParamTest

NAME
    ParamTest
    
SYNTAX
    ParamTest [-A] (string) [-Set1] [-Set1B] (string) [-Set1C]  {Option1,Option2,Option3} (string)  [CommonParameters]
    
    ParamTest [-A] (string) [-Set2] [-Set2B] (string)  [CommonParameters]
    

ALIASES
    None
    

REMARKS
    None

Every way I try to do this ends up with the Dynamic Parameter (Set1C) just disappearing from the parameter list…

The other thing I notice is that, even though the ordering shows Set1C in the 5th position when you use help, when you actually tab through the parameters on the command line, in populates Set1C last, ignoring the position attribute. I assume it’s because the DynamicParam section is being evaluated after the Param section, but is there any way around that?

Here’s the code. Thanks to anyone who can offer assistance!

Function ParamTest {
[CmdletBinding()]
Param (

        [Parameter(Mandatory=$true,Position=1,ParameterSetName="__AllParameterSets")]
        [string]
        $A,
                
        [Parameter(Mandatory=$true,Position=2,ParameterSetName="__AllParameterSets")]
        [switch]
        $Set1,
        
        [Parameter(Mandatory=$true,Position=3,ParameterSetName="__AllParameterSets")]
        [switch]
        $Set2,

        [Parameter(Mandatory=$true,Position=4,ParameterSetName="__AllParameterSets")]
        [string]
        $Set1B,

        [Parameter(Mandatory=$true,Position=6,ParameterSetName="__AllParameterSets")]
        [string]
        $Set2B

    )
DynamicParam {
            # Set the dynamic parameters' name
            $ParameterName = 'Set1C'
            
            # Create the dictionary 
            $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

            # Create the collection of attributes
            $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            
            # Create and set the parameters' attributes
            $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
            $ParameterAttribute.Mandatory = $true
            $ParameterAttribute.Position = 5
            $ParameterAttribute.ValuefromPipeline = $true
            $ParameterAttribute.ValueFromPipelineByPropertyName = $true
            $ParameterAttribute.ParameterSetName = "__AllParameterSets"

            # Add the attributes to the attributes collection
            $AttributeCollection.Add($ParameterAttribute)

            # Generate and set the ValidateSet 
            $arrSet = ("Option1,Option2,Option3")
            $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)

            # Add the ValidateSet to the attributes collection
            $AttributeCollection.Add($ValidateSetAttribute)

            # Create and return the dynamic parameter
            $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
            $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
            return $RuntimeParameterDictionary
        }

    }

Interesting… I modified your code as follows:

Function ParamTest {
[CmdletBinding()]
Param (
 
        [Parameter(Mandatory=$true,Position=1,ParameterSetName="Set1")]
        [Parameter(Mandatory=$true,Position=1,ParameterSetName="Set2")]
        [string]
        $A,
 
        [Parameter(Mandatory=$true,Position=2,ParameterSetName="Set1")]
        [switch]
        $Set1,
 
        [Parameter(Mandatory=$true,Position=3,ParameterSetName="Set2")]
        [switch]
        $Set2,
 
        [Parameter(Mandatory=$true,Position=4,ParameterSetName="Set1")]
        [string]
        $Set1B,
 
        [Parameter(Mandatory=$true,Position=6,ParameterSetName="Set2")]
        [string]
        $Set2B
 
    )

    DynamicParam {
            # Set the dynamic parameters' name
            $ParameterName = 'Set1C'
 
            # Create the dictionary 
            $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
 
            # Create the collection of attributes
            $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
 
            # Create and set the parameters' attributes
            $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
            $ParameterAttribute.Mandatory = $true
            $ParameterAttribute.Position = 5
            $ParameterAttribute.ValuefromPipeline = $true
            $ParameterAttribute.ValueFromPipelineByPropertyName = $true
            $ParameterAttribute.ParameterSetName = "Set1"
 
            # Add the attributes to the attributes collection
            $AttributeCollection.Add($ParameterAttribute)
 
            # Generate and set the ValidateSet 
            $arrSet = ("Option1,Option2,Option3")
            $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
 
            # Add the ValidateSet to the attributes collection
            $AttributeCollection.Add($ValidateSetAttribute)
 
            # Create and return the dynamic parameter
            $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
            $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
            return $RuntimeParameterDictionary
        }
 
    }

Which reproduces the behavior you’re describing, of -Set1C disappearing from Get-Command -Syntax. That parameter also doesn’t tab complete until you’ve specified enough parameters to make powerShell certain that “Set1” is in effect. For example, typing ParamTest -A Whatever -{Tab} will not show Set1C in the list. Once you’ve typed either -Set1 or -Set1B Something, then it’ll show up. I’d call that a bug in PowerShell’s handling of dynamic parameters. It should be showing all parameters that could still potentially be valid based on the currently available sets, rather than only showing dynamic parameters once a single parmaeter set is the only valid one remaining. Dynamic parameters assigned to a specific parameter set should also be showing up in Get-Command -Syntax at all times.

I’m not sure if this is a bug that would also affect compiled cmdlets, or if it’s specific to advanced functions. I’ll try to test that when I have time. (There may also already be bugs filed for this on the Connect site; haven’t checked yet.)

Two bugs in two days - story of my life lol!

Yep, that’s almost exactly how I was trying to modify it. Glad I’m not missing something obvious. Fingers crossed for a workaround or fix…

Thanks!

I’ve had issues with getting dynamic parameters to show up in Get-Help and Get-Command -Syntax properly in the past. There’s at least one connect bug on it here: https://connect.microsoft.com/PowerShell/feedback/details/397832/dynamicparam-not-returned-when-aliased

I haven’t tried to run your example (maybe I’ll have time later), so forgive me if it’s not the exact issue you’re describing.

This may be a bit of an old thread but I ran into this issue today while working on some code. I still think it is a bug, but I think I found a work around. If you specify a defaultparametersetname in the cmdletbinding attribute, all the sets seems to show up and will tab complete.

For example, modifying Dave’s code by:

Function ParamTest {
[CmdletBinding(DefaultParameterSetName='Set1c')]

seems to be enough to get all parametersets working. Maybe that is a way of forcing the condition that Dave mentions about resolving to a single valid parameter set. Maybe setting defaultparametersetname defaults that condition. I only spent a little time on it, but it worked for my code…