diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index 99ce5d303..19fcc4992 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -2823,7 +2823,7 @@ namespace System.Management.Automation.Runspaces
// If a user has any module with the same name as that of the core module( or nested module inside the core module)
// in his module path, then that will get loaded instead of the actual nested module (from the GAC - in our case)
// Hence, searching only from the system module path while loading the core modules
- ProcessImportModule(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetSystemwideModulePath(), publicCommands);
+ ProcessImportModule(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetPSHomeModulePath(), publicCommands);
// Win8:328748 - functions defined in global scope end up in a module
// Since we import the core modules, EngineSessionState's module is set to the last imported module. So, if a function is defined in global scope, it ends up in that module.
diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
index 890eb6ee1..6b4464061 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
@@ -542,13 +542,13 @@ namespace System.Management.Automation
}
///
- /// Gets the default system-wide module path.
+ /// Gets the PSHome module path, as known as the "system wide module path" in windows powershell.
///
- /// The default system wide module path
- internal static string GetSystemwideModulePath()
+ /// The PSHome module path
+ internal static string GetPSHomeModulePath()
{
- if (s_systemWideModulePath != null)
- return s_systemWideModulePath;
+ if (s_psHomeModulePath != null)
+ return s_psHomeModulePath;
try
{
@@ -565,32 +565,33 @@ namespace System.Management.Automation
#if !UNIX
psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\");
#endif
- Interlocked.CompareExchange(ref s_systemWideModulePath, Path.Combine(psHome, "Modules"), null);
+ Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
}
}
catch (System.Security.SecurityException) { }
- return s_systemWideModulePath;
+ return s_psHomeModulePath;
}
- private static string s_systemWideModulePath;
+ private static string s_psHomeModulePath;
///
- /// Get the DSC module path.
+ /// Get the module path that is shared among different users.
+ /// It's known as "Program Files" module path in windows powershell.
///
///
- internal static string GetDscModulePath()
+ internal static string GetSharedModulePath()
{
#if UNIX
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.SHARED_MODULES);
#else
- string dscModulePath = null;
+ string sharedModulePath = null;
string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (!string.IsNullOrEmpty(programFilesPath))
{
- dscModulePath = Path.Combine(programFilesPath, Utils.ModuleDirectory);
+ sharedModulePath = Path.Combine(programFilesPath, Utils.ModuleDirectory);
}
- return dscModulePath;
+ return sharedModulePath;
#endif
}
@@ -601,20 +602,20 @@ namespace System.Management.Automation
///
private static string CombineSystemModulePaths()
{
- string systemModulePath = GetSystemwideModulePath();
- string commonModulePath = GetDscModulePath();
+ string psHomeModulePath = GetPSHomeModulePath();
+ string sharedModulePath = GetSharedModulePath();
- bool isSystemPathNullOrEmpty = string.IsNullOrEmpty(systemModulePath);
- bool isCommonPathNullOrEmpty = string.IsNullOrEmpty(commonModulePath);
+ bool isPSHomePathNullOrEmpty = string.IsNullOrEmpty(psHomeModulePath);
+ bool isSharedPathNullOrEmpty = string.IsNullOrEmpty(sharedModulePath);
- if (!isSystemPathNullOrEmpty && !isCommonPathNullOrEmpty)
+ if (!isPSHomePathNullOrEmpty && !isSharedPathNullOrEmpty)
{
- return (commonModulePath + Path.PathSeparator + systemModulePath);
+ return (sharedModulePath + Path.PathSeparator + psHomeModulePath);
}
- if (!isSystemPathNullOrEmpty || !isCommonPathNullOrEmpty)
+ if (!isPSHomePathNullOrEmpty || !isSharedPathNullOrEmpty)
{
- return isSystemPathNullOrEmpty ? commonModulePath : systemModulePath;
+ return isPSHomePathNullOrEmpty ? sharedModulePath : psHomeModulePath;
}
return null;
@@ -716,13 +717,13 @@ namespace System.Management.Automation
/// - inbox nano/iot ps started from sxs ps
/// If it's likely one of them, then we need to clear the current process module path.
///
- private static bool NeedToClearProcessModulePath(string currentProcessModulePath, string personalModulePath, string programFilesModulePath, bool runningSxS)
+ private static bool NeedToClearProcessModulePath(string currentProcessModulePath, string personalModulePath, string sharedModulePath, bool runningSxS)
{
#if UNIX
return false;
#else
Dbg.Assert(!string.IsNullOrEmpty(personalModulePath), "caller makes sure it's not null or empty");
- Dbg.Assert(!string.IsNullOrEmpty(programFilesModulePath), "caller makes sure it's not null or empty");
+ Dbg.Assert(!string.IsNullOrEmpty(sharedModulePath), "caller makes sure it's not null or empty");
const string winSxSModuleDirectory = @"PowerShell\Modules";
const string winLegacyModuleDirectory = @"WindowsPowerShell\Modules";
@@ -736,7 +737,7 @@ namespace System.Management.Automation
string hklmModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine);
string hkcuModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User);
string legacyPersonalModulePath = personalModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory);
- string legacyProgramFilesModulePath = programFilesModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory);
+ string legacyProgramFilesModulePath = sharedModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory);
return (!string.IsNullOrEmpty(hklmModulePath) && currentProcessModulePath.IndexOf(hklmModulePath, StringComparison.OrdinalIgnoreCase) != -1) ||
(!string.IsNullOrEmpty(hkcuModulePath) && currentProcessModulePath.IndexOf(hkcuModulePath, StringComparison.OrdinalIgnoreCase) != -1) ||
@@ -747,7 +748,7 @@ namespace System.Management.Automation
// The sxs personal and shared module paths are only meaningful for sxs ps, so if they appear
// in the current process module path, it's likely the running ps was started from a sxs ps.
string sxsPersonalModulePath = personalModulePath.Replace(winLegacyModuleDirectory, winSxSModuleDirectory);
- string sxsProgramFilesModulePath = programFilesModulePath.Replace(winLegacyModuleDirectory, winSxSModuleDirectory);
+ string sxsProgramFilesModulePath = sharedModulePath.Replace(winLegacyModuleDirectory, winSxSModuleDirectory);
return currentProcessModulePath.IndexOf(sxsPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 ||
currentProcessModulePath.IndexOf(sxsProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1;
@@ -759,7 +760,7 @@ namespace System.Management.Automation
/// show up in current process module path. It doesn't make sense for B to load modules from
/// A's pshome module path, so remove it in such case.
///
- private static string RemoveSxSPsHomeModulePath(string currentProcessModulePath)
+ private static string RemoveSxSPsHomeModulePath(string currentProcessModulePath, string personalModulePath, string sharedModulePath, string psHomeModulePath)
{
#if UNIX
const string powershellExeName = "powershell";
@@ -772,17 +773,25 @@ namespace System.Management.Automation
foreach (var path in currentProcessModulePath.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
{
string trimedPath = path.Trim().TrimEnd(Path.DirectorySeparatorChar);
- if (trimedPath == string.Empty || trimedPath.IndexOfAny(invalidPathChars) != -1)
+ if (trimedPath.IndexOfAny(invalidPathChars) != -1 || !Path.IsPathRooted(trimedPath))
{
- // Path contains invalid characters. Ignore it.
+ // Path contains invalid characters or it's not an absolute path. Ignore it.
continue;
}
- string psExePath = Path.Combine(Path.GetDirectoryName(trimedPath), powershellExeName);
- if (File.Exists(psExePath))
+ if (!trimedPath.Equals(personalModulePath, StringComparison.OrdinalIgnoreCase) &&
+ !trimedPath.Equals(sharedModulePath, StringComparison.OrdinalIgnoreCase) &&
+ !trimedPath.Equals(psHomeModulePath, StringComparison.OrdinalIgnoreCase) &&
+ trimedPath.EndsWith("Modules", StringComparison.OrdinalIgnoreCase))
{
- // Path is a PSHome module path. Ignore it.
- continue;
+ string parentDir = Path.GetDirectoryName(trimedPath);
+ string psExePath = Path.Combine(parentDir, powershellExeName);
+ string psDepsPath = Path.Combine(parentDir, "powershell.deps.json");
+ if (File.Exists(psExePath) && File.Exists(psDepsPath))
+ {
+ // Path is a PSHome module path from a different powershell core instance. Ignore it.
+ continue;
+ }
}
if (modulePathString.Length > 0)
@@ -803,8 +812,8 @@ namespace System.Management.Automation
public static string GetModulePath(string currentProcessModulePath, string hklmMachineModulePath, string hkcuUserModulePath)
{
string personalModulePath = GetPersonalModulePath();
- string programFilesModulePath = GetDscModulePath(); // aka location
- string psHomeModulePath = GetSystemwideModulePath(); // $PSHome\Modules location
+ string sharedModulePath = GetSharedModulePath(); // aka location
+ string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location
#if CORECLR
bool runningSxS = Platform.IsInbox ? false : true;
@@ -812,7 +821,7 @@ namespace System.Management.Automation
bool runningSxS = false;
#endif
if (!string.IsNullOrEmpty(currentProcessModulePath) &&
- NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningSxS))
+ NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, sharedModulePath, runningSxS))
{
// Clear the current process module path in the following cases
// - start sxs ps on windows [machine-wide env:psmodulepath will influence]
@@ -839,7 +848,7 @@ namespace System.Management.Automation
currentProcessModulePath += Path.PathSeparator;
if (string.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist
{
- currentProcessModulePath += CombineSystemModulePaths(); // += (DscModulePath + $PSHome\Modules)
+ currentProcessModulePath += CombineSystemModulePaths(); // += (SharedModulePath + $PSHome\Modules)
}
else
{
@@ -851,7 +860,7 @@ namespace System.Management.Automation
else if (runningSxS) // The running powershell is an SxS PS instance
{
// When SxS PS instance A starts SxS PS instance B, A's PSHome module path might be inherited by B. We need to remove that path from B
- currentProcessModulePath = RemoveSxSPsHomeModulePath(currentProcessModulePath);
+ currentProcessModulePath = RemoveSxSPsHomeModulePath(currentProcessModulePath, personalModulePath, sharedModulePath, psHomeModulePath);
string personalModulePathToUse = string.IsNullOrEmpty(hkcuUserModulePath) ? personalModulePath : hkcuUserModulePath;
string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath;
@@ -869,7 +878,7 @@ namespace System.Management.Automation
{
if (!(hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))
{
- // before returning, use heuristic to conditionally add programFilesModulePath
+ // before returning, use heuristic to conditionally add location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules
{
@@ -881,7 +890,7 @@ namespace System.Management.Automation
psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath);
}
#endif
- return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
+ return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
@@ -896,11 +905,11 @@ namespace System.Management.Automation
(hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) ||
(hkcuUserModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase)))
{
- // before returning, use heuristic to conditionally add programFilesModulePath
+ // before returning, use heuristic to conditionally add location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules
{
- return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
+ return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
@@ -916,15 +925,15 @@ namespace System.Management.Automation
{
if (hkcuUserModulePath.Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))
{
- currentProcessModulePath = hkcuUserModulePath + Path.PathSeparator + CombineSystemModulePaths(); // = EVT.User + (DscModulePath + $PSHome\Modules)
+ currentProcessModulePath = hkcuUserModulePath + Path.PathSeparator + CombineSystemModulePaths(); // = EVT.User + (SharedModulePath + $PSHome\Modules)
}
else
{
- // before returning, use heuristic to conditionally add programFilesModulePath
+ // before returning, use heuristic to conditionally add location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules
{
- return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
+ return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
@@ -932,11 +941,11 @@ namespace System.Management.Automation
}
else // EVT.User does Not exist
{
- // before returning, use heuristic to conditionally add programFilesModulePath
+ // before returning, use heuristic to conditionally add location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules
{
- return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
+ return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
// Neither key is set so go with what the environment variable is already set to
@@ -950,7 +959,7 @@ namespace System.Management.Automation
int indexOfPSHomeModulePath = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
// 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, programFilesModulePath, indexOfPSHomeModulePath);
+ currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, indexOfPSHomeModulePath);
return currentProcessModulePath;
}
@@ -1020,7 +1029,7 @@ namespace System.Management.Automation
if (includeSystemModulePath)
{
- var processedPath = ProcessOneModulePath(context, GetSystemwideModulePath(), processedPathSet);
+ var processedPath = ProcessOneModulePath(context, GetPSHomeModulePath(), processedPathSet);
if (processedPath != null)
yield return processedPath;
}
diff --git a/test/powershell/enginecore/ModulePath.Tests.ps1 b/test/powershell/enginecore/ModulePath.Tests.ps1
index 0c33e15ee..12ad0b425 100644
--- a/test/powershell/enginecore/ModulePath.Tests.ps1
+++ b/test/powershell/enginecore/ModulePath.Tests.ps1
@@ -20,6 +20,15 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
$expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES")
}
$expectedSystemPath = Join-Path -Path $PSHOME -ChildPath '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 "powershell.deps.json"
+
+ New-Item -Path $fakePSHome -ItemType Directory > $null
+ New-Item -Path $fakePSHomeModuleDir -ItemType Directory > $null
}
BeforeEach {
@@ -30,6 +39,7 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
$env:PSMODULEPATH = $originalModulePath
}
+
It "validate sxs module path" {
$env:PSMODULEPATH = ""
@@ -38,8 +48,49 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
$paths = $defaultModulePath -split [System.IO.Path]::PathSeparator
$paths.Count | Should Be 3
- $paths[0] | Should Be $expectedUserPath
- $paths[1] | Should Be $expectedSharedPath
- $paths[2] | Should Be $expectedSystemPath
+ $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
+ }
+
+
+ It "ignore pshome module path derived from a different powershell core instance" -Skip:(!$IsCoreCLR) {
+
+ ## Create 'powershell' and 'powershell.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 core instance should be ignored
+ $env:PSMODULEPATH = $fakePSHomeModuleDir
+ $newModulePath = & $powershell -nopro -c '$env:PSMODULEPATH'
+ $paths = $newModulePath -split [System.IO.Path]::PathSeparator
+
+ $paths.Count | Should Be 3
+ $paths[0] | Should Be $expectedUserPath
+ $paths[1] | Should Be $expectedSharedPath
+ $paths[2] | Should Be $expectedSystemPath
+
+ } finally {
+
+ ## Remove 'powershell' and 'powershell.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 core instance parent" -Skip:(!$IsCoreCLR) {
+
+ ## non-pshome module path derived from another powershell core 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 Be $true
+ $paths -contains $customeModules | Should Be $true
}
}