How can I prevent non position 0 parameters to appear in position 0 in PowerShell?

180 Views Asked by At

Here are my parameters of one of the nested modules in my main module:

    Param(
        [Parameter(Mandatory = $false, ParameterSetName = "set1", Position = 0, ValueFromPipeline = $true)][switch]$Get_BlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set2", Position = 0, ValueFromPipeline = $true)][switch]$Get_DriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set3", Position = 0, ValueFromPipeline = $true)][switch]$Make_AllowMSFT_WithBlockRules,  
        [Parameter(Mandatory = $false, ParameterSetName = "set4", Position = 0, ValueFromPipeline = $true)][switch]$Deploy_LatestDriverBlockRules,                                                                                       
        [Parameter(Mandatory = $false, ParameterSetName = "set5", Position = 0, ValueFromPipeline = $true)][switch]$Set_AutoUpdateDriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set6", Position = 0, ValueFromPipeline = $true)][switch]$Prep_MSFTOnlyAudit,
        [Parameter(Mandatory = $false, ParameterSetName = "set7", Position = 0, ValueFromPipeline = $true)][switch]$Make_PolicyFromAuditLogs,  
        [Parameter(Mandatory = $false, ParameterSetName = "set8", Position = 0, ValueFromPipeline = $true)][switch]$Make_LightPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set9", Position = 0, ValueFromPipeline = $true)][switch]$Make_SuppPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set10", Position = 0, ValueFromPipeline = $true)][switch]$Make_DefaultWindows_WithBlockRules,
       
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$ScanLocation,
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$SuppPolicyName,
        [ValidatePattern('.*\.xml')][parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$PolicyPath,

        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]        
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [switch]$Deployit,

        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [switch]$TestMode,
        
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [switch]$RequireEVSigners,

        [Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Debugmode,

        [ValidateSet([Levelz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string]$Levels,

        [ValidateSet([Fallbackz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string[]]$Fallbacks, 

        [ValidateRange(1024KB, [int64]::MaxValue)]
        [Parameter(Mandatory = $false, ParameterSetName = "set6")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]        
        [Int64]$LogSize,

        [Parameter(Mandatory = $false)][switch]$SkipVersionCheck    
    )

How can I prevent parameters that are not position 0 to appear in position 0?

I've tried adding other positions like 1,2 etc. to other parameters but that didn't work.

Only parameters with position 0 are the main ones, the rest only need to be used/suggested when a position 0 parameter is first selected by the user.

To clarify, I'm using this

Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete

and seeing parameters that don't belong to position 0 in the menu is just weird and I don't want them to appear there or be selectable.

enter image description here

the yellow underlines show parameters that don't make sense on their own if used in position 0.

Update, using Dynamic parameters, I did this but still the other yellow underlined parameters are showing up for position 0.

So maybe someone can post an answer showing how I can actually implement it in my function.

#requires -version 7.3.3
function New-WDACConfig {
    [CmdletBinding(
        DefaultParameterSetName = "set1",
        HelpURI = "https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDACConfig",
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        ConfirmImpact = 'High'
    )]
    Param(
        [Parameter(Mandatory = $false, ParameterSetName = "set1")][switch]$Get_BlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set2")][switch]$Get_DriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set3")][switch]$Make_AllowMSFT_WithBlockRules,  
        [Parameter(Mandatory = $false, ParameterSetName = "set4")][switch]$Deploy_LatestDriverBlockRules,                                                                                       
        [Parameter(Mandatory = $false, ParameterSetName = "set5")][switch]$Set_AutoUpdateDriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set6")][switch]$Prep_MSFTOnlyAudit,
        [Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Make_PolicyFromAuditLogs,  
        [Parameter(Mandatory = $false, ParameterSetName = "set8")][switch]$Make_LightPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set9")][switch]$Make_SuppPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set10")][switch]$Make_DefaultWindows_WithBlockRules,
       
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$ScanLocation,
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$SuppPolicyName,
        
        [ValidatePattern('.*\.xml')]
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)]
        [string]$PolicyPath,

        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]        
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [switch]$Deployit,

        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [switch]$TestMode,
        
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [switch]$RequireEVSigners,

        [Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Debugmode,

        [ValidateSet([Levelz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string]$Levels,

        [ValidateSet([Fallbackz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string[]]$Fallbacks, 

        [ValidateRange(1024KB, [int64]::MaxValue)]
        [Parameter(Mandatory = $false, ParameterSetName = "set6")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]        
        [Int64]$LogSize,

        [Parameter(Mandatory = $false)][switch]$SkipVersionCheck
            
    )


   # parameter control for Make_AllowMSFT_WithBlockRules - Set3
   DynamicParam {
    if ($PSCmdlet.ParameterSetName -eq "Set3") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'RequireEVSigners', 'TestMode', 'Deployit') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set3" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }
 

    # parameter control for Prep_MSFTOnlyAudit - Set6

    if ($PSCmdlet.ParameterSetName -eq "Set6") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'LogSize') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set6" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }


    # parameter control for Make_PolicyFromAuditLogs - Set7

    if ($PSCmdlet.ParameterSetName -eq "Set7") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Deployit', 'TestMode', 'RequireEVSigners') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }

    if ($PSCmdlet.ParameterSetName -eq "Set7") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Fallbacks', 'Levels') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [string], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }

    if ($PSCmdlet.ParameterSetName -eq "Set7") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'LogSize') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }


    # parameter control for Make_LightPolicy - Set8

    if ($PSCmdlet.ParameterSetName -eq "Set8") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Deployit', 'TestMode', 'RequireEVSigners') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set8" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }


    # parameter control for Make_SuppPolicy - Set9

    if ($PSCmdlet.ParameterSetName -eq "Set9") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Fallbacks', 'Levels', 'Deployit', 'PolicyPath', 'SuppPolicyName', 'ScanLocation') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }

    if ($PSCmdlet.ParameterSetName -eq "Set9") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Fallbacks', 'Levels', 'PolicyPath', 'SuppPolicyName', 'ScanLocation') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [string], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }

    if ($PSCmdlet.ParameterSetName -eq "Set9") {
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        foreach ($param in 'Deployit') {
            [Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
            $runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
            $paramDictionary.Add($param, $runtimeParam)
        }
        return $paramDictionary
    }
}
}
}
2

There are 2 best solutions below

5
mklement0 On BEST ANSWER

To complement briantist's helpful answer:

Context:

  • The concept of positional parameters doesn't apply to your use case, as it only applies when arguments (parameter values) are passed unnamed, i.e. not preceded by a target parameter name.

    • As an aside:

      • [switch] parameters shouldn't be declared as positional (even though you can technically get away with it) - switch parameters are expected to be passed as named arguments, with their name alone implying their value ($true), and their absence implying $false.

      • Similarly, binding [switch] parameters via the pipeline is highly unusual and users may not expect it. Switch parameters are usually understood to be a single flag per invocation, not one that may vary based on each pipeline input object.

      • Also, using _ in parameter names is unusual - consider using camel case instead; e.g., consider using $GetBlockRules (-GetBlockRules) instead of $Get_BlockRules (-Get_BlockRules).

  • Tab-completing parameter names always invariably offers you all of the statically declared parameters when completing the first argument, and - after having typed or completed at least one argument and tab-completing a new one after having typed only - - may expand or shrink the set of parameters offered:

    • in the presence of multiple parameter sets, the set of static parameters offered may shrink, if the argument(s) specified so imply parameter set(s) that support only a subset of the static parameters.

    • in the presence of dynamic parameters, they may surface or disappear, depending on the conditions under which they are designed to become available.

    • Note that if you type a prefix of the name of a static parameter (e.g., -fo), it will be offered for completion even if it doesn't belong to the parameter set(s) implied by arguments typed so far.

  • For discoverability and maintainability, it is best to make do with static parameters, as shown in briantist's answer, but it doesn't provide the step-by-step user guidance you're looking for.


Solution:

The only way to ensure that only the desired subset of parameters is shown when you initially type - and tab-complete is to:

  • Declare only that subset as static parameters, putting each parameter in its own parameter set (as you already do).

    • Note, however, that you can never prevent the common parameters (such as -Verbose from showing in the menu display; they show after the command-specific ones (they display is column-based, so read downward).
  • Declare all other parameters as dynamic ones, surfacing them as appropriate based on what initial parameter was bound.

    • In effect, this amounts to a manual re-implementation of the parameter-set logic for those parameters.

A minimal example:

[CmdletBinding(PositionalBinding = $false)]
param(
  # The only parameters to show initially.
  [Parameter(Mandatory, ParameterSetName = "set1")] [switch] $Get_BlockRules,
  [Parameter(Mandatory, ParameterSetName = "set2")] [switch] $Get_DriverBlockRules,
  [Parameter(Mandatory, ParameterSetName = "set3")] [switch] $Make_AllowMSFT_WithBlockRules

  # All other parameters are declared *dynamically*
)

dynamicParam {
  # Create a dynamic-parameters dictionary
  $dict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        
  # Define and add the -Deployit switch, which only belongs to 
  # parameter set "set3"
  $paramName = 'Deployit'
  $dict.Add(
    $paramName,
    [System.Management.Automation.RuntimeDefinedParameter]::new(
      $paramName,
      [switch],
      [System.Management.Automation.ParameterAttribute] @{
        ParameterSetName = 'set3'
      }
    )
  )

  # Define and add the -SkipVersionCheck switch, which belongs to *all*
  # parameter sets.
  $paramName = 'SkipVersionCheck'
  $dict.Add(
    $paramName,
    [System.Management.Automation.RuntimeDefinedParameter]::new(
      $paramName,
      [switch],
      [System.Management.Automation.ParameterAttribute] @{
        ParameterSetName = '__allParameterSets'
      }
    )
  )

  # Return the dictionary
  return $dict
}

process {
  # Print all parameters that were bound.
  $PSBoundParameters
}

Note:

  • The solution requires no Position properties.

  • All static parameters - the only ones to show initially - are declared as Mandatory, with no default parameter-set designation in the [CmdletBinding()] attribute.

    • This prevents all-parameter-sets dynamic parameters such as -SkipVersionCheck from surfacing prematurely, and also prevents argument-less invocation.
  • -SkipVersionCheck is offered once any one of the initial, static parameters has been completed.

  • -Deployit is only offered if the initially completed static parameter (switch) is -Make_AllowMSFT_WithBlockRules, because like the latter it belongs to parameter set set3 only.

8
briantist On

When working with parameter sets, I find it's very helpful to use the Get-Help command on your function or script, because it shows you how PowerShell is interpreting your sets. That way you can compare it with how you want it to work.

So if I take your param block and put it in a function a like this:

function a { 
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false, ParameterSetName = "set1", Position = 0, ValueFromPipeline = $true)][switch]$Get_BlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set2", Position = 0, ValueFromPipeline = $true)][switch]$Get_DriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set3", Position = 0, ValueFromPipeline = $true)][switch]$Make_AllowMSFT_WithBlockRules,  
        [Parameter(Mandatory = $false, ParameterSetName = "set4", Position = 0, ValueFromPipeline = $true)][switch]$Deploy_LatestDriverBlockRules,                                                                                       
        [Parameter(Mandatory = $false, ParameterSetName = "set5", Position = 0, ValueFromPipeline = $true)][switch]$Set_AutoUpdateDriverBlockRules,
        [Parameter(Mandatory = $false, ParameterSetName = "set6", Position = 0, ValueFromPipeline = $true)][switch]$Prep_MSFTOnlyAudit,
        [Parameter(Mandatory = $false, ParameterSetName = "set7", Position = 0, ValueFromPipeline = $true)][switch]$Make_PolicyFromAuditLogs,  
        [Parameter(Mandatory = $false, ParameterSetName = "set8", Position = 0, ValueFromPipeline = $true)][switch]$Make_LightPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set9", Position = 0, ValueFromPipeline = $true)][switch]$Make_SuppPolicy,
        [Parameter(Mandatory = $false, ParameterSetName = "set10", Position = 0, ValueFromPipeline = $true)][switch]$Make_DefaultWindows_WithBlockRules,
       
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$ScanLocation,
        [parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$SuppPolicyName,
        [ValidatePattern('.*\.xml')][parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$PolicyPath,

        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]        
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [switch]$Deployit,

        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [switch]$TestMode,
        
        [Parameter(Mandatory = $false, ParameterSetName = "set3")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]
        [Parameter(Mandatory = $false, ParameterSetName = "set8")]
        [switch]$RequireEVSigners,

        [Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Debugmode,

        [ValidateSet([Levelz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string]$Levels,

        [ValidateSet([Fallbackz])]
        [parameter(Mandatory = $false, ParameterSetName = "set7")]
        [parameter(Mandatory = $false, ParameterSetName = "set9")]
        [string[]]$Fallbacks, 

        [ValidateRange(1024KB, [int64]::MaxValue)]
        [Parameter(Mandatory = $false, ParameterSetName = "set6")]
        [Parameter(Mandatory = $false, ParameterSetName = "set7")]        
        [Int64]$LogSize,

        [Parameter(Mandatory = $false)][switch]$SkipVersionCheck    
    )
}

Then we can Get-Help a, to show us:

NAME
    a
    
SYNTAX
    a [[-Get_BlockRules]] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Get_DriverBlockRules]] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Make_AllowMSFT_WithBlockRules]] [-Deployit] [-TestMode] [-RequireEVSigners] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Deploy_LatestDriverBlockRules]] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Set_AutoUpdateDriverBlockRules]] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Prep_MSFTOnlyAudit]] [-LogSize <long>] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Make_PolicyFromAuditLogs]] [-Deployit] [-TestMode] [-RequireEVSigners] [-Debugmode] [-Levels {}] [-Fallbacks {}] [-LogSize <long>] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Make_LightPolicy]] [-Deployit] [-TestMode] [-RequireEVSigners] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Make_SuppPolicy]] -ScanLocation <string> -SuppPolicyName <string> -PolicyPath <string> [-Deployit] [-Levels {}] [-Fallbacks {}] [-SkipVersionCheck]  [<CommonParameters>]
    
    a [[-Make_DefaultWindows_WithBlockRules]] [-SkipVersionCheck]  [<CommonParameters>]

For me, the first thing that stands out is that most of the parameters sets, I think all except set9, have no mandatory parameters. That means that calling this with no parameters set at all is valid, which may not be what you want.

I would also suggest giving your parameter sets meaningful names. They don't show up in the regular help output, but it can help you organize them.

The reason I mention all this is that it's probably affecting the thing you're really interested in, which is how things show up in the PSReadLine menu. I haven't used that menu option before, but the parameters you underlined in yellow are perfectly valid given the parameter sets you defined.

Position as set on a parameter is only used so that callers can supply the value without the name. But PowerShell otherwise doesn't care in what order the values were supplied.

What I think you want to do, is better define your parameter sets to include mandatory parameters, as sort of "anchor" parameters for the set, and ensure that parameters that cannot be used with the mandatory one(s) are not included in that set.

Since I don't know which combinations you have in mind, I can't really show an example with your existing params.

One trick I use often is to make [Switch] parameters mandatory in a given parameter set.

Usually, it doesn't make sense to make a [Switch] mandatory because that (usually) means it will always be $true, but when used with parameter sets, it can make a lot of sense as a way of "selecting" a particular set.

Here's a contrived example:

function z {
    [CmdletBinding(DefaultParameterSetName='normal')]
    param(
        # all parameter sets
        [Parameter(Mandatory)]
        [String]
        $Thing,

        [Parameter(Mandatory, ParameterSetName='mode2')]
        [Switch]
        $Mode2,

        # mode2 only, mandatory
        [Parameter(Mandatory, ParameterSetName='mode2')]
        [String]
        $Mode2FooBar,

        # mode2 only optional
        [Parameter(ParameterSetName='mode2')]
        [String]
        $Mode2ExtraData
    )
}

Now with Get-Help z:


NAME
    z
    
SYNTAX
    z -Thing <string>  [<CommonParameters>]
    
    z -Thing <string> -Mode2 -Mode2FooBar <string> [-Mode2ExtraData <string>]  [<CommonParameters>]

Hopefully this gives you some ideas!