Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions src/Invoke-Pester.Helpers.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -1283,14 +1283,14 @@ function Invoke-ProcessTestDirectory {
function Install-PSResourceWithRetry {
<#
.SYNOPSIS
Installs a PowerShell module with a retry mechanism, then imports the installed version.
Installs a PowerShell module with a retry mechanism, then imports the installed version as the only loaded version.

.DESCRIPTION
Attempts to install a PowerShell module multiple times in case of failure. When a version
constraint is supplied, the newest version satisfying it is installed; that exact version is
then imported so the loaded module is deterministic even when other versions (for example the
runner's preinstalled copy) are present on the machine. When no version is supplied, the latest
available version is installed.
constraint is supplied, the newest version satisfying it is installed. Any other versions already
loaded in the session are then removed and that exact version is imported, so the loaded module is
deterministic even when other versions (for example the runner's preinstalled copy) are present on
the machine. When no version is supplied, the latest available version is installed.
#>
[CmdletBinding()]
param (
Expand Down Expand Up @@ -1363,28 +1363,43 @@ function Install-PSResourceWithRetry {

# Resolve the exact version to import. Prefer what was just installed; if the resource was
# already present, Install-PSResource returns nothing, so fall back to the newest installed
# version that satisfies the requested constraint.
$resolved = $installed | Where-Object { $_.Name -eq $Name } | Sort-Object Version -Descending | Select-Object -First 1
if (-not $resolved) {
# version that satisfies the requested constraint. When prerelease versions are not requested,
# never resolve to one; when a stable and a prerelease share a base version, the stable one wins.
# PSResourceGet exposes prerelease state as the 'Prerelease' string (empty for stable), present on
# both Install-PSResource and Get-InstalledPSResource output.
$sortOrder = @(
Comment thread
MariusStorhaug marked this conversation as resolved.
@{ Expression = 'Version'; Descending = $true }
@{ Expression = { -not [string]::IsNullOrWhiteSpace($_.Prerelease) }; Descending = $false }
)
$candidates = @($installed | Where-Object { $_.Name -eq $Name })
Comment thread
MariusStorhaug marked this conversation as resolved.
if ($candidates.Count -eq 0) {
$getParams = @{ Name = $Name; Verbose = $false; ErrorAction = 'SilentlyContinue' }
if (-not [string]::IsNullOrWhiteSpace($Version)) {
$getParams['Version'] = $Version
}
$resolved = Get-InstalledPSResource @getParams | Sort-Object Version -Descending | Select-Object -First 1
$candidates = @(Get-InstalledPSResource @getParams)
}
if (-not $Prerelease) {
$candidates = @($candidates | Where-Object { [string]::IsNullOrWhiteSpace($_.Prerelease) })
}
$resolved = $candidates | Sort-Object -Property $sortOrder | Select-Object -First 1

# Import into the global session state so the resolved version is the one every subsequent
# command (for example Invoke-Pester in exec.ps1) uses, instead of PowerShell auto-loading the
# highest version available on PSModulePath.
# A version constraint was requested but no satisfying installed version could be resolved. Fail fast
# before mutating session state; importing unconstrained would silently load an arbitrary copy from
# PSModulePath (for example the runner's preinstalled module), defeating the deterministic,
# constraint-driven selection this function exists to guarantee.
if (-not $resolved -and -not [string]::IsNullOrWhiteSpace($Version)) {
throw "No installed '$Name' version satisfies constraint '$Version'; refusing to import an unconstrained version."
}

# Remove every already-loaded instance (all versions, including nested) from the session so the
# chosen version is the only one loaded, then import into the global session state so the resolved
# version is the one every subsequent command (for example Invoke-Pester in exec.ps1) uses, instead
# of PowerShell auto-loading the highest version available on PSModulePath.
Get-Module -Name $Name -All | Remove-Module -Force -ErrorAction SilentlyContinue
Comment thread
MariusStorhaug marked this conversation as resolved.
if ($resolved) {
Comment thread
MariusStorhaug marked this conversation as resolved.
Write-Output "Importing module: $Name $($resolved.Version)"
$imported = Import-Module -Name $Name -RequiredVersion $resolved.Version -Force -Global -PassThru -ErrorAction Stop
Comment thread
MariusStorhaug marked this conversation as resolved.
} elseif (-not [string]::IsNullOrWhiteSpace($Version)) {
# A version constraint was requested but no satisfying installed version could be resolved. Importing the
# module unconstrained here would silently load an arbitrary copy from PSModulePath (for example the
# runner's preinstalled module), defeating the deterministic, constraint-driven selection this function
# exists to guarantee. Fail fast instead.
throw "No installed '$Name' version satisfies constraint '$Version'; refusing to import an unconstrained version."
} else {
$imported = Import-Module -Name $Name -Force -Global -PassThru -ErrorAction Stop
}
Expand Down
Loading