From 3ff19e1d8095d4508eec36d9d92462345033f61b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 10 Aug 2016 10:37:42 -0700 Subject: [PATCH 1/9] Update module path calculation logic to support side-by-side. Use 'Path.PathSeparator' instead of ';' to separate paths, so that it uses ';' on windows and ':' on unix. Also refactor some code as part of the change. --- .../CoreCLR/CorePsExtensions.cs | 203 ++++++--------- .../CoreCLR/CorePsPlatform.cs | 118 ++++++++- .../engine/CommandDiscovery.cs | 4 +- .../engine/Modules/ModuleIntrinsics.cs | 243 +++++++++++------- .../engine/PropertyAccessor.cs | 14 +- .../engine/SessionState.cs | 6 +- .../engine/Utils.cs | 29 ++- .../engine/hostifaces/HostUtilities.cs | 4 + .../fanin/InitialSessionStateProvider.cs | 2 +- .../namespaces/FileSystemProvider.cs | 2 +- .../security/SecuritySupport.cs | 7 +- 11 files changed, 386 insertions(+), 246 deletions(-) diff --git a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs index 657475c06..d07a04d95 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs @@ -741,6 +741,7 @@ namespace System.Management.Automation #endregion Reflection and Type related extensions #region Environment Extensions + // TODO:CORECLR - Environment Extensions need serious work to refine. internal enum EnvironmentVariableTarget { @@ -824,37 +825,7 @@ namespace System.Management.Automation public static string GetEnvironmentVariable(string variable) { - string value = System.Environment.GetEnvironmentVariable(variable); - - // Porting note: if not otherwise defined, map Windows environment - // variables to their corresponding Linux counterparts - if (!Platform.IsWindows && String.IsNullOrEmpty(value)) - { - switch (variable) - { - case "OS": - return "Linux"; - - case "COMPUTERNAME": - return System.Environment.GetEnvironmentVariable("HOSTNAME"); - - case "USERNAME": - return System.Environment.GetEnvironmentVariable("USER"); - - case "HOMEPATH": - case "USERPROFILE": - return System.Environment.GetEnvironmentVariable("HOME"); - - case "TMP": - case "TEMP": - return System.Environment.GetEnvironmentVariable("TMPDIR"); - - default: - break; - } - } - - return value; + return System.Environment.GetEnvironmentVariable(variable); } public static IDictionary GetEnvironmentVariables() @@ -896,7 +867,6 @@ namespace System.Management.Automation #if UNIX return null; #else - if( target == EnvironmentVariableTarget.Machine) { using (RegistryKey environmentKey = @@ -957,7 +927,6 @@ namespace System.Management.Automation #if UNIX return null; #else - if (target == EnvironmentVariableTarget.Machine) { using (RegistryKey environmentKey = @@ -986,31 +955,6 @@ namespace System.Management.Automation #region Property_Extensions - internal static string WinGetUserDomainName() - { - StringBuilder domainName = new StringBuilder(1024); - uint domainNameLen = (uint)domainName.Capacity; - - byte ret = Win32Native.GetUserNameEx(Win32Native.NameSamCompatible, domainName, ref domainNameLen); - if (ret == 1) - { - string samName = domainName.ToString(); - int index = samName.IndexOf('\\'); - if (index != -1) - { - return samName.Substring(0, index); - } - } - else - { - int errorCode = Marshal.GetLastWin32Error(); - throw new InvalidOperationException(Win32Native.GetMessage(errorCode)); - } - - // Cannot use LookupAccountNameW to get DomainName because 'GetUserName' is not available in CSS and thus we cannot get the account. - throw new InvalidOperationException(CoreClrStubResources.CannotGetDomainName); - } - /// /// UserDomainName /// @@ -1018,15 +962,11 @@ namespace System.Management.Automation { get { - if (Platform.IsWindows) - { - return WinGetUserDomainName(); - } - else - { - return Platform.NonWindowsGetDomainName(); - } - +#if UNIX + return Platform.NonWindowsGetDomainName(); +#else + return WinGetUserDomainName(); +#endif } } @@ -1040,20 +980,7 @@ namespace System.Management.Automation #if UNIX return Platform.Unix.UserName; #else - StringBuilder domainName = new StringBuilder(1024); - uint domainNameLen = (uint)domainName.Capacity; - - byte ret = Win32Native.GetUserNameEx(Win32Native.NameSamCompatible, domainName, ref domainNameLen); - if (ret == 1) - { - string samName = domainName.ToString(); - int index = samName.IndexOf('\\'); - if (index != -1) - { - return samName.Substring(index + 1); - } - } - return string.Empty; + return WinGetUserName(); #endif } } @@ -1078,45 +1005,19 @@ namespace System.Management.Automation { if (s_os == null) { - if (Platform.IsWindows) - { - s_os = WinOSVersion; - } - else - { - // TODO:PSL use P/Invoke to provide proper version - - // Porting note: cannot put this in CorePsPlatform since - // System.Management.Automation.Environment only exists in CoreCLR - // builds of monad. - s_os = new Environment.OperatingSystem(new Version(1, 0, 0, 0), ""); - } +#if UNIX + // TODO:PSL use P/Invoke to provide proper version + // OSVersion will be back in CoreCLR 1.1 + s_os = new Environment.OperatingSystem(new Version(1, 0, 0, 0), ""); +#else + s_os = WinGetOSVersion(); +#endif } return s_os; } } private static volatile OperatingSystem s_os; - /// - /// Windows OSVersion implementation - /// - private static OperatingSystem WinOSVersion - { - get - { - Win32Native.OSVERSIONINFOEX osviex = new Win32Native.OSVERSIONINFOEX(); - osviex.OSVersionInfoSize = Marshal.SizeOf(osviex); - if (!Win32Native.GetVersionEx(ref osviex)) - { - int errorCode = Marshal.GetLastWin32Error(); - throw new Win32Exception(errorCode); - } - - Version v = new Version(osviex.MajorVersion, osviex.MinorVersion, osviex.BuildNumber, (osviex.ServicePackMajor << 16) | osviex.ServicePackMinor); - return new OperatingSystem(v, osviex.CSDVersion); - } - } - #endregion Property_Extensions #region SpecialFolder_Extensions @@ -1242,13 +1143,79 @@ namespace System.Management.Automation #endregion SpecialFolder_Extensions - #region NativeMethods + #region WinPlatform_Specific_Methods +#if !UNIX + /// + /// Windows UserDomainName implementation + /// + private static string WinGetUserDomainName() + { + StringBuilder domainName = new StringBuilder(1024); + uint domainNameLen = (uint)domainName.Capacity; + + byte ret = Win32Native.GetUserNameEx(Win32Native.NameSamCompatible, domainName, ref domainNameLen); + if (ret == 1) + { + string samName = domainName.ToString(); + int index = samName.IndexOf('\\'); + if (index != -1) + { + return samName.Substring(0, index); + } + } + else + { + int errorCode = Marshal.GetLastWin32Error(); + throw new InvalidOperationException(Win32Native.GetMessage(errorCode)); + } + + // Cannot use LookupAccountNameW to get DomainName because 'GetUserName' is not available in CSS and thus we cannot get the account. + throw new InvalidOperationException(CoreClrStubResources.CannotGetDomainName); + } + + /// + /// Windows UserName implementation + /// + private static string WinGetUserName() + { + StringBuilder domainName = new StringBuilder(1024); + uint domainNameLen = (uint)domainName.Capacity; + + byte ret = Win32Native.GetUserNameEx(Win32Native.NameSamCompatible, domainName, ref domainNameLen); + if (ret == 1) + { + string samName = domainName.ToString(); + int index = samName.IndexOf('\\'); + if (index != -1) + { + return samName.Substring(index + 1); + } + } + return string.Empty; + } + + /// + /// Windows OSVersion implementation + /// + private static OperatingSystem WinGetOSVersion() + { + Win32Native.OSVERSIONINFOEX osviex = new Win32Native.OSVERSIONINFOEX(); + osviex.OSVersionInfoSize = Marshal.SizeOf(osviex); + if (!Win32Native.GetVersionEx(ref osviex)) + { + int errorCode = Marshal.GetLastWin32Error(); + throw new Win32Exception(errorCode); + } + + Version v = new Version(osviex.MajorVersion, osviex.MinorVersion, osviex.BuildNumber, (osviex.ServicePackMajor << 16) | osviex.ServicePackMinor); + return new OperatingSystem(v, osviex.CSDVersion); + } /// /// DllImport uses the ApiSet dll that is available on CSS, since this code /// will only be included when building targeting CoreCLR. /// - internal static class Win32Native + private static class Win32Native { internal const int NameSamCompatible = 2; // EXTENDED_NAME_FORMAT - NameSamCompatible @@ -1307,8 +1274,8 @@ namespace System.Management.Automation } } } - - #endregion NativeMethods +#endif + #endregion WinPlatform_Specific_Methods #region NestedTypes diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 8c681ae25..c6d775f63 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -2,13 +2,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Collections; using System.Collections.Generic; -using System.Text; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; @@ -80,6 +74,105 @@ namespace System.Management.Automation #endif } } + + /// + /// True if the underlying system is NanoServer. + /// + public static bool IsNanoServer + { + get + { +#if !CORECLR + return false; +#elif UNIX + return false; +#else + if (_isNanoServer.HasValue) { return _isNanoServer.Value; } + + _isNanoServer = false; + using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels")) + { + if (regKey != null) + { + object value = regKey.GetValue("NanoServer"); + if (value != null && regKey.GetValueKind("NanoServer") == RegistryValueKind.DWord) + { + _isNanoServer = (int)value == 1; + } + } + } + + return _isNanoServer.Value; +#endif + } + } + + /// + /// True if the underlying system is IoT. + /// + public static bool IsIoT + { + get + { +#if !CORECLR + return false; +#elif UNIX + return false; +#else + if (_isIoT.HasValue) { return _isIoT.Value; } + + _isIoT = false; + using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion")) + { + if (regKey != null) + { + object value = regKey.GetValue("ProductName"); + if (value != null && regKey.GetValueKind("ProductName") == RegistryValueKind.String) + { + _isIoT = string.Equals("IoTUAP", (string)value, StringComparison.OrdinalIgnoreCase); + } + } + } + + return _isIoT.Value; +#endif + } + } + +#if CORECLR + /// + /// True if it is the inbox powershell for NanoServer or IoT. + /// + internal static bool IsInbox + { + get + { +#if UNIX + return false; +#else + if (_isInbox.HasValue) { return _isInbox.Value; } + + _isInbox = false; + if (IsNanoServer || IsIoT) + { + _isInbox = string.Equals( + Utils.GetApplicationBase(Utils.DefaultPowerShellShellID), + Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID), + StringComparison.OrdinalIgnoreCase); + } + + return _isInbox.Value; +#endif + } + } + +#if !UNIX + private static bool? _isNanoServer = null; + private static bool? _isIoT = null; + private static bool? _isInbox = null; +#endif + +#endif // format files internal static List FormatFileNames = new List @@ -97,6 +190,19 @@ namespace System.Management.Automation "WSMan.format.ps1xml" }; + /// + /// Some common environment variables used in PS have different + /// names in different OS platforms + /// + internal static class CommonEnvVariableNames + { +#if UNIX + internal const string Home = "HOME"; +#else + internal const string Home = "USERPROFILE"; +#endif + } + /// /// X Desktop Group configuration type enum. /// diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index 0f2f24e73..4142b52a5 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -1548,7 +1548,7 @@ namespace System.Management.Automation if (_pathCacheKey != null) { - string[] tokenizedPath = _pathCacheKey.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + string[] tokenizedPath = _pathCacheKey.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries); _cachedPath = new Collection(); foreach (string directory in tokenizedPath) @@ -1633,7 +1633,7 @@ namespace System.Management.Automation lock (s_lockObject) { s_cachedPathExtCollection = pathExt != null - ? pathExt.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries) + ? pathExt.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries) : Utils.EmptyArray(); s_cachedPathExtCollectionWithPs1 = new string[s_cachedPathExtCollection.Length + 1]; s_cachedPathExtCollectionWithPs1[0] = StringLiterals.PowerShellScriptFileExtension; diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 5f205bce6..ea441ed2a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -8,6 +8,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using Microsoft.PowerShell.Commands; using System.Linq; +using System.Text; using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -529,26 +530,15 @@ namespace System.Management.Automation /// /// Gets the personal module path - /// (i.e. C:\Users\lukasza\Documents\WindowsPowerShell\modules, or - /// ~/.powershell/Modules on Linux) /// /// personal module path internal static string GetPersonalModulePath() { - if (Platform.IsWindows) - { - string personalModuleRoot = Path.Combine( - Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - Utils.ProductNameForDirectory), - Utils.ModuleDirectory); - return personalModuleRoot; - } - else - { - string personalModuleRoot = Platform.SelectProductNameForDirectory(Platform.XDG_Type.MODULES); - return personalModuleRoot; - } +#if UNIX + return Platform.SelectProductNameForDirectory(Platform.XDG_Type.MODULES); +#else + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Utils.ModuleDirectory); +#endif } /// @@ -559,39 +549,26 @@ namespace System.Management.Automation { if (s_systemWideModulePath != null) return s_systemWideModulePath; - - // There is no runspace config so we use the default string - string shellId = Utils.DefaultPowerShellShellID; - - // Now figure out what $PSHOME is. - // This depends on the shellId. If we cannot read the application base - // registry key, set the variable to empty string - string psHome = null; + try { - psHome = Utils.GetApplicationBase(shellId); - } - catch (System.Security.SecurityException) - { - } - - if (!string.IsNullOrEmpty(psHome)) - { - // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed - // This happens because ModuleTable uses Path as the key and CBS installer - // expands the path to include "SysWOW64" (for - // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase). - // Because of this, the module that is getting loaded during startup (through LocalRunspace) - // is using "SysWow64" in the key. Later, when Import-Module is called, it loads the - // module using ""System32" in the key. - - // Porting note: psHome cannot be lower-cased on case sensitive file systems - if (Platform.IsWindows) + string psHome = Utils.GetApplicationBase(Utils.DefaultPowerShellShellID); + if (!string.IsNullOrEmpty(psHome)) { + // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed + // This happens because ModuleTable uses Path as the key and CBS installer + // expands the path to include "SysWOW64" (for + // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase). + // Because of this, the module that is getting loaded during startup (through LocalRunspace) + // is using "SysWow64" in the key. Later, when Import-Module is called, it loads the + // module using ""System32" in the key. +#if !UNIX psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\"); +#endif + Interlocked.CompareExchange(ref s_systemWideModulePath, Path.Combine(psHome, "Modules"), null); } - Interlocked.CompareExchange(ref s_systemWideModulePath, Path.Combine(psHome, Utils.ModuleDirectory), null); } + catch (System.Security.SecurityException) { } return s_systemWideModulePath; } @@ -604,18 +581,17 @@ namespace System.Management.Automation /// internal static string GetDscModulePath() { - if (!Platform.IsWindows) - { - return string.Empty; - } - +#if UNIX + return string.Empty; +#else string dscModulePath = null; string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); if (!string.IsNullOrEmpty(programFilesPath)) { - dscModulePath = Path.Combine(programFilesPath, Utils.DscModuleDirectory); + dscModulePath = Path.Combine(programFilesPath, Utils.ModuleDirectory); } return dscModulePath; +#endif } /// @@ -625,17 +601,20 @@ namespace System.Management.Automation /// private static string CombineSystemModulePaths() { - string psSystemModulePath = GetSystemwideModulePath(); - string dscSystemModulePath = GetDscModulePath(); + string systemModulePath = GetSystemwideModulePath(); + string commonModulePath = GetDscModulePath(); - if (psSystemModulePath != null && dscSystemModulePath != null) + bool isSystemPathNullOrEmpty = string.IsNullOrEmpty(systemModulePath); + bool isCommonPathNullOrEmpty = string.IsNullOrEmpty(commonModulePath); + + if (!isSystemPathNullOrEmpty && !isCommonPathNullOrEmpty) { - return (dscSystemModulePath + ";" + psSystemModulePath); + return (commonModulePath + Path.PathSeparator + systemModulePath); } - if (psSystemModulePath != null || dscSystemModulePath != null) + if (!isSystemPathNullOrEmpty || !isCommonPathNullOrEmpty) { - return (psSystemModulePath ?? dscSystemModulePath); + return isSystemPathNullOrEmpty ? commonModulePath : systemModulePath; } return null; @@ -664,11 +643,11 @@ namespace System.Management.Automation Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); int pos = 0; // position of the current substring in pathToScan - string[] substrings = pathToScan.Split(new char[] { ';' }, StringSplitOptions.None); // we want to process empty entries - string goodPathToLookFor = pathToLookFor.Trim().TrimEnd('\\'); // trailing backslashes and white-spaces will mess up equality comparison + string[] substrings = pathToScan.Split(Utils.Separators.PathSeparator, StringSplitOptions.None); // we want to process empty entries + string goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison foreach (string substring in substrings) { - string goodSubstring = substring.Trim().TrimEnd('\\'); // trailing backslashes and white-spaces will mess up equality comparison + string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } @@ -698,29 +677,28 @@ namespace System.Management.Automation Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); - System.Text.StringBuilder result = new System.Text.StringBuilder(basePath); - - char[] semicolonSeparator = new char[] { ';' }; + StringBuilder result = new StringBuilder(basePath); + if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths { - foreach (string subPathToAdd in pathToAdd.Split(semicolonSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + foreach (string subPathToAdd in pathToAdd.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) { int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly if (-1 == position) // subPathToAdd not found - add it { if (-1 == insertPosition) // append subPathToAdd to the end { - bool resultHasEndingSemicolon = false; - if (result.Length > 0) resultHasEndingSemicolon = (result[result.Length - 1] == ';'); + bool endsWithPathSeparator = false; + if (result.Length > 0) endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); - if (resultHasEndingSemicolon) + if (endsWithPathSeparator) result.Append(subPathToAdd); else - result.Append(";" + subPathToAdd); + result.Append(Path.PathSeparator + subPathToAdd); } else // insert at the requested location (this is used by DSC ( location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User)) { - result.Insert(insertPosition, subPathToAdd + ";"); + result.Insert(insertPosition, subPathToAdd + Path.PathSeparator); } } } @@ -728,6 +706,75 @@ namespace System.Management.Automation return result.ToString(); } + + private static bool NeedToClearCurrentProcessModulePath(string currentProcessModulePath, string personalModulePath, string programFilesModulePath, bool runningOps) + { +#if UNIX + return false; +#else + Dbg.Assert(!string.IsNullOrEmpty(currentProcessModulePath), "caller makes sure it's not null or empty"); + 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"); + + const string winOpsModuleDirectory = @"PowerShell\Modules"; + const string winLegacyModuleDirectory = @"WindowsPowerShell\Modules"; + + if (runningOps) + { + string hklmModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine); + string hkcuModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User); + string legacyPersonalModulePath = personalModulePath.Replace(winOpsModuleDirectory, winLegacyModuleDirectory); + string legacyProgramFilesModulePath = programFilesModulePath.Replace(winOpsModuleDirectory, winLegacyModuleDirectory); + + return (!string.IsNullOrEmpty(hklmModulePath) && currentProcessModulePath.IndexOf(hklmModulePath, StringComparison.OrdinalIgnoreCase) != -1) || + (!string.IsNullOrEmpty(hkcuModulePath) && currentProcessModulePath.IndexOf(hkcuModulePath, StringComparison.OrdinalIgnoreCase) != -1) || + currentProcessModulePath.IndexOf(legacyPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || + currentProcessModulePath.IndexOf(legacyProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; + } + + string opsPersonalModulePath = personalModulePath.Replace(winLegacyModuleDirectory, winOpsModuleDirectory); + string opsProgramFilesModulePath = programFilesModulePath.Replace(winLegacyModuleDirectory, winOpsModuleDirectory); + + return currentProcessModulePath.IndexOf(opsPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || + currentProcessModulePath.IndexOf(opsProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; +#endif + } + + private static string RemoveOpsPsHomeModulePath(string currentProcessModulePath) + { +#if UNIX + const string powershellExeName = "powershell"; +#else + const string powershellExeName = "powershell.exe"; +#endif + StringBuilder modulePathString = new StringBuilder(currentProcessModulePath.Length); + char[] invalidPathChars = Path.GetInvalidPathChars(); + + 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) + { + // Path contains invalid characters. Ignore it. + continue; + } + + string psExePath = Path.Combine(Path.GetDirectoryName(trimedPath), powershellExeName); + if (File.Exists(psExePath)) + { + // Path is a PSHome module path. Ignore it. + continue; + } + + if (modulePathString.Length > 0) + { + modulePathString.Append(Path.PathSeparator); + } + modulePathString.Append(trimedPath); + } + + return modulePathString.ToString(); + } /// /// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate. Note - because these @@ -736,23 +783,41 @@ namespace System.Management.Automation /// public static string GetModulePath(string currentProcessModulePath, string hklmMachineModulePath, string hkcuUserModulePath) { + string personalModulePath = GetPersonalModulePath(); string programFilesModulePath = GetDscModulePath(); // aka location - string psHomeModulePath = Environment.ExpandEnvironmentVariables(GetSystemwideModulePath()); // $PSHome\Modules location + string psHomeModulePath = GetSystemwideModulePath(); // $PSHome\Modules location + +#if CORECLR + bool runningOps = Platform.IsInbox ? false : true; +#else + bool runningOps = false; +#endif + if (NeedToClearCurrentProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningOps)) + { + // Clear the current process module path in the following cases + // - start ops on windows [machine-wide env:psmodulepath will influence] + // - start ops from full ps + // - start ops from inbox nano/iot ps + // - start full ps from ops + // - start inbox nano/iot ps from ops + currentProcessModulePath = null; + } + // If the variable isn't set, then set it to the default value if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case { // Handle the default case... - if (String.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to location + if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to location { - currentProcessModulePath = GetPersonalModulePath(); // = SpecialFolder.MyDocuments + Utils.ProductNameForDirectory + Utils.ModuleDirectory + currentProcessModulePath = personalModulePath; // = SpecialFolder.MyDocuments + Utils.ProductNameForDirectory + Utils.ModuleDirectory } else // EVT.User exists -> set to EVT.User { currentProcessModulePath = hkcuUserModulePath; // = EVT.User } - currentProcessModulePath += ';'; - if (String.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist + currentProcessModulePath += Path.PathSeparator; + if (string.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist { currentProcessModulePath += CombineSystemModulePaths(); // += (DscModulePath + $PSHome\Modules) } @@ -761,18 +826,21 @@ namespace System.Management.Automation currentProcessModulePath += hklmMachineModulePath; // += EVT.Machine } } - else // EVT.Process exists + // EVT.Process exists + // Now handle the case where the environment variable is already set. + else if (runningOps) // The running powershell is an OPS instance { - // Now handle the case where the environment variable is already set. + // When OPS instance A starts OPS instance B, A's PSHome module path might be inherited by B. We need to remove that path from B + currentProcessModulePath = RemoveOpsPsHomeModulePath(currentProcessModulePath); - // CoreCLR PowerShell on Windows has a Modules folder in the the application base - // path which contains the built-in modules It must be in the front of the path no - // matter what, regardless of inherited path. -#if CORECLR && !UNIX - // TODO: #1184 will resolve this work-around - currentProcessModulePath = AddToPath(currentProcessModulePath, GetSystemwideModulePath(), 0); -#endif + string personalModulePathToUse = string.IsNullOrEmpty(hkcuUserModulePath) ? personalModulePath : hkcuUserModulePath; + string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath; + currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0); + currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, -1); + } + else // The running powershell is Full PS or inbox Core PS + { // If there is no personal path key, then if the env variable doesn't match the system variable, // the user modified it somewhere, else prepend the default personel module path if (hklmMachineModulePath != null) // EVT.Machine exists @@ -789,8 +857,7 @@ namespace System.Management.Automation // for bug 6678623, if we are running wow64 process (x86 32-bit process on 64-bit (amd64) OS), then ensure that exists in currentProcessModulePath / return value if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) { - string userModulePath = GetPersonalModulePath(); - currentProcessModulePath = AddToPath(currentProcessModulePath, userModulePath, psHomePosition); + currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePath, psHomePosition); psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); } #endif @@ -799,12 +866,12 @@ namespace System.Management.Automation return null; } - currentProcessModulePath = GetPersonalModulePath() + ';' + hklmMachineModulePath; // + EVT.Machine + inserted later in this function + currentProcessModulePath = personalModulePath + Path.PathSeparator + hklmMachineModulePath; // + EVT.Machine + inserted later in this function } else // EVT.User exists { // PSModulePath is designed to have behaviour like 'Path' var in a sense that EVT.User + EVT.Machine are merged to get final value of PSModulePath - string combined = string.Concat(hkcuUserModulePath, ';', hklmMachineModulePath); // EVT.User + EVT.Machine + string combined = string.Concat(hkcuUserModulePath, Path.PathSeparator, hklmMachineModulePath); // EVT.User + EVT.Machine if (!((combined).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) || (hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) || (hkcuUserModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))) @@ -829,7 +896,7 @@ namespace System.Management.Automation { if (hkcuUserModulePath.Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase)) { - currentProcessModulePath = hkcuUserModulePath + ';' + CombineSystemModulePaths(); // = EVT.User + (DscModulePath + $PSHome\Modules) + currentProcessModulePath = hkcuUserModulePath + Path.PathSeparator + CombineSystemModulePaths(); // = EVT.User + (DscModulePath + $PSHome\Modules) } else { @@ -923,13 +990,13 @@ namespace System.Management.Automation if (!string.IsNullOrWhiteSpace(modulePathString)) { - foreach (string envPath in modulePathString.Split(Utils.Separators.Semicolon, StringSplitOptions.RemoveEmptyEntries)) + foreach (string envPath in modulePathString.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) { var processedPath = ProcessOneModulePath(context, envPath, processedPathSet); if (processedPath != null) yield return processedPath; } - } + } if (includeSystemModulePath) { diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index 15203fa24..7cdd7e3bb 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -36,17 +36,9 @@ namespace System.Management.Automation static ConfigPropertyAccessor() { #if CORECLR - // TODO: Update this for #1184 - //if (Platform.IsInbox()) - //{ - // Instance = new RegistryAccessor(); - //} - //else - //{ - // Instance = new JsonConfigFileAccessor(); - //} - // Remove this following line: - Instance = new JsonConfigFileAccessor(); + Instance = Platform.IsInbox + ? (ConfigPropertyAccessor) new RegistryAccessor() + : new JsonConfigFileAccessor(); #else Instance = new RegistryAccessor(); #endif diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs index 9b8fd3bdb..5ded60804 100644 --- a/src/System.Management.Automation/engine/SessionState.cs +++ b/src/System.Management.Automation/engine/SessionState.cs @@ -303,8 +303,10 @@ namespace System.Management.Automation RunspaceInit.PSHostDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); - // $HOME = %USERPROFILE% - indicate where a user's home directory is located in the file system. - string home = Environment.GetEnvironmentVariable("USERPROFILE") ?? string.Empty; + // $HOME - indicate where a user's home directory is located in the file system. + // -- %USERPROFILE% on windows + // -- %HOME% on unix + string home = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home) ?? string.Empty; v = new PSVariable(SpecialVariables.Home, home, ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope, diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 3ba32c7bf..2395d991f 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -216,6 +216,7 @@ namespace System.Management.Automation if (engineKey != null) { var result = engineKey.GetValue(RegistryStrings.MonadEngine_ApplicationBase) as string; + result = Environment.ExpandEnvironmentVariables(result); if (wantPsHome) Interlocked.CompareExchange(ref s_pshome, null, result); @@ -584,25 +585,26 @@ namespace System.Management.Automation /// /// String representing the Default shellID. /// - internal static string DefaultPowerShellShellID = "Microsoft.PowerShell"; - /// - /// String used to control directory location for PowerShell - /// - /// - /// Profile uses this to control profile loading. - /// - internal static string ProductNameForDirectory = - Platform.IsWindows ? "WindowsPowerShell" : Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); + internal const string DefaultPowerShellShellID = "Microsoft.PowerShell"; /// - /// The name of the subdirectory that contains packages. + /// This is used to construct the profile path. /// - internal static string ModuleDirectory = "Modules"; +#if CORECLR + internal static string ProductNameForDirectory = Platform.IsInbox ? "WindowsPowerShell" : "PowerShell"; +#else + internal const string ProductNameForDirectory = "WindowsPowerShell"; +#endif /// - /// The partial path to the DSC module directory + /// The subdirectory of module paths + /// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules /// - internal static string DscModuleDirectory = Path.Combine("WindowsPowerShell", "Modules"); +#if CORECLR + internal static string ModuleDirectory = Platform.IsInbox ? @"WindowsPowerShell\Modules" : Path.Combine("PowerShell", "Modules"); +#else + internal const string ModuleDirectory = @"WindowsPowerShell\Modules"; +#endif internal static string GetRegistryConfigurationPrefix() { @@ -1519,6 +1521,7 @@ namespace System.Management.Automation internal static readonly char[] Semicolon = new char[] { ';' }; internal static readonly char[] StarOrQuestion = new char[] { '*', '?' }; internal static readonly char[] ColonOrBackslash = new char[] { '\\', ':' }; + internal static readonly char[] PathSeparator = new char[] { Path.PathSeparator }; internal static readonly char[] QuoteChars = new char[] { '\'', '"' }; internal static readonly char[] Space = new char[] { ' ' }; diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 1928eed5b..324a1d7ea 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -182,8 +182,12 @@ namespace System.Management.Automation if (forCurrentUser) { +#if UNIX + basePath = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); +#else basePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); basePath = IO.Path.Combine(basePath, Utils.ProductNameForDirectory); +#endif } else { diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 21c7261ca..494827dc9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -1891,7 +1891,7 @@ namespace System.Management.Automation.Remoting } // Go through each directory in the module path - string[] modulePaths = ModuleIntrinsics.GetModulePath().Split(Utils.Separators.Semicolon); + string[] modulePaths = ModuleIntrinsics.GetModulePath().Split(Utils.Separators.PathSeparator); foreach (string path in modulePaths) { try diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 1ae3130c5..e68a286c9 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -351,7 +351,7 @@ namespace Microsoft.PowerShell.Commands if (providerInfo != null && string.IsNullOrEmpty(providerInfo.Home)) { // %USERPROFILE% - indicate where a user's home directory is located in the file system. - string homeDirectory = Environment.GetEnvironmentVariable("USERPROFILE"); + string homeDirectory = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); if (!string.IsNullOrEmpty(homeDirectory)) { diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index f2ec5051a..ffaabf398 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -211,9 +211,9 @@ namespace System.Management.Automation.Internal private static void CleanKeyParents(RegistryKey baseKey, string keyPath) { #if CORECLR - // if ( ! Platform.Inbox) // Modify this to support registry checks for inbox CoreCLR - return; -#else // Windows && FullCLR + if (!Platform.IsInbox) + return; +#endif using (RegistryKey key = baseKey.OpenSubKey(keyPath, true)) { // Verify the child key has no children @@ -248,7 +248,6 @@ namespace System.Management.Automation.Internal return; } } -#endif } internal static ExecutionPolicy GetExecutionPolicy(string shellId) From 07f9fefee850e53733cd8dbde835da5fe08bffac Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 14:22:23 -0700 Subject: [PATCH 2/9] Add the shared module path to unix (counterpart of 'Program Files\PowerShell\Modules'). Also make 'Platform.XDG_Type' and 'Platform.SelectProductNameForDirectory' unix build only, as they doesn't apply to windows. --- .../CoreCLR/CorePsPlatform.cs | 15 +++++++++++---- .../engine/Modules/ModuleIntrinsics.cs | 10 +++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index c6d775f63..57964c2fe 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -203,10 +203,11 @@ namespace System.Management.Automation #endif } +#if UNIX /// /// X Desktop Group configuration type enum. /// - public enum XDG_Type + internal enum XDG_Type { /// XDG_CONFIG_HOME/powershell CONFIG, @@ -215,7 +216,9 @@ namespace System.Management.Automation /// XDG_DATA_HOME/powershell DATA, /// XDG_DATA_HOME/powershell/Modules - MODULES, + USER_MODULES, + /// /usr/local/share/powershell/Modules + SHARED_MODULES, /// XDG_CONFIG_HOME/powershell DEFAULT } @@ -223,7 +226,7 @@ namespace System.Management.Automation /// /// function for choosing directory location of PowerShell for profile loading /// - public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) + internal static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) { //TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060 @@ -266,7 +269,7 @@ namespace System.Management.Automation return Path.Combine(xdgdatahome, "powershell"); } - case Platform.XDG_Type.MODULES: + case Platform.XDG_Type.USER_MODULES: //the user has set XDG_DATA_HOME corresponding to module path if (String.IsNullOrEmpty(xdgdatahome)) { @@ -282,6 +285,9 @@ namespace System.Management.Automation return Path.Combine(xdgdatahome, "powershell", "Modules"); } + case Platform.XDG_Type.SHARED_MODULES: + return "/usr/local/share/powershell/Modules"; + case Platform.XDG_Type.CACHE: //the user has set XDG_CACHE_HOME if (String.IsNullOrEmpty(xdgcachehome)) @@ -327,6 +333,7 @@ namespace System.Management.Automation return xdgConfigHomeDefault; } } +#endif // Platform methods prefixed NonWindows are: // - non-windows by the definition of the IsWindows method above diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index ea441ed2a..52bc75e35 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -535,7 +535,7 @@ namespace System.Management.Automation internal static string GetPersonalModulePath() { #if UNIX - return Platform.SelectProductNameForDirectory(Platform.XDG_Type.MODULES); + return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); #else return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Utils.ModuleDirectory); #endif @@ -582,7 +582,7 @@ namespace System.Management.Automation internal static string GetDscModulePath() { #if UNIX - return string.Empty; + return Platform.SelectProductNameForDirectory(Platform.XDG_Type.SHARED_MODULES); #else string dscModulePath = null; string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); @@ -707,12 +707,11 @@ namespace System.Management.Automation return result.ToString(); } - private static bool NeedToClearCurrentProcessModulePath(string currentProcessModulePath, string personalModulePath, string programFilesModulePath, bool runningOps) + private static bool NeedToClearProcessModulePath(string currentProcessModulePath, string personalModulePath, string programFilesModulePath, bool runningOps) { #if UNIX return false; #else - Dbg.Assert(!string.IsNullOrEmpty(currentProcessModulePath), "caller makes sure it's not null or empty"); 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"); @@ -792,7 +791,8 @@ namespace System.Management.Automation #else bool runningOps = false; #endif - if (NeedToClearCurrentProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningOps)) + if (!string.IsNullOrEmpty(currentProcessModulePath) && + NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningOps)) { // Clear the current process module path in the following cases // - start ops on windows [machine-wide env:psmodulepath will influence] From c27b4db3ac6367880bafcaf661bf4c77e920daca Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 14:25:32 -0700 Subject: [PATCH 3/9] Change runtime check to build check because 'Platform.XDG_Type' and 'Platform.SelectProductNameForDirectory' are make unix only --- .../host/msh/ConsoleHost.cs | 20 ++++++++----------- .../engine/Modules/AnalysisCache.cs | 9 +++++---- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 0c99e4767..da0df1159 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -179,21 +179,17 @@ namespace Microsoft.PowerShell try { string profileDir; - if (Platform.IsWindows) - { - profileDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + - @"\Microsoft\Windows\PowerShell"; +#if UNIX + profileDir = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE); +#else + profileDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + + @"\Microsoft\Windows\PowerShell"; - if (!Directory.Exists(profileDir)) - { - Directory.CreateDirectory(profileDir); - } - } - else + if (!Directory.Exists(profileDir)) { - profileDir = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE); + Directory.CreateDirectory(profileDir); } - +#endif ClrFacade.SetProfileOptimizationRoot(profileDir); } catch diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index d07bb0427..70ebe628c 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -1027,10 +1027,11 @@ namespace System.Management.Automation { s_cacheStoreLocation = Environment.GetEnvironmentVariable("PSModuleAnalysisCachePath") ?? - (Platform.IsWindows - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - @"Microsoft\Windows\PowerShell\ModuleAnalysisCache") - : Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ModuleAnalysisCache")); +#if UNIX + Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ModuleAnalysisCache"); +#else + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Windows\PowerShell\ModuleAnalysisCache"); +#endif } } From 896577e285823834a788856ad8cdf44428b929c3 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 14:26:33 -0700 Subject: [PATCH 4/9] update linux path doc --- docs/installation/linux.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/installation/linux.md b/docs/installation/linux.md index 2b9cf44b4..81806c59c 100644 --- a/docs/installation/linux.md +++ b/docs/installation/linux.md @@ -79,12 +79,13 @@ sudo installer -pkg powershell-6.0.0-alpha.8-osx.10.11-x64.pkg -target / Paths ===== -* User profiles will be read from `~/.config/powershell/profile.ps1`. -* User modules will be read from `~/.local/share/powershell/Modules` -* PSReadLine history will be recorded to `~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt` * `$PSHOME` is `/opt/microsoft/powershell/6.0.0-alpha.8/` -* Default profiles will be read from `$PSHOME/profile.ps1`. +* User profiles will be read from `~/.config/powershell/profile.ps1` +* Default profiles will be read from `$PSHOME/profile.ps1` +* User modules will be read from `~/.local/share/powershell/Modules` +* Shared modules will be read from `/usr/local/share/powershell/Modules` * Default modules will be read from `$PSHOME/Modules` +* PSReadLine history will be recorded to `~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt` The profiles respect PowerShell's per-host configuration, so the default host-specific profiles exists at `Microsoft.PowerShell_profile.ps1` in the same locations. From 753b202a759df64dfdf43dbed2ae8a3b52655643 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 14:30:02 -0700 Subject: [PATCH 5/9] Fix tests that were broken by the sxs module path change. Also fix some unreliable tests. --- test/powershell/Host/Base-Directory.Tests.ps1 | 4 +-- .../Scripting.Classes.Modules.Tests.ps1 | 2 +- .../scripting.Classes.NestedModules.tests.ps1 | 2 +- .../Classes/scripting.Classes.using.tests.ps1 | 2 +- .../Export-Csv.Tests.ps1 | 4 +-- .../Export-FormatData.Tests.ps1 | 35 ++++++++++--------- .../Out-File.Tests.ps1 | 4 +-- .../Write-Stream.Tests.ps1 | 2 +- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/test/powershell/Host/Base-Directory.Tests.ps1 b/test/powershell/Host/Base-Directory.Tests.ps1 index 11811fdb3..70f5163d3 100644 --- a/test/powershell/Host/Base-Directory.Tests.ps1 +++ b/test/powershell/Host/Base-Directory.Tests.ps1 @@ -11,8 +11,8 @@ Describe "Configuration file locations" -tags "CI","Slow" { if ($IsWindows) { $expectedCache = [IO.Path]::Combine($env:LOCALAPPDATA, "Microsoft", "Windows", "PowerShell", "StartupProfileData-NonInteractive") - $expectedModule = [IO.Path]::Combine($env:USERPROFILE, "Documents", "WindowsPowerShell", "Modules") - $expectedProfile = [io.path]::Combine($env:USERPROFILE, "Documents","WindowsPowerShell",$profileName) + $expectedModule = [IO.Path]::Combine($env:USERPROFILE, "Documents", "PowerShell", "Modules") + $expectedProfile = [io.path]::Combine($env:USERPROFILE, "Documents","PowerShell",$profileName) $expectedReadline = [IO.Path]::Combine($env:AppData, "Microsoft", "Windows", "PowerShell", "PSReadline", "ConsoleHost_history.txt") } else { $expectedCache = [IO.Path]::Combine($env:HOME, ".cache", "powershell", "StartupProfileData-NonInteractive") diff --git a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 index 5caca7d22..b9bc55132 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 @@ -26,7 +26,7 @@ Describe 'use of a module from two runspaces' -Tags "CI" { $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\)[0].FullName) if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { - $env:PSMODULEPATH += ";$resolvedTestDrivePath" + $env:PSMODULEPATH += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" } } diff --git a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 index 86ae42c12..886ea067f 100644 --- a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 @@ -31,7 +31,7 @@ Describe 'NestedModules' -Tags "CI" { $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\)[0].FullName) if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { - $env:PSMODULEPATH += ";$resolvedTestDrivePath" + $env:PSMODULEPATH += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" } } diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index d4c6fc42f..82fd31780 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -24,7 +24,7 @@ Describe 'using module' -Tags "CI" { $resolvedTestDrivePath = Split-Path ((get-childitem "${TestDrive}\$ModulePathPrefix")[0].FullName) if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { - $env:PSMODULEPATH += ";$resolvedTestDrivePath" + $env:PSMODULEPATH += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 index 0340ae3d4..f4c95abb4 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 @@ -1,6 +1,6 @@ Describe "Export-Csv" -Tags "CI" { $testObject = @("test","object","array") - $testCsv = "output.csv" + $testCsv = Join-Path -Path $TestDrive -ChildPath "output.csv" AfterEach { Remove-Item $testCsv -Force -ErrorAction SilentlyContinue @@ -51,7 +51,7 @@ Describe "Export-Csv" -Tags "CI" { It "Should have the same information when using the alias vs the cmdlet" { $testObject | Export-Csv -Path $testCsv - $aliasObject = "alias.csv" + $aliasObject = Join-Path -Path $TestDrive -ChildPath "alias.csv" $testObject | epcsv -Path $aliasObject diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 index fc50695b8..7761cf4ac 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 @@ -16,26 +16,29 @@ Describe "Export-FormatData DRT Unit Tests" -Tags "CI" { Describe "Export-FormatData" -Tags "CI" { + $testOutput = Join-Path -Path $TestDrive -ChildPath "outputfile" + + AfterEach { + Remove-Item $testOutput -Force -ErrorAction SilentlyContinue + } + Context "Check Export-FormatData can be called validly." { - It "Should be able to be called without error" { - { Get-FormatData | Export-FormatData -Path "outputfile" } | Should Not Throw - Remove-Item "outputfile" -Force -ErrorAction SilentlyContinue - } + It "Should be able to be called without error" { + { Get-FormatData | Export-FormatData -Path $testOutput } | Should Not Throw + } } Context "Check that the output is in the correct format" { - It "Should not return an empty xml file" { - Get-FormatData | Export-FormatData -Path "outputfile" - $piped = Get-Content "outputfile" - $piped | Should Not Be "" - Remove-Item "outputfile" -Force -ErrorAction SilentlyContinue - } + It "Should not return an empty xml file" { + Get-FormatData | Export-FormatData -Path $testOutput + $piped = Get-Content $testOutput + $piped | Should Not Be "" + } - It "Should have a valid xml tag at the start of the file" { - Get-FormatData | Export-FormatData -Path "outputfile" - $piped = Get-Content "outputfile" - $piped[0] | Should Be "<" - Remove-Item "outputfile" -Force -ErrorAction SilentlyContinue - } + It "Should have a valid xml tag at the start of the file" { + Get-FormatData | Export-FormatData -Path $testOutput + $piped = Get-Content $testOutput + $piped[0] | Should Be "<" + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Out-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Out-File.Tests.ps1 index cf99491fc..70e4ce1cd 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Out-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Out-File.Tests.ps1 @@ -1,6 +1,6 @@ Describe "Out-File DRT Unit Tests" -Tags "CI" { It "Should be able to write the contens into a file with -pspath" { - $tempFile = "ExposeBug928965" + $tempFile = Join-Path -Path $TestDrive -ChildPath "ExposeBug928965" { 1 | Out-File -PSPath $tempFile } | Should Not Throw $fileContents = Get-Content $tempFile $fileContents | Should be 1 @@ -8,7 +8,7 @@ Describe "Out-File DRT Unit Tests" -Tags "CI" { } It "Should be able to write the contens into a file with -pspath" { - $tempFile = "outfileAppendTest.txt" + $tempFile = Join-Path -Path $TestDrive -ChildPath "outfileAppendTest.txt" { 'This is first line.' | out-file $tempFile } | Should Not Throw { 'This is second line.' | out-file -append $tempFile } | Should Not Throw $tempFile |Should Contain "first" diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Stream.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Stream.Tests.ps1 index 4c52d3e04..7df534ae1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Stream.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Stream.Tests.ps1 @@ -1,5 +1,5 @@ Describe "Stream writer tests" -Tags "CI" { - $targetfile = "writeoutput.txt" + $targetfile = Join-Path -Path $TestDrive -ChildPath "writeoutput.txt" # A custom function is defined here do handle the debug stream dealing with the confirm prompt # that would normally From 8bb291896c4d4443198d2f486209c5765f51c51f Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 10 Aug 2016 16:34:07 -0700 Subject: [PATCH 6/9] Change 'Platform.XDG_Type' and 'Platform.SelectProductNameForDirectory' back to 'public' Add CI tests for the module path change --- .../CoreCLR/CorePsPlatform.cs | 4 +-- .../enginecore/ModulePath.Tests.ps1 | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 test/powershell/enginecore/ModulePath.Tests.ps1 diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 57964c2fe..1cccdabdc 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -207,7 +207,7 @@ namespace System.Management.Automation /// /// X Desktop Group configuration type enum. /// - internal enum XDG_Type + public enum XDG_Type { /// XDG_CONFIG_HOME/powershell CONFIG, @@ -226,7 +226,7 @@ namespace System.Management.Automation /// /// function for choosing directory location of PowerShell for profile loading /// - internal static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) + public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) { //TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060 diff --git a/test/powershell/enginecore/ModulePath.Tests.ps1 b/test/powershell/enginecore/ModulePath.Tests.ps1 new file mode 100644 index 000000000..57987a134 --- /dev/null +++ b/test/powershell/enginecore/ModulePath.Tests.ps1 @@ -0,0 +1,32 @@ +Describe "Module Path Basic Tests" -tags "CI" { + + It "validate module path on windows" -Skip:(!$IsWindows) { + + $paths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + $paths.Count | Should Be 3 + + $expectedUserPath = Join-Path -Path $HOME -ChildPath 'Documents\PowerShell\Modules' + $paths[0] | Should Be $expectedUserPath + + $expectedSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath 'PowerShell\Modules' + $paths[1] | Should Be $expectedSharedPath + + $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules' + $paths[2] | Should Be $expectedSystemPath + } + + It "validate module path on unix" -Skip:($IsWindows) { + + $paths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + $paths.Count | Should Be 3 + + $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES") + $paths[0] | Should Be $expectedUserPath + + $expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES") + $paths[1] | Should Be $expectedSharedPath + + $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules' + $paths[2] | Should Be $expectedSystemPath + } +} From 23ed0e4fa2adbe6f0c4f0ea81a68278e11d1801e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 10 Aug 2016 18:11:24 -0700 Subject: [PATCH 7/9] Use capital 'PSMODULEPATH' in tests so that it works in unix --- test/powershell/enginecore/ModulePath.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/powershell/enginecore/ModulePath.Tests.ps1 b/test/powershell/enginecore/ModulePath.Tests.ps1 index 57987a134..b6b664de5 100644 --- a/test/powershell/enginecore/ModulePath.Tests.ps1 +++ b/test/powershell/enginecore/ModulePath.Tests.ps1 @@ -2,7 +2,7 @@ Describe "Module Path Basic Tests" -tags "CI" { It "validate module path on windows" -Skip:(!$IsWindows) { - $paths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + $paths = $env:PSMODULEPATH -split [System.IO.Path]::PathSeparator $paths.Count | Should Be 3 $expectedUserPath = Join-Path -Path $HOME -ChildPath 'Documents\PowerShell\Modules' @@ -17,7 +17,7 @@ Describe "Module Path Basic Tests" -tags "CI" { It "validate module path on unix" -Skip:($IsWindows) { - $paths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + $paths = $env:PSMODULEPATH -split [System.IO.Path]::PathSeparator $paths.Count | Should Be 3 $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES") From ea9694980ed822c3e7019f635db21b88edc85b93 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 17:07:03 -0700 Subject: [PATCH 8/9] Address review comments in product code --- .../engine/Modules/ModuleIntrinsics.cs | 62 ++++++++++++------- .../engine/Utils.cs | 6 +- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 52bc75e35..0e96745c7 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -707,7 +707,16 @@ namespace System.Management.Automation return result.ToString(); } - private static bool NeedToClearProcessModulePath(string currentProcessModulePath, string personalModulePath, string programFilesModulePath, bool runningOps) + /// + /// Check if the current powershell is likely running in following scenarios: + /// - sxs ps started on windows [machine-wide env:psmodulepath will influence] + /// - sxs ps started from full ps + /// - sxs ps started from inbox nano/iot ps + /// - full ps started from sxs ps + /// - 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) { #if UNIX return false; @@ -715,15 +724,19 @@ namespace System.Management.Automation 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"); - const string winOpsModuleDirectory = @"PowerShell\Modules"; + const string winSxSModuleDirectory = @"PowerShell\Modules"; const string winLegacyModuleDirectory = @"WindowsPowerShell\Modules"; - if (runningOps) + if (runningSxS) { + // The machine-wide and user-wide environment variables are only meaningful for full ps, + // so if the current process module path contains any of them, it's likely that the sxs + // ps was started directly on windows, or from full ps. The same goes for the legacy personal + // and shared module paths. string hklmModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine); string hkcuModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User); - string legacyPersonalModulePath = personalModulePath.Replace(winOpsModuleDirectory, winLegacyModuleDirectory); - string legacyProgramFilesModulePath = programFilesModulePath.Replace(winOpsModuleDirectory, winLegacyModuleDirectory); + string legacyPersonalModulePath = personalModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory); + string legacyProgramFilesModulePath = programFilesModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory); return (!string.IsNullOrEmpty(hklmModulePath) && currentProcessModulePath.IndexOf(hklmModulePath, StringComparison.OrdinalIgnoreCase) != -1) || (!string.IsNullOrEmpty(hkcuModulePath) && currentProcessModulePath.IndexOf(hkcuModulePath, StringComparison.OrdinalIgnoreCase) != -1) || @@ -731,15 +744,22 @@ namespace System.Management.Automation currentProcessModulePath.IndexOf(legacyProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; } - string opsPersonalModulePath = personalModulePath.Replace(winLegacyModuleDirectory, winOpsModuleDirectory); - string opsProgramFilesModulePath = programFilesModulePath.Replace(winLegacyModuleDirectory, winOpsModuleDirectory); + // 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); - return currentProcessModulePath.IndexOf(opsPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || - currentProcessModulePath.IndexOf(opsProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; + return currentProcessModulePath.IndexOf(sxsPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || + currentProcessModulePath.IndexOf(sxsProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; #endif } - private static string RemoveOpsPsHomeModulePath(string currentProcessModulePath) + /// + /// When sxs ps instance B got started from sxs ps instance A, A's pshome module path might + /// 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) { #if UNIX const string powershellExeName = "powershell"; @@ -787,19 +807,19 @@ namespace System.Management.Automation string psHomeModulePath = GetSystemwideModulePath(); // $PSHome\Modules location #if CORECLR - bool runningOps = Platform.IsInbox ? false : true; + bool runningSxS = Platform.IsInbox ? false : true; #else - bool runningOps = false; + bool runningSxS = false; #endif if (!string.IsNullOrEmpty(currentProcessModulePath) && - NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningOps)) + NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, programFilesModulePath, runningSxS)) { // Clear the current process module path in the following cases - // - start ops on windows [machine-wide env:psmodulepath will influence] - // - start ops from full ps - // - start ops from inbox nano/iot ps - // - start full ps from ops - // - start inbox nano/iot ps from ops + // - start sxs ps on windows [machine-wide env:psmodulepath will influence] + // - start sxs ps from full ps + // - start sxs ps from inbox nano/iot ps + // - start full ps from sxs ps + // - start inbox nano/iot ps from sxs ps currentProcessModulePath = null; } @@ -828,10 +848,10 @@ namespace System.Management.Automation } // EVT.Process exists // Now handle the case where the environment variable is already set. - else if (runningOps) // The running powershell is an OPS instance + else if (runningSxS) // The running powershell is an SxS PS instance { - // When OPS instance A starts OPS instance B, A's PSHome module path might be inherited by B. We need to remove that path from B - currentProcessModulePath = RemoveOpsPsHomeModulePath(currentProcessModulePath); + // 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); string personalModulePathToUse = string.IsNullOrEmpty(hkcuUserModulePath) ? personalModulePath : hkcuUserModulePath; string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath; diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 2395d991f..63c687050 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -600,11 +600,7 @@ namespace System.Management.Automation /// The subdirectory of module paths /// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules /// -#if CORECLR - internal static string ModuleDirectory = Platform.IsInbox ? @"WindowsPowerShell\Modules" : Path.Combine("PowerShell", "Modules"); -#else - internal const string ModuleDirectory = @"WindowsPowerShell\Modules"; -#endif + internal static string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules"); internal static string GetRegistryConfigurationPrefix() { From 401b6194da2427cf4a85d854cd2d5b0e896ca8c1 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 11 Aug 2016 17:59:04 -0700 Subject: [PATCH 9/9] Address review comments in test code --- test/powershell/Host/Base-Directory.Tests.ps1 | 9 ++- .../enginecore/ModulePath.Tests.ps1 | 55 ++++++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/test/powershell/Host/Base-Directory.Tests.ps1 b/test/powershell/Host/Base-Directory.Tests.ps1 index 70f5163d3..625c64b1d 100644 --- a/test/powershell/Host/Base-Directory.Tests.ps1 +++ b/test/powershell/Host/Base-Directory.Tests.ps1 @@ -10,9 +10,14 @@ Describe "Configuration file locations" -tags "CI","Slow" { BeforeAll { if ($IsWindows) { + $ProductName = "WindowsPowerShell" + if ($IsCoreCLR -and ($PSHOME -notlike "*Windows\System32\WindowsPowerShell\v1.0")) + { + $ProductName = "PowerShell" + } $expectedCache = [IO.Path]::Combine($env:LOCALAPPDATA, "Microsoft", "Windows", "PowerShell", "StartupProfileData-NonInteractive") - $expectedModule = [IO.Path]::Combine($env:USERPROFILE, "Documents", "PowerShell", "Modules") - $expectedProfile = [io.path]::Combine($env:USERPROFILE, "Documents","PowerShell",$profileName) + $expectedModule = [IO.Path]::Combine($env:USERPROFILE, "Documents", $ProductName, "Modules") + $expectedProfile = [io.path]::Combine($env:USERPROFILE, "Documents", $ProductName, $profileName) $expectedReadline = [IO.Path]::Combine($env:AppData, "Microsoft", "Windows", "PowerShell", "PSReadline", "ConsoleHost_history.txt") } else { $expectedCache = [IO.Path]::Combine($env:HOME, ".cache", "powershell", "StartupProfileData-NonInteractive") diff --git a/test/powershell/enginecore/ModulePath.Tests.ps1 b/test/powershell/enginecore/ModulePath.Tests.ps1 index b6b664de5..0c33e15ee 100644 --- a/test/powershell/enginecore/ModulePath.Tests.ps1 +++ b/test/powershell/enginecore/ModulePath.Tests.ps1 @@ -1,32 +1,45 @@ -Describe "Module Path Basic Tests" -tags "CI" { +Describe "SxS Module Path Basic Tests" -tags "CI" { - It "validate module path on windows" -Skip:(!$IsWindows) { - - $paths = $env:PSMODULEPATH -split [System.IO.Path]::PathSeparator - $paths.Count | Should Be 3 - - $expectedUserPath = Join-Path -Path $HOME -ChildPath 'Documents\PowerShell\Modules' - $paths[0] | Should Be $expectedUserPath - - $expectedSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath 'PowerShell\Modules' - $paths[1] | Should Be $expectedSharedPath + BeforeAll { + if ($IsWindows) + { + $powershell = "$PSHOME\powershell.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" + } + else + { + $powershell = "$PSHOME/powershell" + $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES") + $expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES") + } $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules' - $paths[2] | Should Be $expectedSystemPath } - It "validate module path on unix" -Skip:($IsWindows) { - - $paths = $env:PSMODULEPATH -split [System.IO.Path]::PathSeparator + BeforeEach { + $originalModulePath = $env:PSMODULEPATH + } + + AfterEach { + $env:PSMODULEPATH = $originalModulePath + } + + It "validate sxs module path" { + + $env:PSMODULEPATH = "" + $defaultModulePath = & $powershell -nopro -c '$env:PSMODULEPATH' + + $paths = $defaultModulePath -split [System.IO.Path]::PathSeparator + $paths.Count | Should Be 3 - - $expectedUserPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("USER_MODULES") $paths[0] | Should Be $expectedUserPath - - $expectedSharedPath = [System.Management.Automation.Platform]::SelectProductNameForDirectory("SHARED_MODULES") $paths[1] | Should Be $expectedSharedPath - - $expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules' $paths[2] | Should Be $expectedSystemPath } }