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
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 -Modulescan cost the script its exit code.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.ps1onmainis 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
#Requires -Modules, and shows the reproduction.tests/Requires-Modules.Tests.ps1proves both halves — an explicit import returns the requested exit code, and the#Requiresform does not.tests/suite stays green, andInvoke-ScriptAnalyzer -Settings .github/linters/.powershell-psscriptanalyzer.psd1reports no Error or Warning.Technical decisions
#Requires -Modules Pester, with the module-specification hashtable form, and with Pester5.8.0as well as6.0.1.#Requires -ModulesnamingPSScriptAnalyzer,PSReadLine,Microsoft.PowerShell.PSResourceGet,Microsoft.PowerShell.Management, or a hand-made throwaway module.#Requires -Version 7.0alone.Import-Module -Name Pester -MinimumVersion 6.0.0 -MaximumVersion 6.*returns the requested code, with or without-Global.pwsh -Command ". ./gate.ps1"— the form GitHub Actions'shell: pwshuses — every non-zero exit collapses to1whether or not the#Requiresline is present, so a CI step still fails. The false green is specific topwsh -File, which is how a contributor runs a script by hand.#Requiresworks. Overstating it would put an unverified claim in a standard.#Requires -Moduleslines in the*.Tests.ps1files 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.7), and the#Requirescase 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
tests/Requires-Modules.Tests.ps1that runs a child script throughpwsh -Fileand asserts an explicitImport-Modulereturns the exit code the script asks for; confirm it passes.#Requires -Modulesform 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.src/docs/Coding-Standards/PowerShell/Requires-Modules.mdwith the reproduction, the verified boundaries, and the rule for entry scripts.tests/suite,.github/scripts/Test-DocumentationLink.ps1,.github/scripts/Update-DocumentationIndex.ps1 -Check, andInvoke-ScriptAnalyzerwith the repository settings.