Skip to content

Reconcile -Switch:$false parameter invocation with parameter set resolution #16852

Closed

Description

Summary of the new feature / enhancement

Consider the following pattern, common among PowerShell libraries, including official Microsoft ones such as Azure-PowerShell:

function Foo {
    [CmdletBinding()]
    param(
        [Parameter(ParametersetName="First")]
        [switch] $A,
        [Parameter(ParametersetName="Second")]
        [switch] $B,
        [Parameter(ParametersetName="Third")]
        [switch] $C
    )

    switch ($PSCmdlet.ParameterSetName) {
        "First" { Write-Host "First set selected" }
        "Second" { Write-Host "Second set selected" }
        "Third" { Write-Host "Third set selected" }
    }
}

Straightforward executions work well:
Foo -A -> First set selected
Foo -B -> Second set selected
Foo -C -> Third set selected

However, any attempt to explicitly set switch parameter values using a boolean value (which is invaluable in scripting) will fail:
Foo -A:$true -b:$false -> 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.

Of course, this breaks the Switch parameter contract when specified as above: Azure/azure-powershell#12881

Proposed technical implementation details (optional)

There seem to be many suggestions for improving the parameter set experience (#11237, #13746, #10137, #12818 etc.) however many of them have far reaching, backward compatibility implications.

There is one suggestion in particular I think could be very helpful, namely an additional attribute value to help PowerShell's resolution mechanism choose the right parameter set by considering the parameter's value, not just its presence (in this case $false would not match the selector)

Activity

  1. jhoneill commented on Feb 6, 2022

    @jhoneill

    A switch is NOT A BOOLEAN

    A switch is an instruction to do something non-default. Writing -A:$true is redundant
    The absence of a switch says follow the default path.
    -A:$false does not say follow the default path it says UNDO a previous change from the default and bring it back to the default. Often the Undo path doesn't exist for example -NoClobber:$false has the same effect as writing nothing which is what almost everyone does almost all the time. But consider a Set command which can disable something instead of removing it.

    • Set-Thing -disabled would disable something .
    • Set-Thing with no switch doesn't do anything to the state
    • Set-Thing -disabled:$false would change the state from disabled back to enabled.

    The author might add a switch -ReEnable because it is accepted that new users take time to discover :$false and Parameter sets are there to prevent mutually exclusive parameters being specified like Set-Thing -Disabled -ReEnable some bad code exists where someone writes something like this

    function Export-Picture {
    param (
    [switch]$AsJpg
    [switch]$AsPNG
    [switch]$AsTIF
    

    when they should have a type field with a validateSet and a default value.

    One case where non default options are mutually exclusive is exporting to excel. Data can be a range, or it can be a table. By default it's neither, but it cannot be both so -AsRange -AsTable might be in different sets.

    Where someone is determined to write their switches as UNDO to mean Do Not do it is better to educate them than to redesign the behaviour which authors already rely on.

  2. ohadschn commented on Feb 6, 2022

    @ohadschn
    Author

    James O'Neill (@jhoneill) Could you please provide a reference to support your view on switch parameters?

    The docs seems to support my view above, namely that -Switch=$false is the same as not specifying -Switch it at all . More specifically, they equate boolean parameters with switch parameters semantically, merely pointing out the convenience advantage of the latter.

    To the extent that we both agree boolean parameters don't mean what you detailed above, the official docs would seem to contradict your view. I assume we both agree that when I write function Foo ([bool)$B= $false]), then calling Foo is completely equivalent to Foo -B $false.

    https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines?view=powershell-7.2#parameters-that-take-true-and-false

    Parameters That Take True and False
    If your parameter takes only true and false, define the parameter as type System.Management.Automation.SwitchParameter. A switch parameter is treated as true when it is specified in a command. If the parameter is not included in a command, Windows PowerShell considers the value of the parameter to be false. Do not define Boolean parameters.

    A similar explanation is provided here: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-7.2#switch-parameters

    Switch parameters are parameters that take no parameter value. Instead, they convey a Boolean true-or-false value through their presence or absence, so that when a switch parameter is present it has a true value and when absent it has a false value.

    And here: https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/types-of-cmdlet-parameters?view=powershell-7.2

    Windows PowerShell provides a System.Management.Automation.SwitchParameter type that allows you to define a parameter whose value is automatically set to false if the parameter is not specified when the cmdlet is called. Whenever possible, use switch parameters in place of Boolean parameters.

    Moreover, in the official System Center blog I found this stated explicitly:
    https://techcommunity.microsoft.com/t5/system-center-blog/oit-quick-tip-using-powershell-switch-parameters-in-cli/ba-p/345469

    Here’s an example:
    get-scoevents
    get-scoevents –formatXML
    The equivalent of the example lines above looks like this:
    get-scoevents –formatXML:$false
    get-scoevents –formatXML:$true

    Note that in the Azure-PowerShell bug I linked to, no one rejected the fact that it was a bug. Rather they pointed out the implementation difficulty, which was my motivation for opening this issue.

    Strongly Encouraged Development Guidelines
    Explains how to add parameters to advanced functions.
    Types of Cmdlet Parameters
    TECHCOMMUNITY.MICROSOFT.COM
    First published on TECHNET on May 04, 2012 One of the common parameter types in PowerShell is the switch parameter. It’s basically a Boolean (true/false) type of parameter, and you either add it to the command line or not. Here’s an example: get-scoevents get-scoevents –formatXML The above comman...
  3. jhoneill commented on Feb 7, 2022

    @jhoneill

    I will just point to the entire history of PowerShell :-)

    You quoted this

    Switch parameters are parameters that take no parameter value.

    Under normal circumstances a user should write -Switch to covey true and nothing implies false.

    There are exceptions in scripts / functions.

    1. Splatting. If I call some-commnd @params and the parameters need to include a switch, the hash table needs set s=true . It is not good practice to put s = $false in the hash table but it is mostly harmless . It's often easier to write code which always sets the value and generally doesn't cause a problem.
    2. -S is a switch parameter in function 1 which calls function 2 and wants to pass the value it was passed to function 2 and specifies -s:$s

    The guide to developers is clear. Booleans create a bad user interface, don't use them, There are still Microsoft supplied cmdlets where the team which implemented them did not follow the developers guide, or look at existing practice, or ask the PowerShell team for advice. Microsoft is not one homogenous hive mind - trust me I worked there for 10 years - and parts of it say or do things which contradict others. The graph API team don't observe any of the normal rules, the Azure AD team and even ordinary AD cmdlets break the rules on Booleans, accepting -erroraction etc,

    A switch parameter is treated as true when it is specified in a command. If the parameter is not included in a command, Windows PowerShell considers the value of the parameter to be false. Do not define Boolean parameters.

    This ignores the case where the parameter is specified with :$false but is true in normal use I.e. when the param block declares
    [Switch]$S
    $S evaluates to boolean true if switch is specified and and boolean false if not. The default state is the user doesn't specify the switch so the code inside the function looks like:

    If ($s)  {   non default  action with a name like  'S' } 
    else     {  default action , if any }
    

    or if $s defines "Do Not Do something you would do by default"

    If ( -not $s)   {  default action }
    

    So for example in write-host it would look like If ( -not $NoNewLine) { write a new line } ; In both cases although the user should not write -s:$false it has the same effect as writing nothing. Obviously requesting the default behaviour with a -NoNewLine:$false adds an confusing double negative when the code is read.

    Some authors who want to make everything strongly typed do not like treating $s as a boolean and the either use $s.ispresent or $psboundparameters.containsKey('s') To show the effect of these:

    function f {
    param ([Switch]$s)
    
    "S acts as $([boolean]$s)"
    if ($PSBoundParameters.ContainsKey('S')) {"S in bound parameters"}
    "IsPresent set to $($s.IsPresent)" 
    }
    
    
    ps> f
    S acts as False
    IsPresent set to False
    
    ps> f -s
    S acts as True
    S in bound parameters
    IsPresent set to True
    
    ps> f -s:$false
    S acts as False
    S in bound parameters
    IsPresent set to False
    

    The last case shows (a) how one would detect :$false and (b) if the author is using dynamic parameters where $Param isn't created and it is necessary to look at $PSBoundParameters if they only check for presence of a switch specifying $false will have the same effect as specifying true!

    What SystemCenter blog says is true for the command it is talking about and for most other commands.

    • -Switch:$true is always the same as -Switch - -Switch is the preferred form
    • -Switch:$False is Usually the same as writing nothing and omitting the switch entirely is the preferred form.
    • In the few cases where -Switch:$False is not the same as writing nothing either the author has created a bug as above, or it means something else.

    I used exporting to excel as an example and it might have
    Format-Cell -Size 14 -bold -underline
    For "change the font to 14 point, make it bold and underlined but do not change italic" compared with
    Format-Cell -Size 14 -bold -underline -italic:$false
    For "change the font to 14 point, make it bold and underlined and remove italic if it was set previously."

    "Don't add" and "Remove" are not the same. The same with the -Disable switch in the previous comment. "Don't disable" is not the same as "Enable"

    As I said before if the user writes something which they should not, and which is redundant, AND they happen to do with parameters which are declared mutually exclusive, user education is the solution to the problem. The resource required to add this functionality would never be justified, and even if it were added what you proposed on the other issue would require commands to be changed, and would require authors to think "Oh, I've put that in an exclusive set but I need to add something in case the user writes in a way which they should not write it." It would be lovely to think that would happen.

  4. ohadschn commented on Feb 8, 2022

    @ohadschn
    Author

    Thank you, I understand your argument, what I'm missing is any reference to the fact that this is indeed the expected semantic. I concede that different groups within MS could be out of sync, but as far as I can tell you haven't supplied an actual example to support your case in terms of existing libraries, let alone any piece of documentation (which we both agree would hold more weight than any specific implementation).

    In my humble opinion, my previous post demonstrated that on both fronts my view is more likely, but let's wait for someone from the PowerShell team to weigh in... I'd be happy to be proven wrong, and hopefully we can both agree on that as the official word :)

  5. iSazonov commented on Feb 8, 2022

    @iSazonov
    Collaborator

    I don't know what else to add. As you yourself pointed out, the choice of the parameter set is based on the presence of the parameter, but not on its value.
    PowerShell switch semantic is not something new too. For example in cmd.exe, dir /s - /s switch turns on recursion if present and turns off recursion if absent.
    The fact that PowerShell switch can be converted to Boolean is an implementation detail to make it using more convenient.

  6. ohadschn commented on Feb 8, 2022

    @ohadschn
    Author

    Ilya (@iSazonov)

    Going by the docs, I don't see the conversion to boolean as strictly an implementation detail, more like a design meant to abolish Boolean parameters. The basically say - don't use boolean params, use switch.

    But we digress. The question here is not how switch parameters or parameter set resolution work technically.
    Rather the question is about PowerShell standard / contract / convention:
    Should not specifying a switch to a CmdLet (Foo) behave exactly the same as specifying it as false (Foo -Switch:$false)?

  7. jhoneill commented on Feb 8, 2022

    @jhoneill

    Ilya (@iSazonov) the question here is not how switch parameters or parameter set resolution work technically. Rather the question is about PowerShell standard / contract / convention: Should not specifying a switch to a CmdLet (Foo) behave exactly the same as specifying it as false (Foo -Switch:$false)?

    Should it ? Most the time it does. Must it do so ? I gave this example before

    Format-Cell -Size 14 -bold -underline is "do not change italic" compared with
    Format-Cell -Size 14 -bold -underline -italic:$false is "remove italic if it was set previously."
    "Don't add" and "Remove" are not the same. The same with the -Disable switch in the previous comment. "Don't disable" is not the same as "Enable"

    Sometimes specifying Foo -Switch:$false intentionally does something different from not specifying -switch
    As such switch is like a nullable Boolean. Usually we only care if it is true or not true, but sometimes (-not $b) is not the same as ($b -eq $false).

    You have demanded documentation. :-) I'm pretty sure no PowerShell documentation recommends writing -switch:$false , (outside of the help for commands where it does something different). I have been criticized for using -Italic:$false because "Nobody knows you can write that".
    It's not widely understood, even in documentation for the switch class at
    https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.switchparameter?view=powershellsdk-7.0.0
    it says the type is

    used to define a parameter on a cmdlet script of function that can only be used as a switch

    But all of this shows it can be used, not as a switch. It also says that the ispresent property

    Returns true if the parameter was specified on the command line, false otherwise.

    again that's not the case if -Switch:$false is specified.

    • The purpose of switch is to be a parameter which takes no value and is either present or absent.
    • Most documents ignore the -Switch:$Variable syntax which is sometimes needed in code and the use of -Switch:$false for undo.
    • Use of :$true and :$false is never encouraged, and is at best considered "mostly-harmless"
    • In PowerShell code, a switch parameter can be treated as a Boolean which, when true, adds a non-default action and/or removes a default one. Code rarely looks for -Switch:$false being passed and occasionally mistakes it for -Switch. The OP gave "Connect-AzAccount -Identity:$false" tries to connect to managed identity (bare "Connect-AzAccount" does not) Azure/azure-powershell#12881 as an example of that. As that showed, the advice to a user when specifying -Switch:$false does something unexpected is "Don't do that then".
    • Allowing a parameter to be in one parameter set when its value meets a test, and in all parameter sets otherwise requires both a change to PowerShell AND a change to the code where the parameter is used - assuming the author thought to do that and was happy their code would only run a new release; it is not certain that the AZ cmdlets in question would be updated
    • There is a proposal Add support for dynamic parameter declaration in param() #16173 for a [dynamic] ("appear if") label for parameters which would support "only allow switch X if neither switch Y nor switch Z evaluates to true". This still requires a PowerShell update and still results in code which requires that version.
    • The problem as described can be addressed today with a DynamicParam{} but most developers avoid using it and that proposal avoids the need for DynamicParam and simplifies the use of parameter sets.
    Type used to define a parameter on a cmdlet script of function that can only be used as a switch.
  8. iSazonov commented on Feb 8, 2022

    @iSazonov
    Collaborator

    Going by the docs,

    Ohad Schneider (@ohadschn) PowerShell documentation was never at the specification level. In the last couple of years PowerShell team has made a lot of improvements, but it's still not specs like it is for Windows APIs and features. You can open new issue-request in PowerShell Doc repo to improve the topic.

  9. ghost closed this as completedon Feb 10, 2022
  10. mklement0 commented on Feb 15, 2025

    @mklement0
    Contributor

    I just created the following proposal / bug report (before I was made aware of this issue):

    In short:

    • Treating -Switch:$false the same as omitting -Switch in terms of parameter-set selection strikes me as the sensible thing to do.

      • Doing so does not preclude implementing (rarely needed) custom logic that treats the two forms differently.
    • The counterintuitive current behavior results in several (possibly most) cmdlets currently misbehaving with
      -Switch:$false (effectively ignoring the $false value); see @MartinGC94's analysis.

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-Enhancementthe issue is more of a feature request than a bugIssue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aNeeds-TriageThe issue is new and needs to be triaged by a work group.Resolution-AnsweredThe question is answered.WG-Engine-ParameterBinder

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions