5

I've got a PowerShell function that takes a string, and three switches. So the invocation would be something like this:

My-Function "some string" -Switch1 -Switch2 -Switch3

However, I want to make this function only run if the string and at least one switch is provided. So:

My-Function "some string" -Switch1 -Switch3  # Valid
My-Function "some string" -Switch2           # Valid
My-Function "some string"                    # Invalid

I know I could this by checking to see if the switches have been passed using the $MyInvocation object, but is there a way to do this using the Parameter and ParameterSet attributes?

In short, this is what I'm trying to do:

  • The string must be provided every time the function is called.
  • At least one switch must be provided every time the function is called.
Jake
  • 1,701
  • 3
  • 23
  • 44

3 Answers3

4

I don't think you can do this with parameter sets and mandatory parameters. If you defined 3 different parameter sets with alternating mandatory parameter like this:

Param(
  [Parameter(Mandatory=$true)]
  [string]$Text,

  [Parameter(Mandatory=$true, ParameterSetName='o1')]
  [Parameter(Mandatory=$false, ParameterSetName='o2')]
  [Parameter(Mandatory=$false, ParameterSetName='o3')]
  [Switch][bool]$Switch1,

  [Parameter(Mandatory=$false, ParameterSetName='o1')]
  [Parameter(Mandatory=$true, ParameterSetName='o2')]
  [Parameter(Mandatory=$false, ParameterSetName='o3')]
  [Switch][bool]$Switch2,

  [Parameter(Mandatory=$false, ParameterSetName='o1')]
  [Parameter(Mandatory=$false, ParameterSetName='o2')]
  [Parameter(Mandatory=$true, ParameterSetName='o3')]
  [Switch][bool]$Switch3
)

it works if you use just one of the switches:

My-Function "foo" -Switch2

but fails if you use more than one switch:

PS C:\> .\test.ps1 "foo" -Switch1 -Switch2
C:\test.ps1 : Parameter set cannot be resolved using the specified named
parameters.
At line:1 char:1
+ .\test.ps1 "foo" -Switch1 -Switch2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [test.ps1], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameterSet,test.ps1

I'd use a set-validated mandatory parameter instead:

Param(
  [Parameter(Mandatory=$true)]
  [string]$Text,

  [Parameter(Mandatory=$true)]
  [ValidateSet('Switch1', 'Switch2', 'Switch3', ignorecase=$true)]
  [string[]]$Options
)

That would allow you to call the function like this:

My-Function "foo" -Options Switch1
My-Function "foo" -Options Switch2,Switch3

Or you could make all three switches optional and validate them inside the function:

Param(
  [Parameter(Mandatory=$true)]
  [string]$Text,

  [Parameter(Mandatory=$false)]
  [Switch][bool]$Switch1,

  [Parameter(Mandatory=$false)]
  [Switch][bool]$Switch2,

  [Parameter(Mandatory=$false)]
  [Switch][bool]$Switch3
)

if (-not ($Switch1.IsPresent -or $Switch2.IsPresent -or $Switch3.IsPresent)) {
  throw 'Missing switch'
}

Dynamic parameters might be another option, but I can't say that for certain.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • I didn't think of using ValidateSet. That actually might work better for this function. Thanks for that. – Jake Nov 30 '15 at 15:18
  • It actually is useful to use ParameterSets - see my [answer](https://stackoverflow.com/a/63325515/2770331) – T S Aug 09 '20 at 11:08
2

It absolutely is possible to require, that at least one out of n parameters is specified by using ParameterSets:

function My-Function() {
    Param(
        [Parameter(Mandatory, Position=0)]
        [string]$Text,

        [Parameter(Mandatory, ParameterSetName='o1')]
        #not member of the following ParameterSets
        [Switch][bool]$Switch1,

        [Parameter(ParameterSetName='o1')] #non-mandatory member of previous ParameterSets
        [Parameter(Mandatory, ParameterSetName='o2')]
        #not member of the following ParameterSets
        [Switch][bool]$Switch2,

        [Parameter(ParameterSetName='o1')] #non-mandatory member of previous ParameterSets
        [Parameter(ParameterSetName='o2')] #non-mandatory member of previous ParameterSets
        [Parameter(Mandatory, ParameterSetName='o3')]
        [Switch][bool]$Switch3
    )
    $PSBoundParameters
    echo "`n"
}

#tests
echo "These should work:"
My-Function "some string" -Switch1 -Switch2 -Switch3
My-Function "some string" -Switch1 -Switch2
My-Function "some string" -Switch1          -Switch3
My-Function "some string" -Switch1
My-Function "some string"          -Switch2 -Switch3
My-Function "some string"          -Switch2 
My-Function "some string"                   -Switch3
echo "This should not work:"
My-Function "some string" 

echo "However, this does work:"
My-Function "some string" -Switch1:$false

However, the parameter sets only enforce that one of the switches is used when calling the command.

It is not enforced, that one of the switches is actually set to true. If that is what you need, then do the actual test in the function, as recommended in Ansgar Wiechers' answer (slightly modified):

if (-not ($Switch1.IsPresent -or $Switch2.IsPresent -or $Switch3.IsPresent)) {
    #throw 'Missing switch' #not good, error message does not show where My-Function was called...
    #Write-Error 'Missing switch' -ErrorAction Stop # better, but still stops the whole script, not just the function
    $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new(
        [System.Management.Automation.ParameterBindingException]'At least one of Switch1, Switch2, Switch3 must be true.',
        'MissingRequiredSwitch',
        [System.Management.Automation.ErrorCategory]::InvalidArgument,
        $null
    )) # statement-terminating error, same as generated when ParameterSet can not be resolved... 
}

Regarding statement-terminating errors see Difference between throw and $pscmdlet.ThrowTerminatingerror()?

However, I would still recommend to use parameter sets (in addition to the explicit test). Because using ParameterSets as described above makes PSReadLine show in the autocompletion information, that at least one of the parameters is required. Example output, when using ctrl+space:

enter image description here

T S
  • 1,656
  • 18
  • 26
-1

Yes, you make the parameter mandatory when defining like so:

 Param(
   [Parameter(Mandatory=$true)]
   [string]$myParameter
) 
AutomatedOrder
  • 501
  • 4
  • 14
  • 1
    The OP wants to enforce that at least one of the switches `-Switch1`, `-Switch2`, and `-Switch3` is present. Making a parameter mandatory enforces the presence of this exact switch every time the function is called. – Ansgar Wiechers Nov 30 '15 at 14:24
  • 1
    Further to this, only the string is mandatory each time. Updating OP to reflect this. – Jake Nov 30 '15 at 14:31