Repository navigation
Description
Revisiting ParameterSets
Summary
The current limitation to parametersets easily leads to an explosion of parametersets.
Let's say a command has 4 different ways to connect to something, with 3 different identity resolution options to achieve its ways in one of 4 different ways:
4 * 3 * 4 = 48
That currently ends up as 48 different parametersets. Any additional parameter that is legal for most (but not all) 48 sets would require an equal number of separate parameter attributes! As Kirk mentioned in a previous discussion on this, Invoke-Command is a default cmdlet with a great example of the curent complexity (though not quite as bad as the scenario drawn up above).
In total, I have three issues with the current system, that I'd like to address (and a proposed form for each of them):
- Limited utility of
DefaultParameterSetNamein complex parameterset situations. - Parameter attribute duplication with exactly the same properties except
ParameterSetName - Combination of multiple sets of mutually exclusive parameters exploding total set number
On top of all that, we also need to work on the consuming user's experience in help and syntax display, as simplifying the experience for developers is likely to lead to an increased use, turning the current display form into utter futility if the total available complexity increases any further.
So here are my ideas on how the final form could be made to look. Feedback, counters and counter proposals more than welcome :)
1) Improving the DefaultParameterSetName
The current system is limited by the fact that we can only have a single DefaultParameterSetName to help with parameterset resolution. An example parameter situation:
The command Get-Something has two primary parameters that are mutually exclusive. It also has two supplementary parameters which may be used with either, but not together. Theoretically this results in 6 parameter sets:
Get-Something -Main1
Get-Something -Main1 -SubA
Get-Something -Main1 -SubB
Get-Something -Main2
Get-Something -Main2 -SubA
Get-Something -Main2 -SubBWith the current system, this cannot be expressed at all: Assuming the first path becomes the DefaultParameterSetName, the fourth option will automatically lead to an ambiguous parameterset (as all the last three options could still be possible).
This could be solved by making the attribute accept a string-array in descending order of relevance:
function Get-Something {
[CmdletBinding(DefaultParameterSetName = 'Set1', 'Set2')]
param (
# Parameters go here
)
}With that, Set1 would be the default parameterset. If that set is no longer possible, switch to Set2 being the default set (and so on, if more are specified).
In conjunction with proposal 3), this could be extended to also support a hashtable instead, for different parameterset groups:
function Get-Something {
[CmdletBinding(DefaultParameterSetName = @{
Group1 = 'Set1'
Group2 = 'SetA'
})]
param (
# Parameters go here
)
}2) Reduce Parameter Attribute duplication
A parameter that can be part of multiple (but not all) parametersets requires an individual parameter attribute for each possible set. This increases parameter block size, causes more effort and reduces overall readability. Example:
function Get-Something {
[CmdletBinding()]
param (
# Lots of other parameters
[Parameter(ParameterSetName = 'Set1')]
[Parameter(ParameterSetName = 'Set2')]
[Parameter(ParameterSetName = 'Set3')]
[Parameter(ParameterSetName = 'Set4')]
[Parameter(ParameterSetName = 'Set5')]
[Parameter(ParameterSetName = 'Set6')]
[MyModule.Options]
$Option
)
}I thus propose adding three new legal notations:
# Proposal 1: Array
function Get-Something {
[CmdletBinding()]
param (
# Lots of other parameters
[Parameter(ParameterSetName = 'Set1', 'Set2', 'Set3', 'Set4', 'Set5', 'Set6')]
[MyModule.Options]
$Option
)
}
# Proposal 2: Wildcard
function Get-Something {
[CmdletBinding()]
param (
# Lots of other parameters
[Parameter(ParameterSetWildcard = 'Set*')]
[MyModule.Options]
$Option
)
}
# Proposal 3: Regex
function Get-Something {
[CmdletBinding()]
param (
# Lots of other parameters
[Parameter(ParameterSetPattern = '^Set\d$')]
[MyModule.Options]
$Option
)
}3) Multiple Sets of Parametersets
Simply put, too often we have two different sets of parameters, that each within their group are mutually exclusive, but could be freely combined with any one of the options of the other group.
An example of how that could look:
function Get-Something {
[CmdletBinding()]
param (
[Parameter(ParameterSetGroup = 'Group1', ParameterSetName = 'Set1')]
$Param1,
[Parameter(ParameterSetGroup = 'Group1', ParameterSetName = 'Set2')]
$Param2,
[Parameter(ParameterSetGroup = 'Group1', ParameterSetName = 'Set3')]
$Param3,
[Parameter(ParameterSetGroup = 'Group2', ParameterSetName = 'SetA')]
$Param4,
[Parameter(ParameterSetGroup = 'Group2', ParameterSetName = 'SetB')]
$Param5,
[Parameter(ParameterSetGroup = 'Group2', ParameterSetName = 'SetC')]
$Param6,
$Param7
)
}Adding a bit on top: What if another parameter is not actually supported for all parameters of a given group?
function Get-Something {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group1', ParameterSetName = 'Set1')]
$Param1,
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group1', ParameterSetName = 'Set2')]
$Param2,
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group1', ParameterSetName = 'Set3')]
$Param3,
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group2', ParameterSetName = 'SetA')]
$Param4,
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group2', ParameterSetName = 'SetB')]
$Param5,
[Parameter(Mandatory = $true, ParameterSetGroup = 'Group2', ParameterSetName = 'SetC')]
$Param6,
[Parameter(ParameterSetName = @{
Group1 = 'Set1','Set2'
Group2 = 'SetA'
})]
[switch]
$Param7,
[string]
$Param8
)
}I am not sure I would be happy with commands that introduce this level of complexity to their parameterization, but it could at least still be handled that way.
4) Impact on the syntax notation
With all the proposals above, we would be enabling more complex parameterization situations while - I believe - improving the overall coder experience of defining them. However, we cannot forget the user experience, trying to figure out how to actually use the bloody command!
Here too I'd like to borrow from Kirk's excellent post. With actual ParameterSetGroups, I believe it to be possible to implement his proposed new form of syntax notation:
SYNTAX
Get-Something {<Group1>} {<Group2>} [-Param8 <String>] [<CommonParameters>]
{<Group1>}
Set1: -Param1 <Object> [-Param7 / {<SetA>}]
Set2: -Param2 <Object> [-Param7 / {<SetA>}]
Set3: -Param3 <Object>
{<Group2>}
SetA: -Param4 <Object> [-Param7 / {<Set1> | <Set2>}]
SetB: -Param5 <Object>
SetC: -Param6 <Object>
Concluding
I really hope we can improve the parameterset situation and I believe this to be a step in that direction. Any opinions / counter proposals / further enhancements?
Cheers,
Fred
Activity
vexx32 commentedon May 27, 2020
I like the ideas here, for the most part; I'm not sold on the groups / partial groups thing, I think that may be overcomplicating things a fair bit.
I also think the syntax should be much simpler, if possible. Reducing lines & repetition is great, but if we end up with lines > 1 screen wide a lot of folks will just end up splitting it up into more lines again.
Also... if we're redefining this and adding new names/syntaxes/etc I'd much prefer the
Parameterprefix on every property name to go away. We already have the clear context that this is in a[parameter]attribute -- I don't think we also need to specify that it's aParameterSetName, it's (IMO) plenty clear to call itSetNameor even justSetin that context -- CmdletBinding should still useDefaultParameterSet(drop theName, please, it's so long!) for clarity.Hmm. Still not sold on the groups.
SetandGroupseem fairly interchangeable and prone to confusion tbh.Also, allowing mutliple default parameter sets would need different syntax I think?
[CmdletBinding(DefaultParameterSet = 'SetA', 'SetB')]is ambiguous; we'd need at least[CmdletBinding(DefaultParameterSet = @( 'SetA', 'SetB') )]to be able to be clear about which values belong to which property. There may be better alternatives 🙂scriptingstudio commentedon May 27, 2020
Multiple default parameter sets are NOT default at all logically. Default result is logically single one. Multiple end-result implies condition. Dead end idea.
vexx32 commentedon May 27, 2020
I agree with the premise there Matthew Gray (@scriptingstudio) but I don't agree with your conclusion. Yes, having "multiple defaults" is a bit of a contradiction, but the idea comes from the currently-unworkable but still desirable configuration Friedrich Weinmann (@FriedrichWeinmann) mentioned in his initial description.
One way or another we need a way to handle that appropriately, whether that's just making the binder much smarter in those circumstances, or having an explicit way to specify fallback "defaults" for those slightly more ambiguous cases.
Windos commentedon May 27, 2020
Anything we can do to make the management of parameter sets easier I'm all for. The "main" function on BurntToast is sitting at eight sets, and that's after trimming them back. I dread, and am very picky, about adding new functionality because of potentially breaking things or the number of sets multiplying.
There was a discussion on native support for mutually exclusive parameters a few years back started by Mark Kraus (@markekraus). This idea would suite my needs, as one of my key drivers is keeping "sound" options away from the "silent" options.
As for this idea, I like everything under heading # 2. Just being able to supple multiple sets under one attribute would simplify things dramatically.
Indifferent on heading # 1. I can see and support the problem it solves, but haven't personally run into it to feel personally invested in the fix 😛
Not sure if I'm sold on the Groups/Sets concept... just not getting my head around it (might just need a more "real world" example.)
We do need a better way of displaying the sets to end users. I wonder if we can use colour to help with this? Say all the mandatory parameters are a certain colour, the parameters that are in all sets are another? I haven't fully thought this through yet...
vexx32 commentedon May 27, 2020
Josh King (@Windos) for that last point, we have #8692 tracking that, but afaik little or no work has been done in that area either. Definitely related here, though; we need a way to display whatever we come up with clearly to users.
merddyin commentedon May 27, 2020
What about a slightly more mixed approach.
Implement a parametersets code block construct similar to what's done for dynamic parameters, in that it comes after the param block. Define one or more groups within the block as string arrays. To make it even easier, set it up so that any parameters not included in any groups are automatically included available in all groups.
To accommodate slightly more complex scenarios, you could add a keyword option for the group definitions to account for specialized functions like 'set', 'exclusivity', and 'allsets'. 'Set' is a normal parameter set, while the second can be used to specify one or more collections of mutually exclusive parameters. This would allow for multiple parameters to all be included in one or mote sets, but use of any single one in an exclusion group blocks use of others within the same exclusion set. The 'allsets' type would be a special optimism type that could only be used once, and would serve to intentionally include specific parameters in all sets. If used, then PowerShell would generate an error in the event not all parameters were accounted for in at least one set group.
This approach would make it easy for devs and also for Parsing the help for users using the proposed augments to format as well. The only change I would propose here would be a simple indicator to mark a parameter as being part of an exclusivity set for a given parameter group.
FriedrichWeinmann commentedon May 27, 2020
Hm, I must admit I have my doubts about that improving the overall readability for me.
Another complicating factor is that all the parameter attribute values can be specific to a given parameterset (for example accepting from pipeline or mandatory). It would admittedly be more flexible, but by the time we add in those other attributes, getting a coherent picture of the parameter when we need to look at both the parameter block as well as the ParameterSet section ... I don't really see that working out.
Mind doing a mock-up of what you had in mind?
Another thing to keep in mind is the cost to parameter binding performance, which I definitely would be concerned with, though I'm sure that would be a solvable issue.
vexx32 commentedon May 27, 2020
I dunno. That kinda feels a bit much like dynamicparam-lite. Part of why I don't like dynamicparam is it's a much less declarative syntax, and it's much harder to tell at a glance what's going on as a result. 😕
merddyin commentedon May 27, 2020
merddyin commentedon May 28, 2020
Ok, finally made it to an actual computer. What's in my head is something like the below example. Processing would, of course, run from top to bottom, stopping at the first full match. Obviously if you end up with 50 different Sets you might run into some speed challenges, but to my non-programmer eyes at least, the below is easier to read down than the current approach, and I would personally find it easier than trying to shove everything into the param block. There's still some repetition (I don't know that you could really avoid that), but it's more logically organized.
The net result of the above would be as follows:
The one scenario the above doesn't necessarily account for would be the need for mutual exclusivity specific to a single 'set' definition, but my though here is perhaps to use something like a 'Group' keyword in the ParamExclusivity definition to allow targeting -
[ParamExclusivity(Group=$Set1)]FriedrichWeinmann commentedon May 28, 2020
Hate to disagree, but ... I don't consider that more intuitive to grasp than my approach. Also doesn't really cover the multiple groups of sets part of my proposal - adding that would not be too hard, but make the whole thing yet more complicated.
Here's the same param block using my proposal with Rain Sallow (/u/ta11ow) (@vexx32) 's updated notation:
merddyin commentedon May 28, 2020
jhoneill commentedon May 29, 2020
Not quite,
This causes a complication when things are piped in but it's solvable.
That aside there is a lot of sense here. Especially with the problem of multiplication, if every parameter ends up being prefixed with multiple lines saying which set it is in and where it is mandatory the parameters themselves shrink into a sea of other information. Help becomes horrible because each set is listed.
A way of saying "Allow one of these" without having to double or treble or quadruple the number of sets would solve many , maybe most, problems in one go
Dyamic parameters cause at least as many problems as they solve. Having everything relating the parameter with the parameter means even if you can't see at once what is going on, it's in one place.
But when you say
Parameter A is in Set 1,3, 5 and 7
Parameter B is in Set 2,3, 6 and 7
Parameter C is in Set 4,5, 6 and 7
What's in any given set isn't obvious (which is what we have now)
If you say
Set 1 is A, 2 is B, 3 is A and B, 4 is C, 5 is A and C, 6 is B and C and 7 is A, B and C
What sets B is in needs a long hard look.
I think we have this X-Y product thing going on.; but I'm having a problem visualizing multiple axes.
In importExcel we have some issues Import-Excel has 6 sets and Set-ExcelRange which applies formatting does use any because of the trouble in trying to support all that would be needed
It has borderAround, which excludes Borderleft, borderRight etc. It originally just had switches for [add] bold, italic, underline, strikethru, subscript, superscript
So you have size, and type face which go in all sets, including the unnamed default
borderaround, and each of the other borders which divide things into two sets; subscript , superscript (and neither), So that's six sets. When someone said they wanted remove-border remove-bold, etc we went for the unconventional -bold:$false because -bold -unbold and neither would allowing -noborder -bold, -borderbottom -Unbold, -borderAround -italic -unStrikethru ... with the appropriate options blocked means over 100 sets.
I think Chris Whitfield (@merddyin) has got closer to solving this problem, because currently sets serve the need of saying "These combinations cannot be used together" by specifying what can. That's great if the choice is either (A and/or,B and/or C) or (D, and/or E and/or F) not for (A or B) and/or (C or D) and / or (E or F)
Expressing the latter needs a new syntax which may need nesting
something like
with nesting it can do things like border weight but I don't think the syntax can be called nice.
[parameter()] inside these blocks would say if one member is present another is mandatory and also say that NoBold and noBorder come at the end of tab completion so users don't need to tab through on/off for options they don't want to set.
I don't think there is a single new syntax which will please everyone, and if there is to be change the first thing to do might be to identify the cases which give the most frequent problems in practice (the above seems is a real problem for me, but how common is it?) there are likely to be cases which can't be done but no-one wants.
It's also worth saying that accessing other parameters in validation is not easy and two validation attributes could solve the problem
Note that tab completion would need to look at the with / notWith attributes of parameters, so borderweight only becomes an option if border around or left or right is specified. I can imagine how one says say some border is mandatory - but there are 17 allowable combinations - but I would hope never to see it coded !
FriedrichWeinmann commentedon May 29, 2020
Interesting idea/concern.
I suppose some of this concern could be mitigated in the original concept by making an entire parameterset-group optional if no parameter in it is defined as mandatory.
Alternatively, I suppose we could also add
ExcludeandRequireoptions in the parameter attribute:Not sure I'd be a fan of the added complexity though, especially when it comes to documenting the syntax to user.
jhoneill commentedon May 29, 2020
I've thought more about this since making the post and something ("with" / "notwith", "exclude/require") in the parameter attribute does feel to me more and more like the right way.
Try doing
get-help whereto see how the existing syntax goes wrong. Because -gt -match -notlike and all the others can't be used together each has it's own set - 32 of them! More than a screenful of sets before the help. add-Fulland look at the detail under, say, NotMatch and NotLike. It says BOTH are required ... they are required in their own mutually exclusive parameter sets but the help doesn't give you that info with the parameter.Now imagine that help showing one parameter set and under the parameter in the help it said "Can't be used with: -eq -ge -gt ...."
Explaining to end users gets way simpler (although where-object is an unusual case) and to programmers who currently have to grasp sets its also simpler. In both cases it does what Rain Sallow (/u/ta11ow) (@vexx32) said above (I think it was) keep everything about the parameter in one place. The information which says -NotLike and -NotMatch are not required at the same time is 500 lines back up the help from the suggestion that they are.
It needs more thought, and some more input would certainly be good, as I say, I'm starting to think this is a good solution to a lot of the cases.
iSazonov commentedon Oct 6, 2020
It is useful to look a conclusion for old Steve Lee (@SteveL-MSFT)'s RFC PowerShell/PowerShell-RFC#2 (comment)
A main point here - User Experience
Obviously enhancements of existing attributes or introducing new attributes add new complicity and impairs readability and maintainability.
We should consider these attributes as implementation details and hide the complicity in PowerShell Engine/compiler.
Why?
Let's look how we create new cmdlet/adv. function: define the cmdlet name, output type(s), define parameters and their input value validations, then define parameter dependencies.
Only last is in a scope of the discussion. How do we define parameter dependencies? First, we create a syntax diagram on paper or in the head, then manually transform/"compile" the diagram to ValidationSet attributes with some names, and at the end, we run
Get-Command -Syntaxto generate a syntax diagram to check results.So starting and ending points is the same syntax diagram. Can anybody propose anything more user-friendly than syntax diagram? We can't think of anything else. (If we look the related issue #13746 there iRon7 is trying to resolve the problem and he has started with syntax diagram too.)
This immediately raises questions:
Thus the solution to this problem is to prompt the user to create a syntax diagram.
This gives us:
Roadmap is:
(we could start with Kirk Munro (@KirkMunro)'s proposal mentioned above in the post)
(we get very flexible Ast tree )
(we get great UX)
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 23, 2023
This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.
drstonephd commentedon Oct 11, 2024
I have a PhD in physics. Cherenkov radiation is a response from the media through which a charge particle travels faster than the local speed of light, not from directly from the moving charge. I figured that out many years ago. Now, why can't I figure out parameter sets? Something has a smell here.