Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/docs/Coding-Standards/Markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 15 additions & 1 deletion src/docs/Coding-Standards/PowerShell/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions src/docs/Coding-Standards/PowerShell/Scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
12 changes: 11 additions & 1 deletion src/docs/Coding-Standards/PowerShell/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@ This standard builds on the [language-agnostic baseline](../index.md); where the

<!-- INDEX:END -->

## 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:

- **`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.

Expand All @@ -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

Expand Down