Chapter 08Lesson 02~140 minutes

Script Parameters: param(), Mandatory Input, Defaults, and Switches

Design script parameters as a stable CLI boundary with mandatory values, defaults, switches, arrays, types, and explicit caller intent.

Learning objectives

  • Use param() to replace hard-coded operational inputs with an explicit script interface.
  • Distinguish named and positional parameters and prefer readable named invocation for automation.
  • Use mandatory parameters, default values, switch parameters, arrays, and appropriate types.
  • Interpret parameter-binding and conversion failures as boundary errors.
  • Use $PSBoundParameters to distinguish explicit caller input from script defaults.
  • Build a small CLI-style planning script that does not rely on Read-Host.

1. Parameters turn changing inputs into an explicit command contract

A hard-coded script can only solve the exact case its author typed. A parameter is a named input accepted at the script boundary. Parameters make variation visible: target environment, paths, counts, flags, and other user choices appear in the command line instead of being hidden edits inside the file.

# Avoid this:
$Environment = 'prod'
$Target = 'api-01'

# Prefer an explicit boundary:
param(
    [string]$Environment,
    [string]$Target
)

The param() block belongs at the beginning of the script body, after any applicable #Requires statements and comments/help. Once a script exposes parameters, callers can discover and automate its interface without editing source code.

2. Named parameters communicate intent; positional parameters are a convenience

A caller can identify a parameter by name, such as -Environment test. PowerShell can also bind some script parameters positionally. Named calls are usually clearer in production because code review shows what each value means and parameter order can evolve more safely.

# Deploy-App.ps1
param(
    [Parameter(Position = 0)]
    [string]$Environment,

    [Parameter(Position = 1)]
    [string]$Application
)

[pscustomobject]@{ Environment = $Environment; Application = $Application }

# Clearer automation call:
# ./Deploy-App.ps1 -Environment test -Application catalog
Interface rule: Choose stable, descriptive parameter names. Scripts become APIs to humans, CI runners, schedulers, and other scripts.

3. Mandatory input and defaults express different obligations

[Parameter(Mandatory)] means the caller must provide a value. A default value means the script has a documented fallback when the caller does not provide one. These are design decisions, not merely syntax.

param(
    [Parameter(Mandatory, HelpMessage = 'Name the deployment environment.')]
    [string]$Environment,

    [int]$RetryCount = 3
)

[pscustomobject]@{ Environment = $Environment; RetryCount = $RetryCount }

In interactive sessions, PowerShell may prompt for a missing mandatory parameter. Do not design automation around that prompt. CI and scheduled tasks should pass all required values explicitly so they never block waiting for input.

4. Switches model on/off choices; arrays and types constrain shape

A [switch] parameter represents presence or absence: callers write -WhatIfLikeMode instead of inventing strings such as "yes". An array parameter accepts multiple values. A type annotation asks PowerShell to convert incoming values into the expected type before normal script logic proceeds.

param(
    [string[]]$Target,
    [switch]$DryRun,
    [int]$TimeoutSeconds = 30
)

[pscustomobject]@{
    TargetCount = @($Target).Count
    DryRun      = $DryRun.IsPresent
    Timeout     = $TimeoutSeconds
}

Typing is a boundary aid, not complete validation. [int] prevents arbitrary text from becoming a timeout integer, but it does not by itself say whether -10 seconds is acceptable. Lesson 3 adds validation attributes for those semantic rules.

5. Binding happens before most of your script body, so conversion errors are boundary errors

When PowerShell invokes the script, its parameter binder associates supplied arguments with parameters and attempts requested type conversions. A failure can occur before your first normal statement executes. That is desirable: invalid input is rejected close to the boundary instead of producing a confusing downstream failure.

# Inspect-Port.ps1
param(
    [int]$Port
)

"Accepted port value: $Port"

# This succeeds because '443' converts to Int32:
# ./Inspect-Port.ps1 -Port '443'

# This fails during binding/conversion:
# ./Inspect-Port.ps1 -Port 'https'

Read binding errors from the outside inward: identify the parameter name, expected type/validation rule, supplied value, and the conversion or rule that failed.

6. $PSBoundParameters tells you what the caller actually supplied

A defaulted parameter always has a value inside the script, so comparing its value with the default cannot reliably tell you whether the caller typed it. $PSBoundParameters is a dictionary containing parameters that were explicitly bound for the current invocation.

param(
    [string]$Region = 'eu-central',
    [int]$RetryCount = 3
)

$regionSource = if ($PSBoundParameters.ContainsKey('Region')) {
    'explicit parameter'
} else {
    'script default'
}

[pscustomobject]@{ Region = $Region; Source = $regionSource }

This distinction becomes valuable when you later implement configuration precedence: explicit command-line input should normally win over environment variables, config files, and defaults.

7. Read-Host is interaction, not a default automation interface

Read-Host is useful for intentionally interactive tools, but it blocks unattended runs. A reusable DevOps script should accept data through parameters, environment variables, files, pipeline input, or APIs according to a documented contract. If interaction is optional, make it an explicit mode rather than an invisible fallback.

# Fragile for CI:
# $Environment = Read-Host 'Environment'

# Automation-friendly:
param(
    [Parameter(Mandatory)]
    [string]$Environment
)
Avoid hidden prompts: A scheduled job that waits for input is not “slow”; it is stuck. Non-interactive behavior must be predictable.

8. Make a script discoverable like a small command-line tool

Even before Chapter 09 introduces advanced functions, a script can expose a coherent CLI: stable names, typed parameters, defaults, help messages, structured output, and documented exit behavior. Comment-based help can make Get-Help ./script.ps1 useful.

<#
.SYNOPSIS
Builds a deployment plan without changing remote systems.
.PARAMETER Environment
Names the environment to plan.
.PARAMETER Target
One or more target names.
.EXAMPLE
./New-DeploymentPlan.ps1 -Environment test -Target api-01,worker-01
#>
param(
    [Parameter(Mandatory)]
    [string]$Environment,

    [Parameter(Mandatory)]
    [string[]]$Target
)

$Target | ForEach-Object {
    [pscustomobject]@{ Environment = $Environment; Target = $_; Action = 'Plan' }
}

9. Lab: build a small CLI-style planning script

Create a temporary script that accepts an environment, target list, optional retry count, and a switch. It performs no deployment; it produces a plan that another command could export or test.

$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-params-lab'
New-Item -ItemType Directory -Path $root -Force | Out-Null
$script = Join-Path $root 'New-Plan.ps1'

@'
param(
    [Parameter(Mandatory)]
    [string]$Environment,

    [Parameter(Mandatory)]
    [string[]]$Target,

    [int]$RetryCount = 3,
    [switch]$Detailed
)

$Target | ForEach-Object {
    [pscustomobject]@{
        Environment = $Environment
        Target      = $_
        RetryCount  = $RetryCount
        Detailed    = $Detailed.IsPresent
        ExplicitRetry = $PSBoundParameters.ContainsKey('RetryCount')
    }
}
'@ | Set-Content -LiteralPath $script -Encoding utf8

& $script -Environment test -Target 'api 01','worker-02'
& $script -Environment prod -Target api-01 -RetryCount 5 -Detailed

Remove-Item -LiteralPath $root -Recurse -Force
  • First call should report ExplicitRetry = False.
  • Second call should report ExplicitRetry = True and Detailed = True.
  • Targets containing spaces remain distinct array elements.
  • No Read-Host prompt is required.

10. Parameter design mistakes that become maintenance problems

MistakeConsequenceBetter choice
Hard-code environment/targetEvery change requires editing sourceExpose a named parameter
Use many positional valuesCalls become difficult to read and fragile to reorderPrefer named parameters in automation
Use strings for every typeConversion/validation failures happen deep in logicUse appropriate types and validation
Cannot tell default from supplied valueConfiguration precedence becomes ambiguousCheck $PSBoundParameters
Prompt automatically with Read-HostCI/schedulers can hangRequire explicit input or a documented interactive mode

11. Knowledge check

Question 1. Why are parameters better than editing hard-coded variables before every run?

Question 2. What does a [switch] parameter represent?

Question 3. Why can a typed parameter fail before normal script statements run?

Question 4. How can a script know whether a value came from an explicit argument instead of its default?

Question 5. Why is Read-Host usually a poor default for CI automation?

12. Summary

Script parameters turn variable operational input into a visible interface. Prefer descriptive named parameters, distinguish mandatory values from sensible defaults, use [switch] for flags, use types to reject impossible shapes early, inspect $PSBoundParameters when precedence depends on whether the caller supplied a value, and design unattended scripts so they never require Read-Host by surprise.

13. Further reading

Next lesson

Reject invalid inputs before they enter operational logic

Continue to Lesson 3, where the chapter builds on this boundary with reject invalid inputs before they enter operational logic.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.