Skip to content
Shell & TerminalDeep Dive Published Updated 6 min readViews unavailable

PowerShell Default Parameter Values: Scope, Binding Order, and Key Collisions

Use $PSDefaultParameterValues in a deliberate scope, understand how defaults bind, and avoid wildcard collisions and hidden pipeline behavior.

PowerShell’s $PSDefaultParameterValues preference variable can remove repetitive options from an interactive session, but it also changes command binding without changing the command text. That makes it useful for a narrowly scoped convention and risky as invisible global policy. A script that appears to request one behavior may actually receive a value inherited from a profile, a parent scope, or a wildcard key.

Treat this variable as a binding rule, not as a convenience alias. Keep the rule local to the commands that need it, make important behavior explicit in automation, and test the exact engine and command shape that will run in production.

The key is a command-parameter pair

The dictionary uses keys in the form CommandName:ParameterName. A value must be valid for the parameter, or may be a script block whose result becomes that value. Command and parameter portions can contain wildcard characters. For example, a narrow table could set ErrorAction to Stop for two file-inspection commands:

& {
    $PSDefaultParameterValues = @{
        'Get-ChildItem:ErrorAction' = 'Stop'
        'Get-Content:ErrorAction'   = 'Stop'
    }

    $root = Join-Path $HOME 'work'
    if (Test-Path -LiteralPath $root) {
        Get-ChildItem -LiteralPath $root -Directory
        $readme = Join-Path $root 'README.md'
        if (Test-Path -LiteralPath $readme -PathType Leaf) {
            Get-Content -LiteralPath $readme -TotalCount 5
        }
    }
}

The call operator runs the script block in a child scope. The preference assignment and commands using it live together; when the block returns, the assignment does not become a lasting profile setting. If a file is optional, handle that condition explicitly rather than broadening the default to every command. The sample’s Test-Path guard is not a substitute for handling a race in which the file changes between the check and read.

An explicit command-line argument remains the clearest exception to a default:

Get-Content -LiteralPath $path -TotalCount 5 -ErrorAction Continue

Microsoft documents that configured values are used unless another value is specified on the command line. Prefer this visible form for one-off exceptions. It gives a reviewer evidence of the behavior instead of requiring them to find a profile setting.

Scope is part of the configuration

PowerShell preference variables are created in the global scope when a runspace starts. A variable is visible to child scopes unless it is private, and a child-scope assignment does not ordinarily rewrite the parent variable. That makes a function or invoked script block a useful boundary for temporary defaults. Dot-sourcing is different: it executes in the caller’s scope, so dot-sourcing a file that assigns this variable can change the caller’s behavior.

Profiles run at startup and can put an assignment into the global scope for the life of that runspace. If that is intentional for interactive work, document it in the profile and inspect it with:

$PSDefaultParameterValues

Do not rely on a profile for scheduled tasks, CI, remote commands, or scripts that must be reproducible. Those contexts may use a fresh runspace, a different profile, or a separate host. Put required behavior on the command line or in a controlled wrapper, and keep transient defaults out of shared shell state. A remote runspace is its own execution context; a local profile does not prove what defaults a remote command will see.

For automation, a temporary block is often safer than profile configuration. If a reusable function deliberately establishes a default, make the function’s contract clear and keep its call boundary narrow. Avoid changing $global:PSDefaultParameterValues from a helper: that turns a local policy into a session-wide side effect and complicates cleanup when an error interrupts the workflow.

Wildcards do not establish a reliable specificity hierarchy

Wildcards are powerful for simple conventions, such as setting a common switch across many commands. They are not a general policy engine. In the current PowerShell parameter-binder source, exact keys are collected before wildcard keys. If a wildcard and an exact key assign different values to the same resolved parameter, the binder warns about conflicting values and removes that parameter’s default rather than promising that the exact key wins. Multiple matching wildcard rules can create the same conflict. Do not treat insertion order or a more-specific-looking string as a precedence guarantee.

For example, this is a poor way to express “Stop everywhere, except continue for Get-Content”:

$PSDefaultParameterValues = @{
    '*:ErrorAction'            = 'Stop'
    'Get-Content:ErrorAction'  = 'Continue'
}

Instead, enumerate the commands that need the default, or set the exceptional behavior explicitly at the call site. A wildcard is reasonable when every matching command should receive the same compatible value and the matching set is understood. Review that set again when modules add commands or aliases.

The special key Disabled has a different meaning from a command-parameter key: when set to a truthy value, it disables default-parameter binding. It is not a per-command override. Avoid leaving it enabled in a long-lived profile, where later commands may silently stop receiving the table’s defaults.

Defaults run before pipeline input

PowerShell binds named and positional command-line arguments, then applies default parameter values before it processes pipeline input. That ordering matters. A default can bind a parameter before the incoming pipeline object is considered; do not assign defaults to parameters that a pipeline is expected to supply unless you have tested the exact command and engine version. A pipeline may bind by value or by property name, and a default can change which value is already bound when that stage begins.

The current engine source also shows a second default-binding pass after dynamic parameters are discovered. This is an implementation detail rather than a substitute for a stable script contract. Module versions can change dynamic parameter sets, and engine releases can refine binding behavior. For scripts that accept pipeline input, test both the pipeline path and the no-input path with the exact PowerShell release used by the job.

Inspect and test the rule at the boundary

Before enabling a table, print it and review every key. For a conflict, the engine warning is useful evidence, but do not make a warning the intended behavior. Test explicit arguments, a command with no matching key, a matching wildcard, an expected pipeline value, and a command that throws. Run the same cases in a clean process with and without profiles so the source of a default is visible.

Keep the table free of credentials, tokens, or machine-specific secret values. A default can put a value into command history, tracing output, or a diagnostic transcript just as an explicit argument can. Do not use it to conceal a parameter that should instead be passed through a credential provider or a scoped secret mechanism.

The practical boundary is simple: use $PSDefaultParameterValues for small, visible, temporary defaults; use explicit parameters for durable automation contracts. Avoid broad wildcards when commands need exceptions, and never assume defaults bind after pipeline data. The value of a default is reduced typing. The cost is hidden behavior, so the configuration should be at least as reviewable as the commands it affects.

Related:

Sources:

Comments