Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions .github/workflows/Action-Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ jobs:
} else {
throw "Helpers module not found!"
}

- name: Run Install-PSModule regression tests
shell: pwsh
run: ./tests/Install-PSModule.Tests.ps1
26 changes: 24 additions & 2 deletions src/Helpers/Helpers.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,33 @@ function Install-PSModule {
Write-Host '::group::Resolving dependencies'
Resolve-PSModuleDependency -ManifestFilePath $manifestFilePath
Write-Host '::endgroup::'
# Install into a version folder that matches the manifest's ModuleVersion so PowerShell
# accepts the module. Fall back to the 999.0.0 placeholder only when the manifest is
# unstamped (e.g. a build that opted out of version resolution). Validate the version as a
# real [version] so a malformed manifest cannot inject path separators/traversal into the
# install path.
$manifestVersion = (Import-PowerShellDataFile -Path $manifestFilePath).ModuleVersion
if ([string]::IsNullOrWhiteSpace($manifestVersion)) {
$moduleVersion = '999.0.0'
} else {
$parsedVersion = $null
if (-not [version]::TryParse($manifestVersion, [ref] $parsedVersion)) {
throw "ModuleVersion '$manifestVersion' in [$manifestFilePath] is not a valid version."
}
$moduleVersion = $parsedVersion.ToString()
}
$PSModulePath = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Select-Object -First 1
$codePath = New-Item -Path "$PSModulePath/$moduleName/999.0.0" -ItemType Directory -Force | Select-Object -ExpandProperty FullName
$moduleInstallRoot = Join-Path -Path $PSModulePath -ChildPath $moduleName
$moduleInstallPath = Join-Path -Path $moduleInstallRoot -ChildPath $moduleVersion
$codePath = New-Item -Path $moduleInstallPath -ItemType Directory -Force |
Select-Object -ExpandProperty FullName
Copy-Item -Path "$Path/*" -Destination $codePath -Recurse -Force
Write-Host '::group::Importing module'
Import-Module -Name $moduleName -Verbose
# Import the freshly-installed copy explicitly by its manifest path (with -Force) so it is
# not shadowed by another version of the same module that may already be loaded or present
# on $env:PSModulePath.
$installedManifestPath = Join-Path -Path $codePath -ChildPath "$moduleName.psd1"
Import-Module -Name $installedManifestPath -Force -Global -Verbose
Write-Host '::endgroup::'
if ($PassThru) {
return $codePath
Expand Down
132 changes: 132 additions & 0 deletions tests/Install-PSModule.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'

$repoRoot = Split-Path -Path $PSScriptRoot -Parent
$helpersManifestPath = Join-Path -Path $repoRoot -ChildPath 'src/Helpers/Helpers.psd1'
$originalPSModulePath = $env:PSModulePath
$testRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "Install-PSModule-$([guid]::NewGuid())"

$assert = {
param(
[bool] $Condition,
[string] $Message
)

if (-not $Condition) {
throw $Message
}
}

try {
$testPSModulePath = Join-Path -Path $testRoot -ChildPath 'PSModules'
New-Item -Path $testPSModulePath -ItemType Directory -Force | Out-Null
$env:PSModulePath = $testPSModulePath + [System.IO.Path]::PathSeparator + $originalPSModulePath

Remove-Module -Name Helpers -Force -ErrorAction SilentlyContinue
Import-Module -Name $helpersManifestPath -Force

$realVersionSourcePath = Join-Path -Path $testRoot -ChildPath 'RealVersionModule'
New-Item -Path $realVersionSourcePath -ItemType Directory -Force | Out-Null
"function Get-RealVersionValue { 'real-1.2.3' }" |
Set-Content -Path (Join-Path -Path $realVersionSourcePath -ChildPath 'RealVersionModule.psm1')
@'
@{
RootModule = 'RealVersionModule.psm1'
ModuleVersion = '1.2.3'
GUID = '11111111-1111-1111-1111-111111111111'
Author = 'Test'
FunctionsToExport = @('Get-RealVersionValue')
}
'@ | Set-Content -Path (Join-Path -Path $realVersionSourcePath -ChildPath 'RealVersionModule.psd1')

$realVersionInstalledPath = Install-PSModule -Path $realVersionSourcePath -PassThru
$realVersionInstallRoot = Join-Path -Path $testPSModulePath -ChildPath 'RealVersionModule'
$expectedRealVersionPath = Join-Path -Path $realVersionInstallRoot -ChildPath '1.2.3'
$unexpectedPlaceholderPath = Join-Path -Path $realVersionInstallRoot -ChildPath '999.0.0'
$realVersionManifestPath = Join-Path -Path $expectedRealVersionPath -ChildPath 'RealVersionModule.psd1'
$returnedRealVersionPath = [System.IO.Path]::GetFullPath($realVersionInstalledPath)
$expectedRealVersionFullPath = [System.IO.Path]::GetFullPath($expectedRealVersionPath)

& $assert ($returnedRealVersionPath -eq $expectedRealVersionFullPath) 'Expected Install-PSModule to return the real version path.'
& $assert (Test-Path -Path $realVersionManifestPath) 'Expected the module to be installed under version 1.2.3.'
& $assert (-not (Test-Path -Path $unexpectedPlaceholderPath)) 'Did not expect stamped modules to be installed under 999.0.0.'
& $assert ((Get-RealVersionValue) -eq 'real-1.2.3') 'Expected the real-version module command to be imported.'

Remove-Module -Name RealVersionModule -Force -ErrorAction SilentlyContinue

$shadowInstallRoot = Join-Path -Path $testPSModulePath -ChildPath 'ShadowModule'
$oldShadowInstallPath = Join-Path -Path $shadowInstallRoot -ChildPath '999.0.0'
New-Item -Path $oldShadowInstallPath -ItemType Directory -Force | Out-Null
"function Get-ShadowValue { 'old-999' }" |
Set-Content -Path (Join-Path -Path $oldShadowInstallPath -ChildPath 'ShadowModule.psm1')
@'
@{
RootModule = 'ShadowModule.psm1'
ModuleVersion = '999.0.0'
GUID = '22222222-2222-2222-2222-222222222222'
Author = 'Test'
FunctionsToExport = @('Get-ShadowValue')
}
'@ | Set-Content -Path (Join-Path -Path $oldShadowInstallPath -ChildPath 'ShadowModule.psd1')

$shadowSourcePath = Join-Path -Path $testRoot -ChildPath 'ShadowModule'
New-Item -Path $shadowSourcePath -ItemType Directory -Force | Out-Null
"function Get-ShadowValue { 'new-2.0.0' }" |
Set-Content -Path (Join-Path -Path $shadowSourcePath -ChildPath 'ShadowModule.psm1')
@'
@{
RootModule = 'ShadowModule.psm1'
ModuleVersion = '2.0.0'
GUID = '33333333-3333-3333-3333-333333333333'
Author = 'Test'
FunctionsToExport = @('Get-ShadowValue')
}
'@ | Set-Content -Path (Join-Path -Path $shadowSourcePath -ChildPath 'ShadowModule.psd1')

$oldShadowManifestPath = Join-Path -Path $oldShadowInstallPath -ChildPath 'ShadowModule.psd1'
Import-Module -Name $oldShadowManifestPath -Force
& $assert ((Get-ShadowValue) -eq 'old-999') 'Expected the fixture to preload the placeholder module.'

$shadowInstalledPath = Install-PSModule -Path $shadowSourcePath -PassThru
$expectedShadowPath = Join-Path -Path $shadowInstallRoot -ChildPath '2.0.0'
$shadowCommand = Get-Command -Name Get-ShadowValue
$returnedShadowPath = [System.IO.Path]::GetFullPath($shadowInstalledPath)
$expectedShadowFullPath = [System.IO.Path]::GetFullPath($expectedShadowPath)

& $assert ($returnedShadowPath -eq $expectedShadowFullPath) 'Expected the shadowed module under version 2.0.0.'
& $assert ((Get-ShadowValue) -eq 'new-2.0.0') 'Expected the real-version command to replace the preloaded 999.0.0 command.'
& $assert ($shadowCommand.Version.ToString() -eq '2.0.0') 'Expected command resolution to point at version 2.0.0.'

Remove-Module -Name ShadowModule -Force -ErrorAction SilentlyContinue

$badSourcePath = Join-Path -Path $testRoot -ChildPath 'BadModule'
New-Item -Path $badSourcePath -ItemType Directory -Force | Out-Null
"function Get-BadValue { 'bad' }" |
Set-Content -Path (Join-Path -Path $badSourcePath -ChildPath 'BadModule.psm1')
@'
@{
RootModule = 'BadModule.psm1'
ModuleVersion = '1.0/bad'
GUID = '44444444-4444-4444-4444-444444444444'
Author = 'Test'
FunctionsToExport = @('Get-BadValue')
}
'@ | Set-Content -Path (Join-Path -Path $badSourcePath -ChildPath 'BadModule.psd1')

$invalidVersionRejected = $false
try {
Install-PSModule -Path $badSourcePath
} catch {
$invalidVersionRejected = $_.Exception.Message -like '*is not a valid version*'
}

& $assert $invalidVersionRejected 'Expected malformed ModuleVersion values to be rejected.'
} finally {
$env:PSModulePath = $originalPSModulePath
Remove-Module -Name Helpers, RealVersionModule, ShadowModule, BadModule -Force -ErrorAction SilentlyContinue
if (Test-Path -Path $testRoot) {
Remove-Item -Path $testRoot -Recurse -Force
}
}
Loading