lit
lit

Reputation: 16226

Mutually exclusive switch parameters

The function has one required parameter, -Path, and two other mutually exclusive switches. This is not the real function, but a MRE (Minimal Reproducable Example). The default operation is to copy the file to a known location and then remove it.

Do-TheFile [-Path] <String[]> [[-Copy] | [-Remove]]
    -Path = filename is mandatory
    -CopyOnly = only copy the file, cannot be used with -Remove
    -RemoveOnly = only remove the file, cannot be used with -Copy

This is the current code.

param (
    [Parameter(Mandatory=$true, Position=0)]
    [string[]]$Path

    ,[Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
    [switch]$CopyOnly

    ,[Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
    [switch]$RemoveOnly
)

The console allows me to specify both -CopyOnly and -RemoveOnly. My expectation was that the console would not permit me to enter both -CopyOnly and -RemoveOnly because they are in different ParameterSets. How can I specify these ParameterSets so that -Copy and -Remove are mutually exclusive?

PS C:\src\t> Do-TheFile -Path t.txt -CopyOnly -RemoveOnly
Do-TheFile: 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.

Upvotes: 4

Views: 2174

Answers (3)

CFou
CFou

Reputation: 1180

@Postanote's answer is great and I will prefer it.

However, as @kfsone underlined, DefaultParameterSetName can achieve this with your two switches if you add a ParameterSetName for $Path only and set it as default :

[CmdletBinding(DefaultParameterSetName='CopyAndRemove')]
param (
   [Parameter(Mandatory=$true, Position=0)]
   [Parameter(ParameterSetName='CopyAndRemove')]
   [Parameter(ParameterSetName='CopyOnly')]
   [Parameter(ParameterSetName='RemoveOnly')]
   [string[]]$Path,

   [Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
   [switch]$CopyOnly,

   [Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
   [switch]$RemoveOnly
)
$Path
$PSCmdlet.ParameterSetName

Upvotes: 1

kfsone
kfsone

Reputation: 24249

In contemporary versions of PowerShell, ParameterSets are mutually exclusive.

    function greet {
        Param(
          [String]
          $Name = "World",
          [Parameter(ParameterSetName="intro")]
          [Switch]
          $Hello,
          [Parameter(ParameterSetName="outro")]
          [Switch]
          $Farewell
        )

        if ($Hello) {
          echo "Hello, $Name!"
        } elseif ($Farewell) {
          echo "Farewell, $Name!"
        } else {
          echo "What's up, $Name"
        }
    }

This results in the split-groupings that you often see in MS cmdlets:

> greet -?

NAME
    greet

SYNTAX
    greet [-Name <string>] [-Hello] [<CommonParameters>]

    greet [-Name <string>] [-Farewell] [<CommonParameters>]

Doing this requires that the user or the script identify which ParameterSet should be used.

greet -Name "Bob"
> greet -Name "Bob"
greet: 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.

This is trying to tell the user they weren't specific enough. See DefaultParameterSetName for how to set it from the script:

There is a limit of 32 parameter sets. When multiple parameter sets are defined, the DefaultParameterSetName keyword of the CmdletBinding attribute specifies the default parameter set. PowerShell uses the default parameter set when it can't determine the parameter set to use based on the information provided to the command.

Upvotes: 2

postanote
postanote

Reputation: 16076

Agree with the others here.

Your code works, as written when using IntelliSense, but PowerShell will not stop you from typing in other valid switches/variables/property names (in either the consolehost, ISE, VSCode, Visual Studio, etc...), that does not mean it would work just because you typed both.

Why make two switches, when you only want to use one option at a time, no matter what. Just use a simple validation set.

Function Test-MyFunctionTest
{
    [cmdletbinding()]
    param 
    (
        [Parameter(Mandatory = $true,  Position = 0)]
        [string[]]$Path,
        [Parameter(Mandatory)][ValidateSet('CopyOnly', 'RemoveOnly')]
        [string]$FileAction
    )

}

# Results
<#
Test-MyFunctionTest -Path $PWD -FileAction CopyOnly
Test-MyFunctionTest -Path $PWD -FileAction RemoveOnly
#>

Otherwise, as you have discovered, you have to code this up yourself. For example:

Function Test-MyFunctionTestAgain
{
    [cmdletbinding()]
    param 
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string[]]$Path,
        [switch]$RemoveOnly
    )

    If($RemoveOnly.IsPresent)
    {'Do the remove action'}
    Else {'Do the copy action'}
}

Test-MyFunctionTestAgain -Path $PWD
# Results
<#
Do the copy action
#>

Test-MyFunctionTestAgain -Path $PWD -RemoveOnly
# Results
<#
Do the remove action
#>

Update

As for this...

"I agree that this could work. Although, the default operation (using no switches) is to both Copy and Remove."

... then this...

Function Test-MyFunctionTestMore
{
    [cmdletbinding()]
    param 
    (
        [Parameter(Mandatory = $true,  Position = 0)]
        [string[]]$Path,
        [Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
        [string]$FileAction = 'CopyAndRemove'
    )

    Switch ($FileAction)
    {
        CopyAndRemove {'Do the copy and remove action'}
        CopyOnly      {'Do the copy only action'}
        RemoveOnly    {'Do the remove only action'}
    }
}


Test-MyFunctionTestMore -Path $PWD
# Results
<#
Do the copy and remove action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction RemoveOnly
# Results
<#
Do the remove only action
#>

Or this way, if you are really yearning just to have a switch ;-} ...

Function Test-MyFunctionTestSwitch
{
    [cmdletbinding()]
    param 
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string[]]$Path,
        [Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
        [string]$FileAction = 'CopyAndRemove',
        [switch]$RemoveOnly
    )

    If($RemoveOnly.IsPresent)
    {
        $FileAction = 'RemoveOnly'
        'Do the remove only action'
    }
    ElseIf ($FileAction -eq 'CopyOnly')
    {'Do the copy only action'}
    Else{'Do the copy and remove action'}
}

Test-MyFunctionTestSwitch -Path $PWD
# Results
<#
Do the copy and remove action
#>

Test-MyFunctionTestSwitch -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>

Test-MyFunctionTestSwitch -Path $PWD -RemoveOnly
# Results
<#
Do the remove only action
#>

Lastly as a point of note:

Trying to emulate some other tools actions, or expecting PowerShell to natively emulate some other tools actions, params, etc., really should not be an expectation.

If you believe PowerShell should have a specific feature, then the option is to submit it to the PowerShell team, to have it upvoted by others for work/inclusion or since PowerShell is open-sourced, you can tool it up and submit it for review/approval of commit.

Upvotes: 2

Related Questions