PowerShell scripting
Build a script contract before writing code. Preserve structured objects, literal
data, mutation authority, and failure evidence across every PowerShell boundary.
Inspect first
Inspect repository instructions, existing scripts and tests, CI, module manifests,
#requires, and configuration before choosing syntax. Inspect the installed version
rather than assuming a documented version. When execution is available, record:
$PSVersionTable | Select-Object PSEdition, PSVersion, Platform, OS
Get-Module -ListAvailable Pester, PSScriptAnalyzer |
Sort-Object Name, Version -Descending |
Select-Object Name, Version, Path
Choose the runtime branch from evidence:
| Declared target |
Action |
| Supported PowerShell 7 only |
Use current PowerShell 7 semantics and test every claimed OS. |
| PowerShell 7 plus Windows PowerShell 5.1 |
Use syntax and APIs supported by both; execute tests in both runtimes on Windows. |
| Windows-only provider or command |
Isolate the platform branch, fail clearly elsewhere, and test on Windows. |
| Target unknown |
Preserve existing compatibility and ask for the matrix before introducing version-specific behavior. |
Read compatibility rules when a script crosses a
runtime or OS boundary.
Workflow
- Define parameters, pipeline input, success output, diagnostics, side effects,
accepted native exit codes, and cleanup behavior.
- Decide the runtime/OS matrix from repository evidence; do not infer portability
from a
.ps1 suffix.
- Use an advanced script or function when validation, pipeline processing, common
parameters, or
ShouldProcess behavior belongs to the contract.
- Keep values typed and structured through processing. Format or serialize only at
an explicit output boundary.
- Implement errors and native failures at their actual boundaries; preserve context
and clean up in
finally where ownership requires it.
- Parse, analyze, and execute the narrow tests, then run every claimed runtime and
platform lane.
Semantic rules
Parameters and pipeline
- Put
param(...) before executable script statements. Use [CmdletBinding()] when
the script needs common parameters, advanced validation, or SupportsShouldProcess.
- Use parameter sets only for genuinely incompatible invocation shapes. Make the
default set explicit when omission would be ambiguous.
- Use
begin for invocation setup, process for each pipeline item, end for final
aggregation, and clean only when the declared runtime supports it.
- Emit domain objects on the success stream.
Write-Host is presentation, not data;
return does not prevent earlier uncaptured success output from becoming output.
- Do not collect an unbounded pipeline merely for convenience. State ordering and
cardinality when they are part of the interface.
Read parameters, objects, and streams when the
task combines pipeline input with multiple output or diagnostic channels.
Paths and mutations
- Resolve paths relative to
$PSScriptRoot when they belong to the script, not the
caller's current directory.
- Use
-LiteralPath for caller-supplied literal paths. Accept wildcard semantics only
through a separately named and documented parameter.
- For deletion, overwrite, service/configuration change, or another consequential
mutation, use
SupportsShouldProcess and gate the operation with
$PSCmdlet.ShouldProcess(...).
- Validate the exact target before mutation. Reject empty, root, unresolved, or
scope-escaping targets rather than broadening them.
- Do not claim that
-WhatIf covered a nested command unless the script itself gates
that command.
Errors, security, and native processes
- Catch only errors the script can classify or enrich.
try/catch handles
terminating errors; use -ErrorAction Stop at a boundary only when converting its
non-terminating errors is part of the contract.
- Re-throw or create a terminating error when the script cannot produce its promised
result. Do not use an empty catch, broad success exit, or global preference change
to hide failure.
- Pass native executable arguments as separate values. Never use
Invoke-Expression
to turn data into source code, and never concatenate an untrusted command string.
- After a native invocation, evaluate its exit code independently of PowerShell's
error stream. Define accepted nonzero codes when the tool has them.
- Keep secrets out of source, logs, command lines, and serialized diagnostics. Use an
existing protected secret boundary; do not invent credential storage.
Read security and mutation boundaries and
native-command boundaries for these branches.
Evaluated recipes
Load only the matching recipe from
the evaluated script recipes:
powershell.safe-literal-removal: previewable deletion of bounded literal files.
powershell.pipeline-object-transform: one stable object per pipeline input.
powershell.native-argv-exit: separate native arguments and enforce exit status.
powershell.dual-runtime-guard: reject unsupported runtime/platform combinations.
powershell.parse-without-execution: return parser evidence without running code.
powershell.pester-ci-gate: run a pinned Pester suite and fail on test failures.
powershell.validated-json-ingestion: validate JSON against a schema before parsing.
powershell.bounded-rest-request: bound an idempotent REST request and expose status.
powershell.checksum-verified-download: stage, verify, and publish a download safely.
powershell.bounded-parallel-file-hash: throttle parallel work and restore input order.
Recipes are semantic anchors, not blind templates. Preserve their invariants and
adapt them only after inspecting the target contract.
Verification contract
Run the lowest-cost applicable checks in this order:
- Parse without executing the script and fail on every parser error.
- Run PSScriptAnalyzer with the repository's pinned version and settings; explain or
deliberately configure suppressions rather than hiding findings ad hoc.
- Run focused Pester tests for parameter binding, pipeline shape, empty/malformed
input, literal metacharacter paths,
-WhatIf, native failure, and cleanup.
- Run the relevant project suite in each declared PowerShell/runtime and OS lane.
- Exercise a preview or disposable fixture before any attended real mutation.
Analyzer compatibility rules are evidence about referenced syntax, commands, and
types; they do not prove runtime behavior. A macOS or Linux PowerShell 7 pass does not
prove Windows PowerShell 5.1 or Windows-provider behavior. See
the verification matrix.
Completion
Complete the task only when the declared interface and mutation scope match the
implementation, success output contains only promised data, failures reach the
correct boundary, deterministic tests cover the dangerous paths, and every claimed
runtime/platform lane has direct evidence. Report unavailable lanes and unexplained
analyzer findings instead of weakening the claim.
1---2name: powershell-scripting3description: Use when writing, reviewing, debugging, or testing PowerShell `.ps1` scripts and advanced functions, including parameters, pipeline objects, streams, native commands, filesystem safety, PowerShell 7 portability, Pester, and PSScriptAnalyzer. Do not use merely to run an existing script, author Bash or batch files, package a module or DSC resource, or perform live system or tenant administration.4---56# PowerShell scripting78Build a script contract before writing code. Preserve structured objects, literal9data, mutation authority, and failure evidence across every PowerShell boundary.1011## Inspect first1213Inspect repository instructions, existing scripts and tests, CI, module manifests,14`#requires`, and configuration before choosing syntax. Inspect the installed version15rather than assuming a documented version. When execution is available, record:1617```powershell18$PSVersionTable | Select-Object PSEdition, PSVersion, Platform, OS19Get-Module -ListAvailable Pester, PSScriptAnalyzer |20 Sort-Object Name, Version -Descending |21 Select-Object Name, Version, Path22```2324Choose the runtime branch from evidence:2526| Declared target | Action |27|---|---|28| Supported PowerShell 7 only | Use current PowerShell 7 semantics and test every claimed OS. |29| PowerShell 7 plus Windows PowerShell 5.1 | Use syntax and APIs supported by both; execute tests in both runtimes on Windows. |30| Windows-only provider or command | Isolate the platform branch, fail clearly elsewhere, and test on Windows. |31| Target unknown | Preserve existing compatibility and ask for the matrix before introducing version-specific behavior. |3233Read [compatibility rules](references/compatibility.md) when a script crosses a34runtime or OS boundary.3536## Workflow37381. Define parameters, pipeline input, success output, diagnostics, side effects,39 accepted native exit codes, and cleanup behavior.402. Decide the runtime/OS matrix from repository evidence; do not infer portability41 from a `.ps1` suffix.423. Use an advanced script or function when validation, pipeline processing, common43 parameters, or `ShouldProcess` behavior belongs to the contract.444. Keep values typed and structured through processing. Format or serialize only at45 an explicit output boundary.465. Implement errors and native failures at their actual boundaries; preserve context47 and clean up in `finally` where ownership requires it.486. Parse, analyze, and execute the narrow tests, then run every claimed runtime and49 platform lane.5051## Semantic rules5253### Parameters and pipeline5455- Put `param(...)` before executable script statements. Use `[CmdletBinding()]` when56 the script needs common parameters, advanced validation, or `SupportsShouldProcess`.57- Use parameter sets only for genuinely incompatible invocation shapes. Make the58 default set explicit when omission would be ambiguous.59- Use `begin` for invocation setup, `process` for each pipeline item, `end` for final60 aggregation, and `clean` only when the declared runtime supports it.61- Emit domain objects on the success stream. `Write-Host` is presentation, not data;62 `return` does not prevent earlier uncaptured success output from becoming output.63- Do not collect an unbounded pipeline merely for convenience. State ordering and64 cardinality when they are part of the interface.6566Read [parameters, objects, and streams](references/execution-semantics.md) when the67task combines pipeline input with multiple output or diagnostic channels.6869### Paths and mutations7071- Resolve paths relative to `$PSScriptRoot` when they belong to the script, not the72 caller's current directory.73- Use `-LiteralPath` for caller-supplied literal paths. Accept wildcard semantics only74 through a separately named and documented parameter.75- For deletion, overwrite, service/configuration change, or another consequential76 mutation, use `SupportsShouldProcess` and gate the operation with77 `$PSCmdlet.ShouldProcess(...)`.78- Validate the exact target before mutation. Reject empty, root, unresolved, or79 scope-escaping targets rather than broadening them.80- Do not claim that `-WhatIf` covered a nested command unless the script itself gates81 that command.8283### Errors, security, and native processes8485- Catch only errors the script can classify or enrich. `try`/`catch` handles86 terminating errors; use `-ErrorAction Stop` at a boundary only when converting its87 non-terminating errors is part of the contract.88- Re-throw or create a terminating error when the script cannot produce its promised89 result. Do not use an empty catch, broad success exit, or global preference change90 to hide failure.91- Pass native executable arguments as separate values. Never use `Invoke-Expression`92 to turn data into source code, and never concatenate an untrusted command string.93- After a native invocation, evaluate its exit code independently of PowerShell's94 error stream. Define accepted nonzero codes when the tool has them.95- Keep secrets out of source, logs, command lines, and serialized diagnostics. Use an96 existing protected secret boundary; do not invent credential storage.9798Read [security and mutation boundaries](references/security-safety.md) and99[native-command boundaries](references/native-commands.md) for these branches.100101## Evaluated recipes102103Load only the matching recipe from104[the evaluated script recipes](references/recipes-core.md):105106- `powershell.safe-literal-removal`: previewable deletion of bounded literal files.107- `powershell.pipeline-object-transform`: one stable object per pipeline input.108- `powershell.native-argv-exit`: separate native arguments and enforce exit status.109- `powershell.dual-runtime-guard`: reject unsupported runtime/platform combinations.110- `powershell.parse-without-execution`: return parser evidence without running code.111- `powershell.pester-ci-gate`: run a pinned Pester suite and fail on test failures.112- `powershell.validated-json-ingestion`: validate JSON against a schema before parsing.113- `powershell.bounded-rest-request`: bound an idempotent REST request and expose status.114- `powershell.checksum-verified-download`: stage, verify, and publish a download safely.115- `powershell.bounded-parallel-file-hash`: throttle parallel work and restore input order.116117Recipes are semantic anchors, not blind templates. Preserve their invariants and118adapt them only after inspecting the target contract.119120## Verification contract121122Run the lowest-cost applicable checks in this order:1231241. Parse without executing the script and fail on every parser error.1252. Run PSScriptAnalyzer with the repository's pinned version and settings; explain or126 deliberately configure suppressions rather than hiding findings ad hoc.1273. Run focused Pester tests for parameter binding, pipeline shape, empty/malformed128 input, literal metacharacter paths, `-WhatIf`, native failure, and cleanup.1294. Run the relevant project suite in each declared PowerShell/runtime and OS lane.1305. Exercise a preview or disposable fixture before any attended real mutation.131132Analyzer compatibility rules are evidence about referenced syntax, commands, and133types; they do not prove runtime behavior. A macOS or Linux PowerShell 7 pass does not134prove Windows PowerShell 5.1 or Windows-provider behavior. See135[the verification matrix](references/verification.md).136137## Completion138139Complete the task only when the declared interface and mutation scope match the140implementation, success output contains only promised data, failures reach the141correct boundary, deterministic tests cover the dangerous paths, and every claimed142runtime/platform lane has direct evidence. Report unavailable lanes and unexplained143analyzer findings instead of weakening the claim.