Opened out of #8218.
We do very little santisation/normalisation on paths passed in as module names in ModuleSpecification types to various cmdlets.
Not many users rely directly on this functionality, but things like RequiredModules in module manifests do.
We currently don't have any layering or type API (that I know of) to reflect that a path passed in has been converted by PowerShell to one that's safe for .NET to operate on, and the ideal scenario would be for us to change that. Otherwise we are doomed to repeat the same path checks/resolutions at every layer in the code, either hurting perf or causing serious bugs.
Steps to reproduce
Some examples of this problem:
- Remove module using a path in a qualified name doesn't work:
Describe "Remove-Module works with FullyQualifiedName using a path for the name" {
BeforeAll {
$moduleName = 'rmomod'
$moduleVersion = '1.2'
$modulePath = Join-Path $TestDrive $moduleName
$manifestPath = Join-Path $modulePath "$moduleName.psd1"
New-Item -ItemType Directory -Path $modulePath
New-ModuleManifest -Path $manifestPath -ModuleVersion $moduleVersion
if ($IsWindows)
{
$sep = '\'
$altSep = '/'
}
else
{
$sep = '/'
$altSep = '\'
}
$absoluteTestCases = @(
@{ ModuleName = $moduleName; Case = 'module name' }
@{ ModuleName = $modulePath; Case = 'absolute module dir path' }
@{ ModuleName = "$TestDrive${altSep}$moduleName"; Case = 'absolute module dir path with alt dir separators' }
@{ ModuleName = $manifestPath; Case = 'absolute manifest path' }
@{ ModuleName = "$TestDrive${altSep}$moduleName${sep}$moduleName.psd1"; Case = 'absolute manifest path with alt dir separators' }
)
$relativeTestCases = @(
@{ Case = 'relative path to module dir'; Location = $TestDrive; ModuleName = "./$moduleName" }
@{ Case = 'relative path to module dir with alt sep'; Location = "$TestDrive/duck"; ModuleName = "..${altSep}moduleName" }
@{ Case = 'relative path to manifest with alt sep'; Location = "$TestDrive/$moduleName"; ModuleName = "./$moduleName.psd1" }
@{ Case = 'relative path to manifest with alt sep'; Location = $TestDrive; ModuleName = ".${sep}$moduleName{$altSep}$moduleName.psd1" }
@{ Case = 'current dir being module dir'; Location = "$TestDrive/$moduleName"; ModuleName = "." }
)
}
BeforeEach {
Import-Module $modulePath
}
AfterEach {
Remove-Module $moduleName -ErrorAction SilentlyContinue
}
It "Removes the module by <Case>" -TestCases $absoluteTestCases {
param([string]$ModuleName, [string]$Case)
$fqn = @{
ModuleName = $ModuleName
ModuleVersion = $moduleVersion
}
Remove-Module -FullyQualifiedName $fqn -ErrorAction Stop
Get-Module $moduleName | Should -HaveCount 0
}
It "Removes the module by <Case>" -TestCases $relativeTestCases {
param([string]$Location, [string]$ModuleName, [string]$Case)
$fqn = @{
ModuleName = $ModuleName
ModuleVersion = $moduleVersion
}
if (-not (Test-Path $Location))
{
New-Item -ItemType Directory -Path $Location
}
Push-Location $Location
try
{
Remove-Module -FullyQualifiedName $fqn -ErrorAction Stop
Get-Module $moduleName | Should -HaveCount 0
}
finally
{
Pop-Location
}
}
}
- Required modules in a script:
Describe "Requiring modules by absolute path" {
BeforeAll {
$scriptPath = Join-Path $TestDrive "script.ps1"
$success = 'SUCCESS'
$moduleName = 'reqmod'
$modulePath = Join-Path $TestDrive $moduleName
$manifestPath = Join-Path $modulePath "$moduleName.psd1"
New-Item -ItemType Directory $modulePath -Force
New-ModuleManifest -Path $manifestPath -ModuleVersion '3.2.4'
if ($IsWindows)
{
$sep = '\'
$altSep = '/'
}
else
{
$sep = '/'
$altSep = '\'
}
$oldModulePath = $env:PSModulePath
$env:PSModulePath += [System.IO.Path]::PathSeparator + $TestDrive
$testCases = @(
@{ Case = 'module name, with module on path'; ModuleName = $moduleName }
@{ Case = 'absolute path to module dir'; ModuleName = $modulePath }
@{ Case = 'absolute path to module dir with alt sep'; ModuleName = "$TestDrive${altSep}$moduleName" }
@{ Case = 'absolute path to manifest'; ModuleName = $manifestPath }
@{ Case = 'absolute path to manifest with alt sep'; ModuleName = "$TestDrive${sep}$moduleName${altSep}$moduleName.psd1" }
)
}
AfterAll {
$env:PSModulePath = $oldModulePath
}
Context "Autoload unloaded required module" {
AfterEach {
Remove-Module $moduleName -ErrorAction SilentlyContinue
}
It "Autoloads the required module by <Case>" -TestCases $testCases {
param([string]$ModuleName, [string]$Case)
$script = @"
#requires -Modules @{ ModuleName = '$ModuleName'; ModuleVersion = '$moduleVersion' }
'$success'
"@
New-Item -Path $scriptPath -Value $script -Force
& $scriptPath | Should -BeExactly $success
}
}
Context "Verify required module is already loaded" {
BeforeAll {
Import-Module $modulePath
}
AfterAll {
Remove-Module $moduleName -ErrorAction SilentlyContinue
}
It "Verifies that the required module is loaded by <Case>" -TestCases $testCases {
param([string]$ModuleName, [string]$Case)
$script = @"
#requires -Modules @{ ModuleName = '$ModuleName'; ModuleVersion = '$moduleVersion' }
'$success'
"@
New-Item -Path $scriptPath -Value $script -Force
& $scriptPath | Should -BeExactly $success
}
}
}
(This second case actually supports alt separators, which I didn't expect. But still no module dir. And no relative paths either)
Opened out of #8218.
We do very little santisation/normalisation on paths passed in as module names in
ModuleSpecificationtypes to various cmdlets.Not many users rely directly on this functionality, but things like
RequiredModulesin module manifests do.We currently don't have any layering or type API (that I know of) to reflect that a path passed in has been converted by PowerShell to one that's safe for .NET to operate on, and the ideal scenario would be for us to change that. Otherwise we are doomed to repeat the same path checks/resolutions at every layer in the code, either hurting perf or causing serious bugs.
Steps to reproduce
Some examples of this problem: