TB

MoppleIT Tech Blog

Welcome to my personal blog where I share thoughts, ideas, and experiences.

Catch Bugs Early with Set-StrictMode in PowerShell: Fail Fast, Fix Faster

You can eliminate a surprising class of production bugs in PowerShell by adopting two disciplined defaults at the very top of every script and module: Set-StrictMode -Version Latest and $ErrorActionPreference = 'Stop'. Together they make hidden problems fail fast, push issues into your try/catch blocks predictably, and produce clearer error messages during development, CI, and operations. In this guide, youll learn what StrictMode does, the kinds of bugs it catches, and how to integrate it into real-world projects without sacrificing flexibility.

Start Every Script with Strict Defaults

Put these two lines at the very top of your script or module:

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Why these lines?

  • Set-StrictMode -Version Latest turns common footguns into hard failures. It treats uninitialized variables, invalid or misspelled member names, and several other subtle mistakes as terminating errors instead of silent no-ops.
  • $ErrorActionPreference = 'Stop' promotes non-terminating errors (the default for many cmdlets) into terminating errors, so your try/catch behaves consistently and you can manage failures centrally.

Together, these enforce a fail-fast posture: errors appear at the exact point of failure and try/catch flows the way you expect.

Quick example

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

try {
  # Undefined variable triggers a strict error
  $total = $price * 1.25
} catch {
  Write-Warning ("StrictMode caught: {0}" -f $_.Exception.Message)
  $price = 100
  $total = $price * 1.25
}
Write-Host ("Total: {0}" -f $total)

Without StrictMode, $price would be $null and the failure might be delayed or misattributed. With StrictMode, you immediately learn that $price was never set, and your catch block can recover intentionally.

What StrictMode Actually Catches

StrictMode focuses on turning quiet mistakes into explicit errors. Here are the biggest wins youll notice immediately.

1) Uninitialized variables

Referencing a variable that hasnt been set throws instead of returning $null or producing unexpected behavior.

Set-StrictMode -Version Latest

# Typo or missing assignment
$total = $totla + 1  # Throws: variable 'totla' cannot be retrieved because it has not been set

This alone catches a large class of typos and scope mistakes.

2) Misspelled or invalid member names

Accessing a property or method that doesnt exist becomes an error, which is critical when dealing with dynamic data or refactors.

Set-StrictMode -Version Latest

$obj = [pscustomobject]@{ Name = 'App'; Version = '1.0.0' }
# Misspelled property
$obj.Nmae  # Throws: PropertyNotFoundException under StrictMode

By default, PowerShell returns $null for a missing property, and the bug may not surface until much later. StrictMode stops it at the source.

3) Cleaner function calls and parameter handling

StrictMode helps surface issues like missing required values or bad member access inside functions, which often show up as odd behavior rather than immediate errors.

Set-StrictMode -Version Latest

function Get-PriceWithTax {
  param(
    [Parameter(Mandatory)] [double] $Price,
    [double] $Rate = 0.25
  )

  # Any typo in member names or uninitialized values throws immediately
  return [math]::Round($Price * (1 + $Rate), 2)
}

try {
  Get-PriceWithTax -Price $undefined  # Fails fast in strict mode
} catch {
  Write-Warning $_
}

Pair StrictMode with 'Stop' for Predictable Flow

Many cmdlets emit non-terminating errors by default (they write to the error stream but continue running). This makes try/catch unreliable unless you explicitly force stop-on-error behavior. Setting $ErrorActionPreference = 'Stop' ensures that errors consistently route into your catch blocks.

Example: turn flaky loops into reliable error handling

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$servers = 'api01', 'api02', 'api-bad'
foreach ($s in $servers) {
  try {
    Test-Connection -ComputerName $s -Count 1 | Out-Null
    Write-Host "OK: $s"
  } catch {
    Write-Warning ("Cannot reach {0}: {1}" -f $s, $_.Exception.Message)
    # Optional: break, retry, or record telemetry
  }
}

Now an unreachable host is a normal, catchable error rather than a subtle warning that continues execution unintentionally.

Patterns for Real Projects

1) Modules: enforce strictness inside the module scope

Place StrictMode and $ErrorActionPreference at the top of your module (.psm1) so all exported/internal functions inherit the behavior.

# MyModule.psm1
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Get-Config {
  param([string] $Path)
  if (-not (Test-Path -LiteralPath $Path)) {
    throw "Config file not found: $Path"
  }
  # StrictMode will catch typos in the parsed objects members later
  return Get-Content -Raw -LiteralPath $Path | ConvertFrom-Json
}

Because StrictMode applies to the current scope and its child scopes, placing it in the module ensures consistent behavior across all functions without relying on consumers to set it themselves.

2) CI/CD: fail fast in pipelines

Run your build, lint, and test steps in a strict session to catch errors before merging. For GitHub Actions:

name: PowerShell CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests under StrictMode
        shell: pwsh
        run: |
          Set-StrictMode -Version Latest
          $ErrorActionPreference = 'Stop'
          ./build.ps1
          ./tests.ps1

That single block saves hours of debugging by ensuring non-terminating errors and silent typos fail the build.

3) Pester: assert strict behavior in tests

Add StrictMode to your test session before importing your module or running scripts.

# tests.ps1
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module ./MyModule.psd1 -Force

Describe 'Get-Config' {
  It 'throws when file is missing' {
    { Get-Config -Path './nope.json' } | Should -Throw
  }
}

4) Scripts that touch external APIs

Dynamic payloads are where StrictMode shines, because misspelled property names are common. Combine property existence checks with StrictMode for safe access:

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$response = Invoke-RestMethod https://api.example.com/app

# Validate before use to avoid PropertyNotFound
if ($response.PSObject.Properties.Match('version')) {
  $version = $response.version
} else {
  throw 'API response missing required property: version'
}

Note: the null-conditional operator ?. helps with null objects, but it wont protect you from missing members. Explicit checks do.

Practical Tips and Recipes

Tip 1: Keep StrictMode at the highest level possible

Set it at the top of the script or module, not just inside a single function. That way, any bug that appears during initialization or parameter parsing also fails fast.

Tip 2: Temporarily relax in narrow scopes if you must

When working with truly dynamic objects (e.g., user-supplied JSON with optional fields), you can temporarily disable StrictMode in a small, audited scope:

function TryGet-Property {
  param($Object, [string] $Name)

  $prev = $ExecutionContext.SessionState.LanguageMode
  try {
    # Relax only what you need
    Set-StrictMode -Off
    return ($Object.PSObject.Properties[$Name]).Value
  } finally {
    Set-StrictMode -Version Latest
  }
}

Prefer validating input and using explicit property checks instead, but this pattern can help bridge third-party edge cases. Keep the relaxed scope minimal and restore StrictMode immediately.

Tip 3: Treat all warnings as blockers in CI

Combine $WarningPreference = 'Stop' with StrictMode during builds to catch deprecations and misconfigurations earlier:

$ErrorActionPreference   = 'Stop'
$WarningPreference       = 'Stop'
$InformationPreference   = 'Continue'  # keep info visible
Set-StrictMode -Version Latest

Tip 4: Validate parameters aggressively

Parameter validation pairs well with StrictMode to catch bad inputs before any business logic runs:

function New-User {
  param(
    [Parameter(Mandatory)][ValidatePattern('^[a-z0-9_-]{3,32}$')] [string] $UserName,
    [Parameter(Mandatory)][ValidateSet('admin','reader','operator')] [string] $Role
  )
  # ...
}

# Invalid role or username fails immediately with a clear message

Tip 5: Keep output types predictable

When StrictMode surfaces a member issue, consider returning PSCustomObjects with stable schemas. It helps downstream consumers rely on members confidently and reduces missing-member bugs.

Performance, Security, and Maintainability Benefits

  • Fewer bugs: Typos and missing members are caught at the line of failure, not downstream.
  • Clearer errors: Exceptions tell you exactly what is wrong, which makes code reviews and incident response faster.
  • Predictable control flow: With 'Stop', non-terminating errors no longer leak through loops or pipelines.
  • Better refactors: Renaming properties or parameters reveals call sites you missed.
  • Safer automation: Failing fast prevents partial, inconsistent changes in deployment and maintenance scripts.

End-to-End Example

This example demonstrates a disciplined script layout, safe property access, and reliable error handling:

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

param(
  [Parameter(Mandatory)] [string] $ConfigPath
)

function Get-RequiredProperty {
  param($Object, [string] $Name)
  if (-not $Object.PSObject.Properties.Match($Name)) {
    throw "Missing required property '$Name'"
  }
  return $Object.$Name
}

try {
  if (-not (Test-Path -LiteralPath $ConfigPath)) {
    throw "File not found: $ConfigPath"
  }

  $cfg = Get-Content -Raw -LiteralPath $ConfigPath | ConvertFrom-Json
  $appName   = Get-RequiredProperty $cfg 'name'
  $replicas  = Get-RequiredProperty $cfg 'replicas'
  $image     = Get-RequiredProperty $cfg 'image'

  # Safe use; any typo would have thrown above
  Write-Host ("Deploying {0} x{1} using image {2}" -f $appName, $replicas, $image)
  # Invoke deployment cmdlets here; any non-terminating error will be caught
}
catch {
  Write-Error ("Deployment failed: {0}" -f $_.Exception.Message)
  exit 1
}

Adopt Disciplined Defaults Today

Adding two lines at the top of your scripts and modules yields fewer bugs, clearer errors, and faster fixes:

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Try it in your next script, wire it into CI, and measure the reduction in late-stage fixes. Youll quickly wonder how you shipped anything without it.

Build stronger scripts with disciplined defaults. Explore the PowerShell Advanced CookBook 6e14aa e https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/

#PowerShell #StrictMode #Scripting #BestPractices #Debugging #PowerShellCookbook

← All Posts Home →