diff --git a/src/docs/Coding-Standards/Markdown.md b/src/docs/Coding-Standards/Markdown.md index c3c313e..6f4c041 100644 --- a/src/docs/Coding-Standards/Markdown.md +++ b/src/docs/Coding-Standards/Markdown.md @@ -54,3 +54,12 @@ These rules are disabled or widened so they do not flag valid documentation — - **Surround headings, lists, and fenced blocks with a blank line** for readability, even though the linter no longer enforces it. - **Prefer relative links** within a repository; use the canonical published URL for cross-repository references. - **Tag every code fence with a language** (` ```bash `, ` ```yaml `) so it is highlighted and converts cleanly when published. + +## PowerShell code samples + +Documentation is full of PowerShell, so present it the way the [PowerShell standard](PowerShell/index.md) writes it: + +- **Label the fence `powershell`**, and put command output in a separate block labelled `Output`, so it is neither syntax-highlighted as a command nor mistaken for input. +- **Use full cmdlet and parameter names**, and avoid positional parameters, so a reader can copy the sample and run it. +- **Avoid backtick line-continuation.** Break a long call with splatting, or at PowerShell's natural points — after a pipe, an opening parenthesis, or a brace. +- **Leave out the prompt string** (`PS>`) unless the sample is specifically about interactive, prompt-changing behaviour. diff --git a/src/docs/Coding-Standards/PowerShell/Functions.md b/src/docs/Coding-Standards/PowerShell/Functions.md index 19b08a0..d6f51cd 100644 --- a/src/docs/Coding-Standards/PowerShell/Functions.md +++ b/src/docs/Coding-Standards/PowerShell/Functions.md @@ -66,7 +66,21 @@ function Get-UserData { ## Errors and output - **`throw` for terminating errors**; `Write-Error` only where the caller is expected to handle a non-terminating one. -- **Emit one object type**, matching `[OutputType()]`; never `Write-Host` data another command might consume. +- **Call cmdlets you mean to trap with `-ErrorAction Stop`** so they raise terminating, catchable errors. Native commands report failure through `$LASTEXITCODE`, not the error stream, so check it and `throw` yourself — or set `$PSNativeCommandUseErrorActionPreference = $true` on PowerShell 7.4+ so their non-zero exits honour `$ErrorActionPreference` too. +- **Put the whole transaction in the `try` block** rather than setting success flags to gate later code, and do not lean on `$?` — it reports only whether the last command considered itself successful, with no detail. +- **In a `catch`, copy `$_` into your own variable first**, before later commands overwrite it. The baseline rules — fail fast, never swallow — live in [Error Handling](../Error-Handling.md). + +## Output streams + +Send each kind of message to the stream built for it, so a caller can capture, redirect, or silence it: + +- **Results** are objects on the output stream — emit them implicitly by naming the object on its own line; do **not** use `return $obj` to emit, and in a pipeline function emit from `process`, not `end`. +- **Emit one object type**, matching `[OutputType()]`. +- **`Write-Verbose`** for status a caller may want (`-Verbose`), **`Write-Debug`** for maintainer breadcrumbs (`-Debug`), and **`Write-Progress`** for progress that need not persist. +- **`Write-Warning`** and **`Write-Error`** for warnings and non-terminating errors. +- **`Write-Host`** only for `Show-` or `Format-` verbs or an interactive prompt — never for data another command might consume. + +`[CmdletBinding()]` is what turns on the `-Verbose` and `-Debug` switches, so those streams reach the caller. ## Comment-based help (required) diff --git a/src/docs/Coding-Standards/PowerShell/Scripts.md b/src/docs/Coding-Standards/PowerShell/Scripts.md index ffcbe53..7eec938 100644 --- a/src/docs/Coding-Standards/PowerShell/Scripts.md +++ b/src/docs/Coding-Standards/PowerShell/Scripts.md @@ -48,3 +48,8 @@ $ErrorActionPreference = 'Stop' - **Name scripts `Verb-Noun.ps1`** to match the function convention. - **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 `$PSScriptRoot` with `Join-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 lag `Set-Location`, and diverges in non-FileSystem providers (`Registry`, `Cert:`). Resolve to a full path first. diff --git a/src/docs/Coding-Standards/PowerShell/index.md b/src/docs/Coding-Standards/PowerShell/index.md index 2dc4480..70318df 100644 --- a/src/docs/Coding-Standards/PowerShell/index.md +++ b/src/docs/Coding-Standards/PowerShell/index.md @@ -19,6 +19,15 @@ This standard builds on the [language-agnostic baseline](../index.md); where the +## Tools and controllers + +PowerShell falls into two kinds, and the difference decides how a command shapes its output: + +- A **tool** is a reusable unit — an advanced function, usually exported from a module. It takes input only through parameters and emits **raw, least-manipulated objects**, so it stays usable in situations its author never imagined; a tool that measures a size returns bytes, not a rounded string. +- A **controller** is a script that automates one process by calling tools. It may reshape, round, or format data for how it will be read, and it is not meant to be reused. + +Keep the shaping at the edge: tools stay general and emit raw objects, and a controller — or a format view (`.format.ps1xml`) — turns those into presentation. This is the [thin script](Scripts.md) rule seen from the other side, and it is why tools [emit objects, not text](#shared-conventions). + ## Shared conventions These hold for all PowerShell, whatever the construct: @@ -26,6 +35,7 @@ These hold for all PowerShell, whatever the construct: - **`Verb-Noun` naming** with an approved verb (`Get-Verb`) and a singular noun: `Get-RepositorySecret`, not `Fetch-Secrets`. - **`PascalCase`** for functions, parameters, public variables, and class members; `camelCase` for local variables. - **Full cmdlet names, never aliases** (`Where-Object`, not `?`; `ForEach-Object`, not `%`). +- **Full parameter names, and standard ones.** Pass parameters by name and avoid positional arguments in shared code — `Get-Process -Name pwsh`, not `Get-Process pwsh` — so a call survives parameter-set changes and reads clearly. Name your own parameters after PowerShell's built-ins (`Path`, `Name`, `ComputerName`), not `$Param_Computer`. - **Set `$ErrorActionPreference = 'Stop'`** at the top of every script and module so errors are terminating, not silently swallowed. - **Emit objects, not formatted text.** Return rich objects and let the caller format; reserve `Write-Host` for genuine console UX, and use `Write-Verbose` / `Write-Information` for progress narration. @@ -49,7 +59,7 @@ Beyond the basics, these language-specific habits keep PowerShell correct and fa - **Put `$null` on the left of a comparison** — `$null -eq $x`, never `$x -eq $null`. Against a collection the right-hand form *filters* rather than tests. Use `-contains` / `-in` for membership, never `-eq`. - **Suppress unwanted output with `$null = ...`** (or `[void]` for method calls), not `| Out-Null` — the pipeline form is markedly slower on hot paths. - **Build collections with a typed list, not `+=` in a loop.** `$a += $x` reallocates the whole array every iteration; use `[System.Collections.Generic.List[T]]` with `.Add()`, and prefer a cmdlet's `-Filter` over piping to `Where-Object` on large sets. -- **Keep secrets out of source, and never `Invoke-Expression` untrusted input.** Take secrets as `[securestring]` or through `Get-Credential`, and guard state-changing commands with `ShouldProcess` (see [Functions](Functions.md)); the wider rules live in the [Security](../Security.md) baseline. +- **Keep secrets out of source, and never `Invoke-Expression` untrusted input.** Accept credentials as a `[PSCredential]` parameter with the `[Credential()]` attribute rather than calling `Get-Credential` inside a reusable function, so a caller can pass one they already hold, and take other sensitive values as `[securestring]`. Guard state-changing commands with `ShouldProcess` (see [Functions](Functions.md)); the wider rules live in the [Security](../Security.md) baseline. ## Toolchain