From 4683934793ccda23659e5d094cbf2bbe966123a6 Mon Sep 17 00:00:00 2001 From: Dan Travison Date: Mon, 10 Sep 2018 10:58:38 -0700 Subject: [PATCH] Update PowerShell to handle the case where the Windows PowerShell module path is already in the environment's `PSModulePath` (#7727) This change updates ModuleIntrinsics.GetModulePath to handle the case where the Windows PowerShell module path is already in the environment's PSModulePath or when launched from a different version of PowerShell. Previously, GetModulePath would append $PSHOME\Modules to the PSModulePath after removing the path for the launching version without considering the Windows PowerShell module path. The result, was the Windows PowerShell modules were found first and loaded incompatible modules; such as the built-in modules. The change detects the Windows PowerShell module path and inserts $PSHOME\Modules path before it. The new test simulates launching from a different version of pwsh that has already added the Windows PowerShell module path. Fixes #7679 --- .../engine/Modules/ModuleIntrinsics.cs | 11 ++++++- .../engine/Module/ModulePath.Tests.ps1 | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index b782b39017..8e008dc0ef 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -910,7 +910,15 @@ namespace System.Management.Automation string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath; currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0); - currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, -1); + + int insertIndex = -1; +#if !UNIX + string windowsPowerShellModulePath = GetWindowsPowerShellPSHomeModulePath(); + // If the Windows PowerShell Module path is already present, insert the system module path + // ($PSHOME/Modules) before it. + insertIndex = PathContainsSubstring(currentProcessModulePath, windowsPowerShellModulePath); +#endif + currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex); } // if we reached this point - always add location to EVT.Process @@ -918,6 +926,7 @@ namespace System.Management.Automation // index of $PSHome\Modules in currentProcessModulePath int indexOfPSHomeModulePath = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); + // if $PSHome\Modules not found (psHomePosition == -1) - append location to the end; // if $PSHome\Modules IS found (psHomePosition >= 0) - insert location before $PSHome\Modules currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, indexOfPSHomeModulePath); diff --git a/test/powershell/engine/Module/ModulePath.Tests.ps1 b/test/powershell/engine/Module/ModulePath.Tests.ps1 index 19e78354dc..11f628f66e 100644 --- a/test/powershell/engine/Module/ModulePath.Tests.ps1 +++ b/test/powershell/engine/Module/ModulePath.Tests.ps1 @@ -130,4 +130,33 @@ Describe "SxS Module Path Basic Tests" -tags "CI" { $paths -contains $customeModules | Should -BeTrue } + It 'Ensures $PSHOME\Modules is inserted correctly when launched from a different version of PowerShell' -Skip:(!($IsCoreCLR -and $IsWindows)) { + # When launched from a different version of PowerShell, PSModulePath contains the other version's PSHOME\Modules path + # and the Windows PowerShell modoule 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 + } }