# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. Describe "SxS Module Path Basic Tests" -tags "CI" { BeforeAll { if ($IsWindows) { $powershell = "$PSHOME\pwsh.exe" $ProductName = "WindowsPowerShell" if ($IsCoreCLR -and ($PSHOME -notlike "*Windows\System32\WindowsPowerShell\v1.0")) { $ProductName = "PowerShell" } $expectedUserPath = Join-Path -Path $HOME -ChildPath "Documents\$ProductName\Modules" $expectedSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath "$ProductName\Modules" $userConfigPath = "~/Documents/powershell/powershell.config.json" } else { $powershell = "$PSHOME/pwsh" $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES") $expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES") $userConfigPath = "~/.config/powershell/powershell.config.json" } $userConfigExists = $false if (Test-Path $userConfigPath) { $userConfigExists = $true Copy-Item $userConfigPath "$userConfigPath.backup" -Force -ErrorAction Ignore } $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules' # Skip these tests in cases when there is no 'pwsh' executable (e.g. when framework dependent PS package is used) $skipNoPwsh = -not (Test-Path $powershell) if ($IsWindows) { $expectedWindowsPowerShellPSHomePath = Join-Path $env:windir "System32" "WindowsPowerShell" "v1.0" "Modules" } ## Setup a fake PSHome $fakePSHome = Join-Path -Path $TestDrive -ChildPath 'FakePSHome' $fakePSHomeModuleDir = Join-Path -Path $fakePSHome -ChildPath 'Modules' $fakePowerShell = Join-Path -Path $fakePSHome -ChildPath (Split-Path -Path $powershell -Leaf) $fakePSDepsFile = Join-Path -Path $fakePSHome -ChildPath "pwsh.deps.json" New-Item -Path $fakePSHome -ItemType Directory > $null New-Item -Path $fakePSHomeModuleDir -ItemType Directory > $null } AfterAll { if ($userConfigExists) { Move-Item "$userConfigPath.backup" $userConfigPath -Force -ErrorAction Ignore } else { Remove-Item "$userConfigPath" -Force -ErrorAction Ignore } } BeforeEach { $originalModulePath = $env:PSModulePath } AfterEach { $env:PSModulePath = $originalModulePath } It "validate sxs module path" -Skip:$skipNoPwsh { $env:PSModulePath = "" $defaultModulePath = & $powershell -nopro -c '$env:PSModulePath' $pathSeparator = [System.IO.Path]::PathSeparator $paths = $defaultModulePath.Replace("$pathSeparator$pathSeparator", "$pathSeparator") -split $pathSeparator if ($IsWindows) { $expectedPaths = 3 # user, shared, pshome $userPaths = [System.Environment]::GetEnvironmentVariable("PSModulePath", [System.EnvironmentVariableTarget]::User) $expectedPaths += $userPaths ? $userPaths.Split($pathSeparator).Count : 0 $machinePaths = [System.Environment]::GetEnvironmentVariable("PSModulePath", [System.EnvironmentVariableTarget]::Machine) $expectedPaths += $machinePaths ? $machinePaths.Split($pathSeparator).Count : 0 $paths.Count | Should -Be $expectedPaths } else { $paths.Count | Should -Be 3 } $paths[0].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedUserPath $paths[1].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSharedPath $paths[2].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSystemPath $defaultModulePath | Should -BeLike "*$expectedWindowsPowerShellPSHomePath*" } It "Works with pshome module path derived from a different PowerShell instance" -Skip:(!$IsCoreCLR -or $skipNoPwsh) { ## Create 'powershell' and 'pwsh.deps.json' in the fake PSHome folder, ## so that the module path calculation logic would believe it's real. New-Item -Path $fakePowerShell -ItemType File -Force > $null New-Item -Path $fakePSDepsFile -ItemType File -Force > $null try { ## PSHome module path derived from another PowerShell instance should be ignored $env:PSModulePath = $fakePSHomeModuleDir $newModulePath = & $powershell -nopro -c '$env:PSModulePath' $paths = $newModulePath -split [System.IO.Path]::PathSeparator $paths.Count | Should -Be 4 $paths[0] | Should -Be $expectedUserPath $paths[1] | Should -Be $expectedSharedPath $paths[2] | Should -Be $expectedSystemPath $paths[3] | Should -Be $fakePSHomeModuleDir } finally { ## Remove 'powershell' and 'pwsh.deps.json' from the fake PSHome folder Remove-Item -Path $fakePowerShell -Force -ErrorAction SilentlyContinue Remove-Item -Path $fakePSDepsFile -Force -ErrorAction SilentlyContinue } } It "keep non-pshome module path derived from PowerShell instance parent" -Skip:(!$IsCoreCLR -or $skipNoPwsh) { ## non-pshome module path derived from another PowerShell instance should be preserved $customeModules = Join-Path -Path $TestDrive -ChildPath 'CustomModules' $env:PSModulePath = $fakePSHomeModuleDir, $customeModules -join ([System.IO.Path]::PathSeparator) $newModulePath = & $powershell -nopro -c '$env:PSModulePath' $paths = $newModulePath -split [System.IO.Path]::PathSeparator $paths.Count | Should -Be 5 $paths -contains $fakePSHomeModuleDir | Should -BeTrue $paths -contains $customeModules | Should -BeTrue } It 'Ensures $PSHOME\Modules is inserted correctly when launched from a different version of PowerShell' -Skip:(!($IsCoreCLR -and $IsWindows) -or $skipNoPwsh) { # When launched from a different version of PowerShell, PSModulePath contains the other version's PSHOME\Modules path # and the Windows PowerShell module path. The other version's module path should be removed and this version's # PSHOME\Modules path should be inserted before Windows PowerShell module path. $winpwshModulePath = [System.IO.Path]::Combine([System.Environment]::SystemDirectory, "WindowsPowerShell", "v1.0", "Modules"); $pwshModulePath = Join-Path -Path $PSHOME -ChildPath 'Modules' # create a fake 'other version' $PSHOME and $PSHOME\Modules $fakeHome = Join-Path -Path $TestDrive -ChildPath 'fakepwsh' $fakeModulePath = Join-Path -Path $fakeHome -ChildPath 'Modules' $null = New-Item -Path $fakeHome -ItemType Directory $null = New-Item -Path $fakeModulePath -ItemType Directory # powershell looks for these to files to determine the directory is a pwsh directory. Set-Content -Path "$fakeHome\pwsh.exe" -Value "fake pwsh.exe" Set-Content -Path "$fakeHome\pwsh.deps.json" -Value 'fake pwsh.deps.json' # replace the actual pwsh module path with the fake one. $fakeModulePath = $env:PSModulePath.Replace($pwshModulePath, $fakeModulePath, [StringComparison]::OrdinalIgnoreCase) $newModulePath = & $powershell -nopro -c '$env:PSModulePath' $pwshIndex = $newModulePath.IndexOf($pwshModulePath, [StringComparison]::OrdinalIgnoreCase) $wpshIndex = $newModulePath.IndexOf($winpwshModulePath, [StringComparison]::OrdinalIgnoreCase) # ensure both module paths exist and the pwsh module path occurs before the Windows PowerShell module path $pwshIndex | Should -Not -Be -1 $wpshIndex | Should -Not -Be -1 $pwshIndex | Should -BeLessThan $wpshIndex } It 'Windows PowerShell does not inherit PowerShell paths' -Skip:(!$IsWindows) { $out = powershell.exe -noprofile -command '$env:PSModulePath' $out | Should -Not -BeLike "*$expectedUserPath*" $out | Should -Not -BeLike "*$expectedSharedPath*" $out | Should -Not -BeLike "*$expectedSystemPath*" } It 'Windows PowerShell inherits user added paths' -Skip:(!$IsWindows) { $env:PSModulePath += ";myPath" $out = powershell.exe -noprofile -command '$env:PSModulePath' $out | Should -BeLike '*;myPath' } It 'Windows PowerShell does not inherit path defined in powershell.config.json' -Skip:(!$IsWindows) { try { $userConfig = '{ "PSModulePath": "myUserPath" }' Set-Content -Path $userConfigPath -Value $userConfig -Force $out = & $powershell -noprofile -command 'powershell.exe -noprofile -command $env:PSModulePath' $out | Should -Not -BeLike 'myUserPath;*' } finally { Remove-Item -Path $userConfigPath -Force } } }