Skip to content

Commit cc8e543

Browse files
Adopt community PowerShell standards from PoshCode and the Microsoft style guide (#9)
## Summary Adopts seven PowerShell and Markdown conventions our standards were missing, found by comparing our Coding Standards against the community references. The pages it edits were introduced by #1, now merged to `main`; this branch has been rebased onto `main` so it applies cleanly. **Sources analyzed:** PoshCode *PowerShell Practice and Style*; the Microsoft PowerShell-Docs style guide; and dsccommunity.org (a Hugo website with a git workflow that mirrors ours and no reusable coding standards — nothing adopted from it). ## Changes - **Tools vs. controllers, and raw output** (`Coding-Standards/PowerShell/index.md`) — Closes #2 - **Output streams, and implicit output over `return`** (`Coding-Standards/PowerShell/Functions.md`) — Closes #3 - **PowerShell error-handling idioms** — `-ErrorAction Stop`, transaction-in-`try`, avoid `$?`, copy `$_` in `catch` (`Coding-Standards/PowerShell/Functions.md`) — Closes #4 - **`[PSCredential]` parameters for secrets** (`Coding-Standards/PowerShell/index.md`) — Closes #5 - **Full parameter names, no positional arguments, standard names** (`Coding-Standards/PowerShell/index.md`) — Closes #6 - **Path handling** — avoid `~` and relative paths, use `$PSScriptRoot`, the `[Environment]::CurrentDirectory` pitfall (`Coding-Standards/PowerShell/Scripts.md`) — Closes #7 - **PowerShell code samples in docs** — `powershell` fence, `Output` block, no backtick continuation (`Coding-Standards/Markdown.md`) — Closes #8 ## Validation textlint (terminology), the documentation link checker, the index-drift check, and the DNB-spillage scan all clean. Now that the base is `main`, `Docs.yml` (lint, link validation, build) runs on this PR.
1 parent 7a87e7e commit cc8e543

4 files changed

Lines changed: 40 additions & 2 deletions

File tree

src/docs/Coding-Standards/Markdown.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,12 @@ These rules are disabled or widened so they do not flag valid documentation —
5454
- **Surround headings, lists, and fenced blocks with a blank line** for readability, even though the linter no longer enforces it.
5555
- **Prefer relative links** within a repository; use the canonical published URL for cross-repository references.
5656
- **Tag every code fence with a language** (` ```bash `, ` ```yaml `) so it is highlighted and converts cleanly when published.
57+
58+
## PowerShell code samples
59+
60+
Documentation is full of PowerShell, so present it the way the [PowerShell standard](PowerShell/index.md) writes it:
61+
62+
- **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.
63+
- **Use full cmdlet and parameter names**, and avoid positional parameters, so a reader can copy the sample and run it.
64+
- **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.
65+
- **Leave out the prompt string** (`PS>`) unless the sample is specifically about interactive, prompt-changing behaviour.

src/docs/Coding-Standards/PowerShell/Functions.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,21 @@ function Get-UserData {
6666
## Errors and output
6767

6868
- **`throw` for terminating errors**; `Write-Error` only where the caller is expected to handle a non-terminating one.
69-
- **Emit one object type**, matching `[OutputType()]`; never `Write-Host` data another command might consume.
69+
- **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.
70+
- **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.
71+
- **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).
72+
73+
## Output streams
74+
75+
Send each kind of message to the stream built for it, so a caller can capture, redirect, or silence it:
76+
77+
- **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`.
78+
- **Emit one object type**, matching `[OutputType()]`.
79+
- **`Write-Verbose`** for status a caller may want (`-Verbose`), **`Write-Debug`** for maintainer breadcrumbs (`-Debug`), and **`Write-Progress`** for progress that need not persist.
80+
- **`Write-Warning`** and **`Write-Error`** for warnings and non-terminating errors.
81+
- **`Write-Host`** only for `Show-` or `Format-` verbs or an interactive prompt — never for data another command might consume.
82+
83+
`[CmdletBinding()]` is what turns on the `-Verbose` and `-Debug` switches, so those streams reach the caller.
7084

7185
## Comment-based help (required)
7286

src/docs/Coding-Standards/PowerShell/Scripts.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ $ErrorActionPreference = 'Stop'
4848
- **Name scripts `Verb-Noun.ps1`** to match the function convention.
4949
- **No side effects on load.** A script runs top to bottom when invoked; it should not do work merely by being dot-sourced.
5050
- **Return objects**, so the script composes in a pipeline like any other command.
51+
52+
## Paths
53+
54+
- **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`.
55+
- **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.

src/docs/Coding-Standards/PowerShell/index.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,23 @@ This standard builds on the [language-agnostic baseline](../index.md); where the
1919

2020
<!-- INDEX:END -->
2121

22+
## Tools and controllers
23+
24+
PowerShell falls into two kinds, and the difference decides how a command shapes its output:
25+
26+
- 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.
27+
- 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.
28+
29+
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).
30+
2231
## Shared conventions
2332

2433
These hold for all PowerShell, whatever the construct:
2534

2635
- **`Verb-Noun` naming** with an approved verb (`Get-Verb`) and a singular noun: `Get-RepositorySecret`, not `Fetch-Secrets`.
2736
- **`PascalCase`** for functions, parameters, public variables, and class members; `camelCase` for local variables.
2837
- **Full cmdlet names, never aliases** (`Where-Object`, not `?`; `ForEach-Object`, not `%`).
38+
- **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`.
2939
- **Set `$ErrorActionPreference = 'Stop'`** at the top of every script and module so errors are terminating, not silently swallowed.
3040
- **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.
3141

@@ -49,7 +59,7 @@ Beyond the basics, these language-specific habits keep PowerShell correct and fa
4959
- **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`.
5060
- **Suppress unwanted output with `$null = ...`** (or `[void]` for method calls), not `| Out-Null` — the pipeline form is markedly slower on hot paths.
5161
- **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.
52-
- **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.
62+
- **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.
5363

5464
## Toolchain
5565

0 commit comments

Comments
 (0)