TB

MoppleIT Tech Blog

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

Make Prerequisites Explicit with the PowerShell #requires Directive: Fail Fast, Ship Safer

When scripts silently assume the right engine, modules, or privileges, you end up debugging flaky pipelines and noisy logs. PowerShell gives you a built-in contract: the #requires directive. Put your prerequisites at the top of the file and the engine will validate them before any code runs. You get explicit requirements, early exits, safer deployments, and cleaner logs—without writing a single if-statement.

Why #requires beats ad-hoc checks

Checking versions and modules with manual if statements is common, but it still executes code before failing and can miss edge cases. The #requires directive lets the PowerShell engine do the gating before your script even begins.

  • Fail fast: If the environment is unsupported, PowerShell stops before running any logic. No side effects.
  • Explicit contract: Requirements are visible on the first lines. Future you (and your teammates) won’t guess what’s needed.
  • Reproducible pipelines: CI/CD jobs and containers either meet requirements or exit early—no half-configured machines.
  • Secure by default: Only demand elevation when it’s truly required, and let the engine enforce it with -RunAsAdministrator.
  • Cleaner logs: Fewer transient errors; failures point to unmet prerequisites, not downstream crashes.

Declare version, edition, modules, and elevation

Place #requires lines at the very top of your script or module file, before any code that executes (comments and blank lines may precede them). Here’s a practical header that enforces engine, edition, modules, and elevation:

#requires -Version 7.2
#requires -PSEdition Core
#requires -Modules PSReadLine, Pester
#requires -RunAsAdministrator

$ErrorActionPreference = 'Stop'
Write-Host ("OK: {0} {1}" -f $PSVersionTable.PSEdition, $PSVersionTable.PSVersion)

-Version: pin the minimum engine

#requires -Version 7.2 ensures the script runs only on PowerShell 7.2 or later. This is essential when you rely on newer language features, APIs, or performance improvements. It helps you avoid subtle regressions on older hosts (for example, Windows PowerShell 5.1).

When a mismatch occurs, the engine halts early with a clear message similar to:

The script 'setup.ps1' cannot be run because it contains a "#requires" statement for PowerShell version 7.2. The current version is 5.1.XXXX.XXXX.

-PSEdition: target Core vs. Desktop

PowerShell has two common editions:

  • Core (cross-platform, pwsh)
  • Desktop (Windows PowerShell 5.1, powershell.exe)

#requires -PSEdition Core prevents the script from running on Windows PowerShell, which is useful if you depend on .NET 6+, cross-platform behavior, or PS7-only features. If you need WinPS-specific behaviors, use Desktop instead. For containers and modern CI, Core is the norm.

-Modules: load and verify dependencies

Use -Modules to both load and enforce presence of required modules before any logic runs. If a module isn’t available, the script fails cleanly.

# Simple names
#requires -Modules PSReadLine, Pester

# Versioned specifications (PowerShell 5.1+)
#requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.5.0' }, @{ ModuleName = 'Az.Accounts'; ModuleVersion = '2.12.0' }
  • RequiredVersion: pin an exact version.
  • ModuleVersion: specify a minimum version.

Relying on #requires -Modules saves you from manual Import-Module checks and ensures the module auto-import happens before your script body runs.

-RunAsAdministrator: demand elevation only when it’s mandatory

#requires -RunAsAdministrator halts execution unless the script is running elevated (Administrator on Windows, root on Linux/macOS). Use it when your script modifies machine state (e.g., installing software, editing system files, configuring services).

If only some operations require elevation, consider splitting your logic into two scripts: a non-elevated driver and an elevated worker script with its own #requires -RunAsAdministrator. That way, you keep principle of least privilege and still benefit from early validation.

Ship it: templates, CI, and real-world tips

A sensible starter template

Start every script with a concise set of requirements and keep the rest focused on logic:

#requires -Version 7.2
#requires -PSEdition Core
#requires -Modules PSReadLine, Pester
#requires -RunAsAdministrator

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

function Invoke-Main {
    Write-Host ('OK: {0} {1}' -f $PSVersionTable.PSEdition, $PSVersionTable.PSVersion)
    # Your script logic begins here.
    # Example: Validate inputs, run idempotent actions, emit clear logs.
}

try {
    Invoke-Main
} catch {
    Write-Error $_
    exit 1
}
  • Strict mode helps catch undefined variables and common mistakes early.
  • Idempotent logic and clear logs make reruns safe and observability straightforward.

CI/CD: enforce prerequisites in pipelines

In CI, use the same script headers to guarantee the right engine and modules. If the runner isn’t compliant, the job fails immediately—no partial setup.

Example: test against both Windows PowerShell and PowerShell 7 to make the contract explicit:

name: powershell-requires

on: [push]

jobs:
  test:
    runs-on: windows-latest
    strategy:
      matrix:
        shell: [ 'pwsh', 'powershell' ]
    steps:
      - uses: actions/checkout@v4
      - name: Run script
        shell: ${{ matrix.shell }}
        run: ./scripts/setup.ps1

If your script requires PS7+, the powershell (WinPS 5.1) leg will fail fast and the pwsh leg will pass—exactly the signal you want. To guarantee a specific engine on Linux, run inside a container:

jobs:
  linux:
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/powershell:7.4-alpine-3.19
    steps:
      - uses: actions/checkout@v4
      - run: pwsh -File ./scripts/setup.ps1

For self-hosted runners, bake the prerequisites into the image (for example, a VM or Docker image with PowerShell 7 and required modules preinstalled). The #requires lines remain the final guardrail.

Module manifests vs #requires

When you’re building reusable modules, use the module manifest (.psd1) to declare requirements at packaging/import time:

@{
    ModuleVersion      = '1.0.0'
    RootModule         = 'MyModule.psm1'
    PowerShellVersion  = '7.2'
    RequiredModules    = @(
        @{ ModuleName = 'Pester'; ModuleVersion = '5.5.0' },
        'PSReadLine'
    )
}

Tip: Use both when appropriate—module manifests for modules you publish, and #requires at the top of standalone scripts or entrypoints to enforce runtime constraints.

Make optional features explicit

Don’t use #requires for optional components. Detect them at runtime and gracefully degrade:

$hasGraph = Get-Module -ListAvailable -Name 'Microsoft.Graph' | Where-Object Version -ge [version]'2.0.0'
if (-not $hasGraph) {
    Write-Host 'Graph SDK not found; skipping graph sync.'
} else {
    Import-Module Microsoft.Graph
    Invoke-GraphSync
}

Reserving #requires for hard prerequisites keeps the contract crisp and avoids unnecessary elevation or hard failures for optional paths.

Security and performance notes

  • Least privilege: Use -RunAsAdministrator only when mandatory. For mixed-privilege workflows, split scripts or tasks.
  • Determinism: Pin exact versions for critical modules using RequiredVersion. For flexibility, prefer ModuleVersion (minimum) and rely on reproducible environments (e.g., pre-baked container images).
  • Observability: Because #requires stops before any code runs, failure messages are unambiguous—CI logs stay clean.

That’s the power of #requires: a one-line contract for your script’s environment. Declare the minimum PowerShell version and edition, list required modules, and demand elevation only when necessary. Keep the rest of your script focused on logic.

What you get: explicit requirements, early exits, safer deployments, cleaner logs.

Set better prerequisites in PowerShell and streamline your automation. For deeper patterns and advanced recipes, check out the PowerShell Advanced CookBook: https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/

← All Posts Home →