Scripts#
A script (.ps1) is an entry point, not a home for logic. Keep scripts thin: parse input, call functions, report results. Anything reusable belongs in a function in a module.
Section structure#
A script file is laid out top to bottom in this order:
#Requiresstatements — PowerShell version and module dependencies. Prefer a minimum version (ModuleVersion) so security patches and fixes flow in. When a new major of a dependency would break you — a test framework such as Pester is the typical case — bound it to that major by addingMaximumVersion(e.g.ModuleVersion = '6.0.0'; MaximumVersion = '6.*'). Avoid exactRequiredVersionpins, which freeze out patches and go stale. Pinning module identity with aGUIDis a separate, stricter supply-chain control — reach for it only when you need identity assurance, not as part of the default version lock.- Comment-based help — the same sections and order as a function's, only without the enclosing
functionblock:.SYNOPSIS,.DESCRIPTION, at least one.EXAMPLE, then.INPUTS,.OUTPUTS,.NOTES, and.LINKas they apply. Document each parameter with an inline comment above it, just as a function does. [CmdletBinding()]+param()— typed and validated, mandatory first; addSupportsShouldProcesswhen the script changes state.$ErrorActionPreference = 'Stop'.- Body — the thin orchestration.
#Requires -Version 7.0
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' }
<#
.SYNOPSIS
Rotate the automation secret and sync it to the target store.
.DESCRIPTION
Remove the current secret, create a replacement, and update the store that consumes it.
.EXAMPLE
./Rotate-Secret.ps1 -ValidityDays 365
.INPUTS
None
You can't pipe objects to Rotate-Secret.ps1.
.OUTPUTS
None
This script produces no output — it operates by side effect.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
# How long the new secret stays valid, in days.
[Parameter()]
[int] $ValidityDays = 180
)
$ErrorActionPreference = 'Stop'
# thin orchestration: call the functions that do the real work
Rules#
- Name scripts
Verb-Noun.ps1to match the function convention. - Name a helper module bundled inside a GitHub Action after the action (
action-name.Helpers), never a generic name likeHelpers— a generic name collides with shared or sibling modules in the runner's shared session. See GitHub Actions. - No side effects on load. A script runs top to bottom when invoked; it should not do work merely by being dot-sourced.
- Return objects, so the script composes in a pipeline like any other command.
Paths#
- Do not depend on the current directory. Avoid relative paths and
~— the meaning of~depends on the current PowerShell provider — and build paths from$PSScriptRootwithJoin-Path. - Pass full paths to .NET and native calls. .NET methods and external executables resolve relative paths against
[System.Environment]::CurrentDirectory, which PowerShell does not keep reliably in step with$PWD— it can lagSet-Location, and diverges in non-FileSystem providers (Registry,Cert:). Resolve to a full path first.