How do parameter sets resolving positional parameters in Powershell?

Summary

This postmortem analyzes why a seemingly valid PowerShell parameter‑set design fails to resolve when positional parameters overlap across sets. The failure stems from PowerShell’s parameter binder attempting to match all possible parameter sets simultaneously, leading to ambiguous interpretations even when a human believes the intent is obvious.

Root Cause

The root cause is parameter‑set ambiguity created by overlapping positional parameters. Specifically:

  • Multiple parameter sets define parameters at the same positions.
  • PowerShell attempts to bind arguments to every parameter set until one becomes impossible.
  • Because $NameOrWidth and $Width are both strings and both occupy early positions in different sets, the binder cannot conclusively determine which set applies.
  • The presence of a mandatory $ScriptBlock at different positions across sets increases ambiguity.
  • The binder fails before it ever considers the scriptblock as a disambiguator.

Key takeaway: PowerShell’s binder does not “skip” parameters that don’t belong to a set; it tries to match all sets until one wins. Overlapping positional definitions make that impossible.

Why This Happens in Real Systems

Real systems hit this problem because PowerShell’s parameter binder:

  • Does not short‑circuit based on type alone.
  • Does not assume intent based on the presence of a scriptblock.
  • Attempts to bind all positional parameters in all sets, even if some parameters are not part of a given set.
  • Fails early when two or more sets remain viable but produce conflicting bindings.

This is a classic case of overlapping positional signatures causing the binder to give up.

Real-World Impact

When this occurs in production:

  • Commands that “should work” fail with confusing errors.
  • Users lose confidence in positional syntax.
  • Engineers waste time debugging binder behavior instead of business logic.
  • Teams often abandon parameter sets entirely for complex commands.

Example or Code (if necessary and relevant)

Below is a minimal reproduction showing the same ambiguity pattern:

function Test-Foo {
    [CmdletBinding(DefaultParameterSetName='A')]
    param(
        [Parameter(ParameterSetName='A', Position=0)]
        [Parameter(ParameterSetName='B', Position=0)]
        [string]$Name,

        [Parameter(ParameterSetName='B', Position=1)]
        [string]$Width,

        [Parameter(Mandatory, ParameterSetName='A', Position=1)]
        [Parameter(Mandatory, ParameterSetName='B', Position=2)]
        [ScriptBlock]$Script
    )
}

Calling:

Test-Foo "One" {}

fails for the same reason: the binder cannot decide whether "One" is $Name for set A or $Name for set B.

How Senior Engineers Fix It

Experienced engineers avoid overlapping positional signatures entirely. Common fixes include:

  • Eliminate positional parameters for complex parameter sets.
  • Use distinct types to disambiguate (e.g., [int] vs [string]).
  • Use explicit flags (-Name, -Width) to avoid binder confusion.
  • Collapse the design into a single parameter accepting [object[]] and parse manually.
  • Use separate commands when the semantics differ enough to require different shapes.

The most reliable fix in your scenario is:

  • Accept a single [object[]] and parse, or
  • Use explicit parameter names for optional values.

Why Juniors Miss It

Junior engineers often assume:

  • PowerShell will “figure out” the right parameter set based on types.
  • Parameters excluded from a set are ignored during binding.
  • Scriptblocks are “obviously” distinguishable from strings.
  • Positional parameters behave like function overloads in other languages.

They miss that PowerShell’s binder is deterministic but not intelligent—it does not infer intent, and it does not treat scriptblocks as special disambiguators.


If you want, I can walk through your exact parameter sets and show step‑by‑step how the binder evaluates each argument.

Leave a Comment