Skip to content

Readability#

String handling#

Practice: Single quotes for literals. Double quotes only for expansion. Here-strings for multi-line. -f operator for formatting.

Why: Single-quoted strings are literal — no accidental variable expansion. Choosing the correct quote style makes intent explicit at write time (Shift Left, Clean Code).

How:

# Good
$name     = 'John'
$greeting = "Hello, $name"
$path     = 'C:\Temp\file.txt'
$message  = 'The value is: {0}' -f $value
# Bad
$name     = "John"                         # No expansion needed — use single quotes
$greeting = 'Hello, $name'                 # Variable will not expand
$message  = 'The value is: ' + $value      # Use -f operator

Comparison operators#

Practice: Use PowerShell operators (-eq, -ne, -gt, -contains). Place $null on the left of null checks.

Why: C-style operators silently produce wrong results. $null -eq $collection is safe even when $collection is an array. Bugs surface at write time, not in production (Shift Left).

How:

# Good
if ($value -eq 10) { }
if ($list -contains $item) { }
if ($null -eq $collection) { }          # Safe for arrays
# Bad
if ($value == 10) { }                   # Not a PowerShell operator
if ($list -eq $item) { }               # Use -contains
if ($collection -eq $null) { }         # Unsafe on arrays

Splatting#

Practice: Use splatting when a call has more than two or three parameters.

Why: Long parameter lines are hard to diff and review (4-eyes principle). Splatting makes parameters easy to add or remove in isolation (Make change easy).

How:

# Good
$params = @{
    Path        = 'C:\Temp'
    Filter      = '*.txt'
    Recurse     = $true
    ErrorAction = 'Stop'
}
Get-ChildItem @params
# Bad
Get-ChildItem -Path 'C:\Temp' -Filter '*.txt' -Recurse $true -ErrorAction 'Stop'

Arrays and hashtables#

Practice: Use @() for arrays, @{} for hashtables. One item per line in multi-line collections. Align hashtable values.

Why: Explicit syntax makes intent clear (Clean Code). One item per line produces single-line diffs (Make change easy).

How:

# Good
$items = @(
    'First'
    'Second'
    'Third'
)

$config = @{
    Name = 'John'
    Age  = 30
}
# Bad
$items = 'First', 'Second', 'Third'
$config = @{Name = 'John'; Age = 30}

String emptiness#

Practice: Test string emptiness with -not $Param. Never use [string]::IsNullOrEmpty(...).

Why: -not on a string evaluates to $true for both $null and empty string, is idiomatic PowerShell, and reads naturally. The .NET method adds noise and is redundant in PowerShell context (Clean Code).

How:

# Good
if (-not $Name) {
    throw 'Name is required'
}
# Bad
if ([string]::IsNullOrEmpty($Name)) {
    throw 'Name is required'
}

Wildcard detection#

Practice: Use [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Value) to detect wildcards. Never use $Value.Contains('*').

Why: The .Contains('*') approach misses ?, [, and ` — all valid PowerShell wildcard characters. The built-in API handles the full wildcard grammar correctly (Determinism before intelligence).

How:

# Good
if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Path)) {
    # Handle wildcard path
}
# Bad
if ($Path.Contains('*')) {
    # Misses ?, [, and backtick wildcards
}

Cross-platform environment access#

Practice: Use [System.Environment]::ProcessorCount instead of $env:NUMBER_OF_PROCESSORS. Prefer .NET APIs over platform-specific environment variables.

Why: $env:NUMBER_OF_PROCESSORS is only available on Windows. The .NET API works reliably on all platforms (Build for all developers).

How:

# Good
$cpuCount = [System.Environment]::ProcessorCount
# Bad
$cpuCount = $env:NUMBER_OF_PROCESSORS   # Windows-only