Skip to content

Make parameter sets of advanced functions/scripts more robust and more predictable #11237

Closed

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 __AllParameterSets in argument-less invocations or in invocations that comprise parameters without explicit set association.

    • __AllParameterSets is 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 __AllParameterSets should 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 Name suffix 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.DefaultParameterSetName will then reflect '' (the empty string) in such invocations.
  • Allow omitting the wordy Name suffix from the DefaultParameterSetName and ParameterSetName attribute 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 -Message argument is passed, print that message as-is.
    If one of the mutually exclusive -AsError or -AsWarning switches 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
}

Activity

  1. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    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.

  2. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    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.

  3. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    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.

  4. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

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 bugResolution-No ActivityIssue has had no activity for 6 months or more

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions