-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Description
Important:
-
The proposal below is undoubtedly a change that cannot be made without breaking existing code.
However, I don't think the existing problems can be fixed while maintaining backward compatibility.
Therefore, use of an implementation of this proposal would have to be on an opt-in basis by future function / script authors, via an optional feature or, should there ever be one, a "vNext" PowerShell version that is allowed to break backward compatibility in the interest of fixing many longstanding problems. -
This will probably have to become an RFC, if there's sufficient interest - this issue is meant to get the conversion started and gauge that interest.
Summary of the new feature/enhancement
There are several problems with the existing parameter-set handling:
-
In the absence of explicitly designating a default parameter set, PowerShell tries to infer one, but does so inconsistently (Advanced functions require specifying a default parameter set with two or more explicit parameter sets #11143) and sometimes incorrectly (Binding a parameter via the pipeline can break implicit parameter-set selection #11235).
-
$PSCmdlet.ParameterSetName, meant to reflect the parameter set in effect (selected by the combination of arguments and/or pipeline input), obscurely and confusingly reflects__AllParameterSetsin argument-less invocations or in invocations that comprise parameters without explicit set association.__AllParameterSetsis also the internal name of the "meta" set automatically assigned to parameters without explicit set association, and it signals that a given parameter belongs to all parameter sets (if any exist beyond the implied unnamed one).- As such, the name
__AllParameterSetsshould be considered an implementation detail, and making it do double duty as the name of the effective set is obscure and confusing.
-
Ill-defined parameter sets do not become apparent until runtime, as opposed to parse time, including misspelled attribute property names. Problems may not be discovered until later, when a specific combination of arguments and inputs is used on invocation (e.g., unintentionally allowing / disallowing argument-less invocation).
-
Designating an otherwise undefined default parameter set as the default - e.g.,
[CmdletBinding(DefaultParameterSetName='PossiblyAccidentalDefault')]is quietly accepted (and an invocation without arguments or only with parameters not explicitly associated with sets make it the effective on).- While this technique is actually currently required to implement a function that explicitly allows passing no arguments alongside parameters with explicit set associations (it is also the easiest way to allow invocations binding all-parameter-sets parameters only), it is non-obvious and brittle.
-
On a minor note, the
Namesuffix in the attribute property names seems unnecessarily verbose (ParameterSetName,DefaultParameterSetName).
Proposed technical implementation details (optional)
-
Enforce the following at parse time to signal that there's a fundamental problem with the function / script that needs resolving, as opposed to the current situational failures that depend on the specifics of a given invocation. This also has the advantage of being able to provide specific error messages with clear resolution directions.
-
If an explicit parameter set is associated with at least one parameter, enforce having to explicitly designate a default parameter set in
[CmdletBinding()]. This avoids the obscurity of the current, situational inference of the default and instead signals explicit intent. -
Do not permit designating an undefined parameter set as the default in the
[CmdletBinding()]attribute - the name must refer to a set named in at least one[Parameter()]attribute. -
If feasible, ensure up front that there are no unknown / mistyped property names in the
[CmdletBinding()],[Parameter()], ... attributes.
-
-
Make
''(empty string) the (non)-name for the implied, unnamed parameter set to become effective in argument-less invocations and invocations with all-parameter-sets parameters only.[CmdletBinding(DefaultParameterSetName='')]will signal the explicit intent to allow invocations without arguments altogether, as well as invocations comprising all-parameter-sets parameters only.$PSCmdlet.DefaultParameterSetNamewill then reflect''(the empty string) in such invocations.
-
Allow omitting the wordy
Namesuffix from theDefaultParameterSetNameandParameterSetNameattribute properties.
Example
Consider a function Write-Message, which should wrap Write-Host as follows:
- Print a default message, if no arguments are passed at all.
- If only a
-Messageargument is passed, print that message as-is.
If one of the mutually exclusive-AsErroror-AsWarningswitches is passed, print the (default or explicit) message in a switch-specific color.
Current declaration:
function Write-Message {
# * Without artificially designating a default parameter set,
# argument-less and -Message-only invocations don't work.
# * If 'ArtificalName' happens to be a leftover / mistyped name, you may
# accidentally be enabling undesired invocations.
[CmdletBinding(DefaultParameterSetName = 'ArtificalName')]
param(
# Optional message that has a default.
[string] $Message = 'Completed.',
# Note: Whether you use `Mandatory` or not makes no difference here.
[Parameter(ParameterSetName = 'err')] [switch] $AsError,
[Parameter(ParameterSetName = 'warn')] [switch] $AsWarning
)
$writeHostArgs = @{ Object = $Message }
if ($AsError) { $writeHostArgs.ForegroundColor = 'Red' }
elseif ($AsWarning) { $writeHostArgs.ForegroundColor = 'Yellow' }
Write-Host @writeHostArgs
}Declaration with the proposal implemented:
function Write-Message {
# * Designating '' as the default parameter set unambiguously signals the intent to
# allow argument-less and all-parameter-sets-parameters-only invocations.
# * Any other name would have to refer to a set explicitly mentioned in at least one
# [Parameter()] attribute.
[CmdletBinding(DefaultParameterSet = '')]
param(
# Optional message that has a default.
[string] $Message = 'Completed.',
# Note: Whether you use `Mandatory` or not makes no difference here.
[Parameter(ParameterSet = 'err')] [switch] $AsError,
[Parameter(ParameterSet = 'warn')] [switch] $AsWarning
)
$writeHostArgs = @{ Object = $Message }
if ($AsError) { $writeHostArgs.ForegroundColor = 'Red' }
elseif ($AsWarning) { $writeHostArgs.ForegroundColor = 'Yellow' }
Write-Host @writeHostArgs
}