Let's take a look at the following function:
function testFunction(){
[CmdletBinding(DefaultParameterSetName="set1")]
Param(
[Parameter(ParameterSetName="set1")]
[switch]$switch1,
[Parameter(ParameterSetName="set2")]
[Parameter(ParameterSetName="set3")]
[switch]$switch2
)
Write-Host "Function executed."
}
When it's executed without switches or passing -switch1, everything works as expected but when we run
testFunction -switch2
we get the AmbiguousParameterSet error for the following two reasons:
set1;$switch2belongs to set2and set3, so PowerShell obviously doesn't know which set to choose.Anyways, since there are no mandatory parameters in set2 and set3, there would be no difference in choosing set2 rather than set3. PowerShell should be smart enough not to throw the error and to just go on executing the code. In other words, in the code above $switch2 is the intersection of set2 and set3 and it's not ambiguous since there are no mandatory parameters in set2 or set3: ambiguities should only occur when having no other choice than adopting a specific parameter set in presence of mandatory parameters.
@egon984
If testFunction -switch2 succeeded, what would $PSCmdlet.ParameterSetName be set to?
It seems like another disambiguation hint would be needed. I've run into this sort of thing a few times. I wonder whether being able to provide a ParameterSetPriority array rather than DefaultParameterSet would be an improvement. Your function would be
function testFunction(){
[CmdletBinding(ParameterSetPriority="set1","set2")]
Param(
[Parameter(ParameterSetName="set1")]
[switch]$switch1,
[Parameter(ParameterSetName="set2")]
[Parameter(ParameterSetName="set3")]
[switch]$switch2
)
}
which would be interpreted as "default to 'set1' and if you can't decide between 'set2' and 'set3', choose 'set2'.
@alx9r
Any improvement in that direction will be welcome beacuse IT'S necessary. Your idea is very good, but what keeps PowerShell from picking a random set by itself when any of them is ok?
I was just going to create the same enhancement request and found this already exists, as @alx9r states it will be good if you can provide an array to DefaultParameterSetName or something like "ParameterSetPriority"
PowerShell will then evaluate them in the order provided until a suitable one is found, and only if none of them suitable throw the error.
Example:
function Test-Params
{
[CmdletBinding(DefaultParameterSetName = 'Default1', 'Default2')]
Param(
[Parameter(ParameterSetName = "Default1")]
[Switch]$Param1,
[Parameter(ParameterSetName = "Default2")]
[Parameter(ParameterSetName = "Default2-Options")]
[Switch]$Param2,
[Parameter(ParameterSetName = "Default2-Options")]
[Switch]$Option1,
[Parameter(ParameterSetName = "Default2-Options")]
[Switch]$Option2,
[Parameter(ParameterSetName = "Default2-Options")]
[Switch]$Option3
)
"ParameterSet: " + $PSCmdlet.ParameterSetName
}
Test-Params -Param2
And yes this can be workaround by doing this:
function Test-Params
{
[CmdletBinding(DefaultParameterSetName = 'Default1')]
Param(
[Parameter(ParameterSetName = "Default1")]
[Switch]$Param1,
[Parameter(ParameterSetName = "Default2")]
[Parameter(ParameterSetName = "Default2-Options1")]
[Parameter(ParameterSetName = "Default2-Options2")]
[Parameter(ParameterSetName = "Default2-Options3")]
[Switch]$Param2,
[Parameter(ParameterSetName = "Default2-Options1", Mandatory)]
[Parameter(ParameterSetName = "Default2-Options2")]
[Parameter(ParameterSetName = "Default2-Options3")]
[Switch]$Option1,
[Parameter(ParameterSetName = "Default2-Options1")]
[Parameter(ParameterSetName = "Default2-Options2", Mandatory)]
[Parameter(ParameterSetName = "Default2-Options3")]
[Switch]$Option2,
[Parameter(ParameterSetName = "Default2-Options1")]
[Parameter(ParameterSetName = "Default2-Options2")]
[Parameter(ParameterSetName = "Default2-Options3", Mandatory)]
[Switch]$Option3
)
"ParameterSet: " + $PSCmdlet.ParameterSetName
}
Test-Params -Param2
But the number of ParameterSets requierd to workaround this can get out of hand very quickly.
Can you consider implement this please?
This will also help avoid this bug https://github.com/PowerShell/PowerShell/issues/8516
Thank you
If it's not too much to ask, can someone who wants this issue addressed please provide a real, not contrived example where this is a problem? In all of the examples provided above, I don't see a problem at all other than the design of the parameter sets themselves, and as a result I don't see the same problem you are trying to indicate is there. It's hard to get momentum for a fix when the problem itself is not clearly identified.
The last time I encountered a situation that this could have helped is in ImportExcel https://github.com/dfinke/ImportExcel/pull/580. we have Parameters for TableName and one for AutoFilter that can not be used together. It is impossible to use ParametersSets to make them exclusive because when you do that it will ask you to provide a value for AutoFilter when running it without it instead of using a parameter set without it if it's not the DefaultParameterSet because of https://github.com/PowerShell/PowerShell/issues/8516. And we have more then one ParameterSet without AutoFilter so I need to set more then one DefaultParameterSet to make it work.
We ended up just ignoring AutoFilter if TableName and it provided together.
Thank you @ili101, the example in the ImportExcel issue is much more helpful, and it clearly identifies an error in the parameter binder.
This particular issue (being able to ignore an AmbiguousParameterSet error when no mandatory parameters are provided on the parameter set) is not really actionable, though. There may be significant differences in the execution paths for set2 and set3 in the example provided at the top, and PowerShell simply cannot chose one arbitrarily. Specifying multiple prioritized parameter sets isn't the right solution here either. We just need to identify errors in the parameter binder and fix them rather than layer on some additional command complexity to work around those errors.
My recommendation would be to create specific issues for parameter binding failures (like the one in Issue 580 in ImportExcel) so that they get fixed.
@KirkMunro , is my initial post helpful? (https://github.com/PowerShell/PowerShell/issues/5935#issue-289662188)
Many thanks
What I find challenging with your example, @egon984, is that such a function should really be invalid.
Here's a simplified version of what you posted earlier on this issue:
function Test-ParameterSet {
[CmdletBinding(DefaultParameterSetName="Default")]
Param(
[Parameter(ParameterSetName="Set1")]
[Parameter(ParameterSetName="Set2")]
[switch]$Switch
)
Write-Host "Function executed using parameter set $($PSCmdlet.ParameterSetName)."
}
The problem with a function defined like this is that there is no way to invoke one of the non-default parameter set. PowerShell cannot decide that for you, nor should it. Every parameter set must be unique, and invocations against commands with multiple parameter sets can only succeed if the combination of parameters that are used can be associated with the default parameter set, or, if that's not possible, with one and only one non-default parameter set. In this case, it is not possible to invoke Test-ParameterSet with the parameter -Switch because doing so results in two matching non-default parameter sets when there can only be one. Ignoring the ambiguous error is not a viable solution in this scenario.
What I suspect may have prompted this to be logged as an issue in the first place is either a command design issue or a bug in the parameter binder. Do you have any specific, real-world examples where this is a problem?
@kirkmunro, what you say does make sense, I understand.
Anyway, IMHO, ambiguities never exist: in fact, parameter sets keep the user from passing at the same time parameters which must not go together, but that's all: once the parsmeters are passed, the command can always run without problems... do you agree?
Ambiguities do exist, in PowerShell commands as well as in .NET methods that have overloads. In the Test-ParameterSet command we just discussed, invoking Test-ParameterSet -Switch is ambiguous. That's a command design issue, not a PowerShell issue.
@KirkMunro, let me show a very, very simple example:
function test {
#[CmdletBinding(DefaultParameterSetName)]
Param(
[Parameter(ParameterSetName="1")]
[switch]$one
[Parameter(ParameterSetName="2")]
[switch]$two
)
Write-Host "Function executed."
}
I want $one and $two to be mutual exclusive BUT I also want to be able to run the function without passing any parameter, right? Well, this is impossible unless I uncomment the line with CmdletBinding. IMHO, this doesn't make sense (as other more complicated things).
There's a very good reason for that though. Every invocation of an advanced function or cmdlet is bound to a parameter set in the command being invoked. That is the only way that you can logically control what happens inside of the function.
Try this:
function test {
param(
[Parameter(ParameterSetName='1')]
[switch]$One,
[Parameter(ParameterSetName='2')]
[switch]$Two
)
Write-Host "Function executed using parameter set '$($PSCmdlet.ParameterSetName)'."
}
(gcm test).ParameterSets | ft Name,@{n='Parameters';e={$_.Parameters.Name -join ','}}
This outputs the following:
Name Parameters
---- ----------
1 One,Verbose,Debug,ErrorAction,WarningAction,InformationAction,ErrorVariable,WarningVariable,InformationVariable,O…
2 Two,Verbose,Debug,ErrorAction,WarningAction,InformationAction,ErrorVariable,WarningVariable,InformationVariable,O…
The output properly identifies that you only have two parameter sets. You're suggesting that you want to invoke your function with no parameters. Which parameter set would that invocation be associated with: 1 or 2? The way the test function is defined, there is no way for PowerShell to tell, because you have not configured the function properly. That's why it's ambiguous, and rightly so. It's not up to PowerShell to make arbitrary choices on which parameter set should be used in an invocation. It's up to a command author to design their command properly so that PowerShell can identify the parameter set to use in a given command invocation.
Now consider this minor tweak to the function definition above:
function test {
[CmdletBinding(DefaultParameterSetName='1')]
param(
[Parameter(ParameterSetName='1')]
[switch]$One,
[Parameter(ParameterSetName='2')]
[switch]$Two
)
Write-Host "Function executed using parameter set '$($PSCmdlet.ParameterSetName)'."
}
In this version of the test function, you still only have two parameter sets, but when you invoke the function with no parameters, PowerShell is able to properly identify that you want it to use the 1 parameter set because that is the default. Since the switch parameter is not mandatory, and since parameter set 1 is the default, PowerShell is able to safely identify that an invocation of test with no parameters should use parameter set 1.
I could have accomplished a similar result by creating a third parameter set, like this:
function test {
[CmdletBinding(DefaultParameterSetName='0')]
param(
[Parameter(ParameterSetName='1')]
[switch]$One,
[Parameter(ParameterSetName='2')]
[switch]$Two
)
Write-Host "Function executed using parameter set '$($PSCmdlet.ParameterSetName)'."
}
In that version, there are three parameter sets. Invoking that version of test with no parameters results in parameter set 0 being used.
Advanced function authors (and cmdlet authors) must be able to define certain logic that should be used if a given parameter set is used. They can check the parameter set by looking at $PSCmdlet.ParameterSetName. The only way that can possibly work is if commands can be invoked using any parameter set unambiguously.
@KirkMunro
My recommendation would be to create specific issues for parameter binding failures (like the one in Issue 580 in ImportExcel) so that they get fixed.
Already did 6 months ago https://github.com/PowerShell/PowerShell/issues/8516. Wasn't addressed yet or Labeled with Bug/Enhancement.
@KirkMunro, IMHO I think that parameter sets are now used by Powershell the wrong way. Is there any reason to know at runtime which parameter set has been chosen? I don't think so: parameter sets should only be used to prevent mutual exclusive parameters to be passed together, but that's all, there's no need to identify a single set at runtime... The developer knows that the function will be invoked correctly and he can just take care of ALL the parameters (those not being passed will be $null, 0, "", $false, etc.)
Parameter sets are often used to change the flow of the command completely, so yeah you're gonna need to be sure of which parameter set you're in. Very often I'll see or use a pattern like this:
switch ($PSCmdlet.ParameterSetName) {
'One' {
}
'Two' {
}
# etc
}
There is often a lot of utility in knowing which parameter set is being used, far beyond just knowing which parameters have been chosen.
I'm pretty sure these patterns can be replaced by analyzing the parameters directly
Doing so is about the same as just specifying your parameter sets properly, and requires significantly less boilerplate code in comparison. :smile:
Obviously I didn't mean that one should analyze the parameters in order to find out which parameter set is in use... I did mean that using the parameters it's enough, no other information is needed, exactly as no set had been specified in the decorations.
What you lose in that approach is knowing which set of parameters you need to look at.
Consider the two different approaches:
You'd be reinventing what is already done by the parameter binder if you didn't have to care about parameter sets internally within your function, on every function that has parameter sets. That doesn't scale very well, and would lead to a whole lot of issues in different commands, vs some issues in the parameter binder that should be looked at.
Correct me if I'm wrong, please: isn't each set identified by a unique parameter (a sort of primary key)? If yes, it would be enough to check that parameter to understand if the associated set is in use.
Sure! And then you have to go through and verify none of the other mutually-exclusive parameters are _also_ specified. That's a _lot_ of manual checks.
Plus, you're completely neglecting UX of a complete function or cmdlet here; without parameter set definitions, users have absolutely no way of knowing (short of painstaking manual documentation) which parameters can and can't be used together.
Additionally, you _completely_ lose all ability to tab-complete available parameters and guarantee that the ones you're suggesting are actually valid in that context.
No: parameter sets work perfectly with tab-complete. The problem raises just at runtime.
That said, once the function runs, you are always automagically sure that everything is correct.
Right. Which is why the engine doesn't just arbitrarily pick an option that hasn't been explicitly defined. Behaviour cannot be guaranteed in ambiguous cases, so you have to be explicit which set would be chosen in order to avoid unforeseen breakages.
Parameter sets guarantee you don't use mutual exclusive parameters at the same time, and that's all you'll ever need. At runtime you are automatically sure everything is correct, and you can check what happens just looking at the unique parameters.
In other words, parameter sets do their whole job just preventing mutual exclusive parameters to be mixed: nothing else is needed.
In some cases, perhaps.
In others, a parameter set being used can be a completely different command mode, or ensuring that certain optional parameters can't be used without also specifying a particular switch.
What I'm trying to say is: just because you personally don't see a need for the more advanced features doesn't mean there isn't a need for it. Permitting ambiguous parameter sets is a really bad idea. If you want to allow an empty set of parameters as input, you need to specify that.
Not really sure why that's such a big deal, to be honest. :confused:
If you can provide a practical example, I'll understand: otherwise I apologize but I can't get the point of having to provide more information than those strictly required.
Many thanks in advance.
You just made the point though: PowerShell strictly requires that every parameter in an advanced function belong to a parameter set.
When you don't specify parameter sets at all, all parameters are added to an implicit parameter set called '__AllParameterSets'. What I hear you saying is that you want parameter sets for documentation only, so that end users see the options, and then internally you want to just work with whatever parameters you want however you want, but you can't do that. You also want to invoke a command with no parameters but without a parameter set for that, but you can't do that either. The documentation, command syntax, and implementation must all match. Having them not match would be a recipe for disaster, and a programming nightmare.
With respect specifically to Export-Excel, where a lot of this discussion seems to have started, without digging into the details too far I think that command could benefit from some design improvements for how it uses parameter sets, and I think it could also benefit from being split out into at least one other separate command. At some point what you want to specify in a single command becomes so complex that multiple commands may be a better approach, and if you want a "singular" way to invoke those multiple commands, then there are other approaches that could be taken on top of that, such as defining a DSL, passing in a configuration object to a command that is defined using a separate command, etc.
Not for documentation only: parameter sets do a wonderful job preventing the user from passing together mutual exclusive data (tab-complete is an example, it won't suggest "foreign" parameters). Even when you run Get-Command you get the correct signatures... but then you run the function and problems arise.
IMHO, parameters sets should be implemented as "parameters subsets" (of a single list of all the parameters), or at least there should be a cmdletbinding letting the developer choose the desired behavior.
@egon984:
I want
$oneand$twoto be mutually exclusive BUT I also want to be able to run the function without passing any parameter, right? Well, this is impossible unless I uncomment the line withCmdletBinding.
Have a look at the proposal in #11237, which addresses this aspect (it doesn't propose _defaulting_ to allowing argument-less invocations, but makes signaling the intent to do so more robust and predictable).
@KirkMunro and @vexx32, I invite you to take a look as well.
Most helpful comment
@egon984
If
testFunction -switch2succeeded, what would$PSCmdlet.ParameterSetNamebe set to?It seems like another disambiguation hint would be needed. I've run into this sort of thing a few times. I wonder whether being able to provide a
ParameterSetPriorityarray rather thanDefaultParameterSetwould be an improvement. Your function would bewhich would be interpreted as "default to 'set1' and if you can't decide between 'set2' and 'set3', choose 'set2'.