Repository navigation
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)
- Support for parameter set selection by parameter value. #4102 (see my comment there as well).
Activity
jhoneill commentedon Feb 6, 2022
A switch is NOT A BOOLEAN
A switch is an instruction to do something non-default. Writing
-A:$trueis redundantThe absence of a switch says follow the default path.
-A:$falsedoes 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:$falsehas the same effect as writing nothing which is what almost everyone does almost all the time. But consider aSetcommand which can disable something instead of removing it.Set-Thing -disabledwould disable something .Set-Thingwith no switch doesn't do anything to the stateSet-Thing -disabled:$falsewould change the state from disabled back to enabled.The author might add a switch
-ReEnablebecause it is accepted that new users take time to discover:$falseand Parameter sets are there to prevent mutually exclusive parameters being specified likeSet-Thing -Disabled -ReEnablesome bad code exists where someone writes something like thiswhen they should have a type field with a
validateSetand 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-AsTablemight 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.
ohadschn commentedon Feb 6, 2022
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=$falseis the same as not specifying-Switchit 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 callingFoois completely equivalent toFoo -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
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
And here: https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/types-of-cmdlet-parameters?view=powershell-7.2
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
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.
jhoneill commentedon Feb 7, 2022
I will just point to the entire history of PowerShell :-)
You quoted this
Under normal circumstances a user should write
-Switchto covey true and nothing implies false.There are exceptions in scripts / functions.
some-commnd @paramsand the parameters need to include a switch, the hash table needs sets=true. It is not good practice to puts = $falsein 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.-s:$sThe 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
-erroractionetc,This ignores the case where the parameter is specified with
:$falsebut 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:
or if $s defines "Do Not Do something you would do by default"
So for example in
write-hostit would look likeIf ( -not $NoNewLine) { write a new line }; In both cases although the user should not write-s:$falseit has the same effect as writing nothing. Obviously requesting the default behaviour with a-NoNewLine:$falseadds an confusing double negative when the code is read.Some authors who want to make everything strongly typed do not like treating
$sas a boolean and the either use$s.ispresentor$psboundparameters.containsKey('s')To show the effect of these:The last case shows (a) how one would detect
:$falseand (b) if the author is using dynamic parameters where$Paramisn't created and it is necessary to look at$PSBoundParametersif they only check for presence of a switch specifying$falsewill 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:$trueis always the same as-Switch--Switchis the preferred form-Switch:$Falseis Usually the same as writing nothing and omitting the switch entirely is the preferred form.-Switch:$Falseis 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 -underlineFor "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:$falseFor "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
-Disableswitch 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.
ohadschn commentedon Feb 8, 2022
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 :)
iSazonov commentedon Feb 8, 2022
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-/sswitch 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.
ohadschn commentedon Feb 8, 2022
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)?jhoneill commentedon Feb 8, 2022
Should it ? Most the time it does. Must it do so ? I gave this example before
Sometimes specifying
Foo -Switch:$falseintentionally does something different from not specifying-switchAs 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:$falsebecause "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
But all of this shows it can be used, not as a switch. It also says that the
ispresentpropertyagain that's not the case if
-Switch:$falseis specified.switchis to be a parameter which takes no value and is either present or absent.-Switch:$Variablesyntax which is sometimes needed in code and the use of-Switch:$falsefor undo.:$trueand:$falseis never encouraged, and is at best considered "mostly-harmless"-Switch:$falsebeing 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:$falsedoes something unexpected is "Don't do that then".[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.DynamicParam{}but most developers avoid using it and that proposal avoids the need forDynamicParamand simplifies the use of parameter sets.iSazonov commentedon Feb 8, 2022
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.
mklement0 commentedon Feb 15, 2025
I just created the following proposal / bug report (before I was made aware of this issue):
-Switch:$falsethe same as omitting a switch when selecting a parameter set #25027In short:
Treating
-Switch:$falsethe same as omitting-Switchin terms of parameter-set selection strikes me as the sensible thing to do.The counterintuitive current behavior results in several (possibly most) cmdlets currently misbehaving with
-Switch:$false(effectively ignoring the$falsevalue); see @MartinGC94's analysis.