Skip to content

Module Requirements does not warn that #Requires -Modules can discard a script's exit code #139

Description

Context and request

Module Requirements is the reference for how a script declares the modules it needs. It covers every version-specification form and backs each one with an executable test — and says nothing about the one side effect that can quietly break a gate script: declaring the dependency with #Requires -Modules can cost the script its exit code.

# gate.ps1
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' }
Write-Output 'ran'
exit 7
$ pwsh -NoProfile -File gate.ps1
ran
$ echo $LASTEXITCODE
0

The script runs to completion and executes its exit; only the code is lost. A test-gate script written this way reports success no matter what its suite did — the exact failure mode Prove the test can fail exists to catch, and it is invisible in a passing run.

This was hit while wiring #131: a first draft of the Pester runner declared Pester the same way the suites do, and the local run reported success on a red suite. .github/scripts/Invoke-PesterSuite.ps1 on main is unaffected — it imports Pester explicitly — but nothing written down says why that is the required shape, so the next script gets it wrong again.

Outcome: a maintainer reading the Module Requirements page learns that an entry script whose exit code matters must import the module itself, and the claim is backed by the same executable proof every other claim on that page has.

Acceptance criteria

  • The Module Requirements page states that a script whose exit code is meaningful imports its modules explicitly instead of declaring them with #Requires -Modules, and shows the reproduction.
  • The page is honest about the boundaries of what was observed: it reproduces with Pester, not with the other modules tried, and the mechanism is not known.
  • tests/Requires-Modules.Tests.ps1 proves both halves — an explicit import returns the requested exit code, and the #Requires form does not.
  • The full tests/ suite stays green, and Invoke-ScriptAnalyzer -Settings .github/linters/.powershell-psscriptanalyzer.psd1 reports no Error or Warning.

Technical decisions

  • What was verified, on PowerShell 7.6.4, 2026-08-02:
    • Reproduces with #Requires -Modules Pester, with the module-specification hashtable form, and with Pester 5.8.0 as well as 6.0.1.
    • Does not reproduce with #Requires -Modules naming PSScriptAnalyzer, PSReadLine, Microsoft.PowerShell.PSResourceGet, Microsoft.PowerShell.Management, or a hand-made throwaway module.
    • Does not reproduce with #Requires -Version 7.0 alone.
    • Does not reproduce when the script imports Pester itself — Import-Module -Name Pester -MinimumVersion 6.0.0 -MaximumVersion 6.* returns the requested code, with or without -Global.
    • Under pwsh -Command ". ./gate.ps1" — the form GitHub Actions' shell: pwsh uses — every non-zero exit collapses to 1 whether or not the #Requires line is present, so a CI step still fails. The false green is specific to pwsh -File, which is how a contributor runs a script by hand.
  • The page records an observation, not a mechanism. Why it is Pester-specific was not established, so the guidance is written as a shape to avoid rather than as a rule about how #Requires works. Overstating it would put an unverified claim in a standard.
  • Scope is the Module Requirements page and its proof suite. The #Requires -Modules lines in the *.Tests.ps1 files stay exactly as they are — Pester runs those as containers, not as entry scripts, so no exit code is at stake. No script under .github/scripts/ changes.
  • The proof asserts the guidance, not the bug. The explicit-import case asserts the exact code (7), and the #Requires case asserts only that the code is not returned. If a future PowerShell or Pester release fixes the behavior, that second assertion turns red and the page gets revisited — which is the intended signal.

Implementation plan

  • Add a test to tests/Requires-Modules.Tests.ps1 that runs a child script through pwsh -File and asserts an explicit Import-Module returns the exit code the script asks for; confirm it passes.
  • Add the companion test asserting the #Requires -Modules form does not return that code; confirm it passes, and confirm it fails when pointed at the explicit-import script — so it is measuring the difference and not passing for free.
  • Add the section to src/docs/Coding-Standards/PowerShell/Requires-Modules.md with the reproduction, the verified boundaries, and the rule for entry scripts.
  • Run the full tests/ suite, .github/scripts/Test-DocumentationLink.ps1, .github/scripts/Update-DocumentationIndex.ps1 -Check, and Invoke-ScriptAnalyzer with the repository settings.

Metadata

Metadata

Labels

documentationImprovements or additions to documentation

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions