Invoke-Pester runs in CI to test PowerShell modules. Test files and module manifests pin the Pester version they expect — for example through #Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } — on the assumption that the pinned version is the one that runs.
Request
Desired capability
The Version input determines which Pester version the action installs and runs, so a workflow can pin an exact version or a compatible window and have that be the version that actually executes the tests. Prerelease opts into prerelease versions.
Today the action installs the latest Pester regardless of any pin, so exact pins cannot hold. On 2026-07-05 this caused a failure: test files pinned Pester 5.7.1, the runner installed 5.8.0, and discovery failed with Could not load file or assembly 'Pester, Version=5.7.1.0'. Hard-pinning the tests to an exact version does not help either — the next Pester release becomes "latest" and the pin no longer matches what is installed.
This is the linchpin of the hard-lock dependency model (PSModule/Process-PSModule#356): a pin only holds if the pinned version is what actually gets installed.
Acceptance criteria
- An exact version installs and runs exactly that version, even when a newer version exists on the Gallery.
- A range or minimum installs the newest version satisfying the constraint.
- Prerelease versions install when explicitly opted in.
- Workflows that do not set
Version are unaffected and keep installing the latest version.
- The version has a single, explicit source of truth (the workflow input); the action does not parse
#Requires from test files to infer a version.
- An optional module-identity pin (GUID) can be supplied and is validated against the installed module.
Related: repositories hard-locking Pester by GUID
PSModule/GitHub#636, PSModule/Context#118, PSModule/Ast#41, PSModule/Dns#18, PSModule/Json#20, PSModule/NerdFonts#83, PSModule/Net#14, PSModule/PSCustomObject#15
Technical decisions
Constraint syntax — NuGet version ranges, not a #Requires -Modules hashtable. The Version input accepts NuGet version-range syntax — the same syntax Install-PSResource -Version accepts — rather than a #Requires -Modules @{ ModuleVersion = …; MaximumVersion = … } hashtable. A bare version (6.0.0) is exact; [6.0.0] is exact; [5.0.0,6.0.0) is a bounded range; [6.0.0,] is a minimum; (,7.0.0) is a maximum. This keeps the value a straight pass-through to Install-PSResource with no translation layer, and matches the Version input on PSModule/GitHub-Script (PSModule/GitHub-Script#97, PSModule/GitHub-Script#98) so the two actions behave the same way.
Resolution. A range installs the newest version satisfying it (confirmed against Install-PSResource). An empty Version installs the latest available version, preserving current behavior.
Deterministic load. After install, the resolved version is imported with Import-Module -RequiredVersion, so the loaded module is deterministic even when the runner ships a different preinstalled Pester. When a constraint is set but no satisfying installed version can be resolved, the action fails fast rather than importing an unconstrained module.
No auto-resolve. The action does not read #Requires from test files; the spec comes only from the input.
GUID identity pin — deferred. The optional GUID validation is not part of the first delivery; it is tracked as remaining work below.
Placement. Install-PSResourceWithRetry (in the action's helper module) gains -Version and -Prerelease; init.ps1 and exec.ps1 read the inputs and pass the spec through.
Test approach. Action-Test jobs exercise a Pester 5.x range ([5.0.0,6.0.0)) and an exact Pester 6.0.0 pin.
Implementation plan
Core — delivered in #71
Remaining
Invoke-Pesterruns in CI to test PowerShell modules. Test files and module manifests pin the Pester version they expect — for example through#Requires -Modules@{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' }— on the assumption that the pinned version is the one that runs.Request
Desired capability
The
Versioninput determines which Pester version the action installs and runs, so a workflow can pin an exact version or a compatible window and have that be the version that actually executes the tests.Prereleaseopts into prerelease versions.Today the action installs the latest Pester regardless of any pin, so exact pins cannot hold. On 2026-07-05 this caused a failure: test files pinned Pester
5.7.1, the runner installed5.8.0, and discovery failed withCould not load file or assembly 'Pester, Version=5.7.1.0'. Hard-pinning the tests to an exact version does not help either — the next Pester release becomes "latest" and the pin no longer matches what is installed.This is the linchpin of the hard-lock dependency model (PSModule/Process-PSModule#356): a pin only holds if the pinned version is what actually gets installed.
Acceptance criteria
Versionare unaffected and keep installing the latest version.#Requiresfrom test files to infer a version.Related: repositories hard-locking Pester by GUID
PSModule/GitHub#636, PSModule/Context#118, PSModule/Ast#41, PSModule/Dns#18, PSModule/Json#20, PSModule/NerdFonts#83, PSModule/Net#14, PSModule/PSCustomObject#15
Technical decisions
Constraint syntax — NuGet version ranges, not a
#Requires -Moduleshashtable. TheVersioninput accepts NuGet version-range syntax — the same syntaxInstall-PSResource -Versionaccepts — rather than a#Requires -Modules @{ ModuleVersion = …; MaximumVersion = … }hashtable. A bare version (6.0.0) is exact;[6.0.0]is exact;[5.0.0,6.0.0)is a bounded range;[6.0.0,]is a minimum;(,7.0.0)is a maximum. This keeps the value a straight pass-through toInstall-PSResourcewith no translation layer, and matches theVersioninput on PSModule/GitHub-Script (PSModule/GitHub-Script#97, PSModule/GitHub-Script#98) so the two actions behave the same way.Resolution. A range installs the newest version satisfying it (confirmed against
Install-PSResource). An emptyVersioninstalls the latest available version, preserving current behavior.Deterministic load. After install, the resolved version is imported with
Import-Module -RequiredVersion, so the loaded module is deterministic even when the runner ships a different preinstalled Pester. When a constraint is set but no satisfying installed version can be resolved, the action fails fast rather than importing an unconstrained module.No auto-resolve. The action does not read
#Requiresfrom test files; the spec comes only from the input.GUID identity pin — deferred. The optional GUID validation is not part of the first delivery; it is tracked as remaining work below.
Placement.
Install-PSResourceWithRetry(in the action's helper module) gains-Versionand-Prerelease;init.ps1andexec.ps1read the inputs and pass the spec through.Test approach. Action-Test jobs exercise a Pester 5.x range (
[5.0.0,6.0.0)) and an exact Pester6.0.0pin.Implementation plan
Core — delivered in #71
-Versionand-PrereleasetoInstall-PSResourceWithRetryand pass them toInstall-PSResource.Import-Module -RequiredVersion; fail fast when a constraint cannot be satisfied.Version/Prereleaseininit.ps1andexec.ps1and pass the spec through.Version→ latest" default for backward compatibility.Get-Module.6.0.0pin.Remaining
GUIDmodule-identity pin, validated against the installed module.