Merge pull request #2265 from daxian-dbw/improve-modulepath

Improve ModulePath calculation and refactor related method names
This commit is contained in:
Mike Richmond authored and GitHub committed 2016-09-14 17:22:47 -07:00
commit d3a49c2a54
3 files changed
+112 -52

No files matched your search

@@ -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.
@@ -542,13 +542,13 @@ namespace System.Management.Automation
}
/// <summary>
/// Gets the default system-wide module path.
/// Gets the PSHome module path, as known as the "system wide module path" in windows powershell.
/// </summary>
/// <returns>The default system wide module path</returns>
internal static string GetSystemwideModulePath()
/// <returns>The PSHome module path</returns>
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;
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
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
/// <returns></returns>
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.
/// </summary>
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.
/// </summary>
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 <Program Files> location
string psHomeModulePath = GetSystemwideModulePath(); // $PSHome\Modules location
string sharedModulePath = GetSharedModulePath(); // aka <Program Files> 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 <presence of Windows module path> heuristic to conditionally add programFilesModulePath
// before returning, use <presence of Windows module path> heuristic to conditionally add <Program Files> location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> 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 = <scenario 4> = '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 <presence of Windows module path> heuristic to conditionally add programFilesModulePath
// before returning, use <presence of Windows module path> heuristic to conditionally add <Program Files> location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = '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 <presence of Windows module path> heuristic to conditionally add programFilesModulePath
// before returning, use <presence of Windows module path> heuristic to conditionally add <Program Files> location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = '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 <presence of Windows module path> heuristic to conditionally add programFilesModulePath
// before returning, use <presence of Windows module path> heuristic to conditionally add <Program Files> location
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = '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 <Program Files> location to the end;
// if $PSHome\Modules IS found (psHomePosition >= 0) - insert <Program Files> 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;
}
@@ -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
}
}