diff --git a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs index 2c43255cca4..d4e1ba033ef 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs @@ -1044,6 +1044,11 @@ private static string InternalGetFolderPath(SpecialFolder folder) string folderPath = null; #if UNIX + string envHome = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); + if (null == envHome) + { + envHome = Platform.GetTemporaryDirectory(); + } switch (folder) { case SpecialFolder.ProgramFiles: @@ -1060,11 +1065,22 @@ private static string InternalGetFolderPath(SpecialFolder folder) if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } break; case SpecialFolder.Personal: - folderPath = System.Environment.GetEnvironmentVariable("HOME"); + folderPath = envHome; break; case SpecialFolder.LocalApplicationData: - folderPath = System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".config"); - if (!System.IO.Directory.Exists(folderPath)) { System.IO.Directory.CreateDirectory(folderPath); } + folderPath = System.IO.Path.Combine(envHome, ".config"); + if (!System.IO.Directory.Exists(folderPath)) + { + try + { + System.IO.Directory.CreateDirectory(folderPath); + } + catch (UnauthorizedAccessException) + { + // directory creation may fail if the account doesn't have filesystem permission such as some service accounts + folderPath = String.Empty; + } + } break; default: throw new NotSupportedException(); @@ -1100,7 +1116,7 @@ private static string InternalGetFolderPath(SpecialFolder folder) } break; case SpecialFolder.MyDocuments: // same as SpecialFolder.Personal - userProfile = System.Environment.GetEnvironmentVariable("USERPROFILE"); + userProfile = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); if (userProfile != null) { folderPath = System.IO.Path.Combine(userProfile, "Documents"); @@ -1117,7 +1133,7 @@ private static string InternalGetFolderPath(SpecialFolder folder) // It's guaranteed by NanoServer team that 'USERPROFILE' will be already set when SetupComplete.cmd runs. // So we use the path '%USERPROFILE%\AppData\Local' as an alternative in this case, and also set the env // variable %LOCALAPPDATA% to it, so that modules running in PS can depend on this env variable. - userProfile = System.Environment.GetEnvironmentVariable("USERPROFILE"); + userProfile = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); if (userProfile != null) { string alternatePath = System.IO.Path.Combine(userProfile, @"AppData\Local"); diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index bc558d053d6..d3f8cead66f 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -16,6 +16,8 @@ namespace System.Management.Automation /// public static class Platform { + private static string _tempDirectory = null; + /// /// True if the current platform is Linux. /// @@ -204,6 +206,41 @@ internal static class CommonEnvVariableNames #endif } + /// + /// Remove the temporary directory created for the current process + /// + internal static void RemoveTemporaryDirectory() + { + if (null == _tempDirectory) + { + return; + } + + try + { + Directory.Delete(_tempDirectory, true); + } + catch + { + // ignore if there is a failure + } + _tempDirectory = null; + } + + /// + /// Get a temporary directory to use for the current process + /// + internal static string GetTemporaryDirectory() + { + if (null != _tempDirectory) + { + return _tempDirectory; + } + + _tempDirectory = PsUtils.GetTemporaryDirectory(); + return _tempDirectory; + } + #if UNIX /// /// X Desktop Group configuration type enum. @@ -234,10 +271,15 @@ public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) string xdgconfighome = System.Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); string xdgdatahome = System.Environment.GetEnvironmentVariable("XDG_DATA_HOME"); string xdgcachehome = System.Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); - string xdgConfigHomeDefault = Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".config", "powershell"); - string xdgDataHomeDefault = Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".local", "share", "powershell"); + string envHome = System.Environment.GetEnvironmentVariable(CommonEnvVariableNames.Home); + if (null == envHome) + { + envHome = GetTemporaryDirectory(); + } + string xdgConfigHomeDefault = Path.Combine(envHome, ".config", "powershell"); + string xdgDataHomeDefault = Path.Combine(envHome, ".local", "share", "powershell"); string xdgModuleDefault = Path.Combine(xdgDataHomeDefault, "Modules"); - string xdgCacheDefault = Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".cache", "powershell"); + string xdgCacheDefault = Path.Combine(envHome, ".cache", "powershell"); switch (dirpath) { @@ -268,6 +310,7 @@ public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) catch (UnauthorizedAccessException) { //service accounts won't have permission to create user folder + return GetTemporaryDirectory(); } } return xdgDataHomeDefault; @@ -291,6 +334,7 @@ public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) catch (UnauthorizedAccessException) { //service accounts won't have permission to create user folder + return GetTemporaryDirectory(); } } return xdgModuleDefault; @@ -317,6 +361,7 @@ public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) catch (UnauthorizedAccessException) { //service accounts won't have permission to create user folder + return GetTemporaryDirectory(); } } @@ -334,6 +379,7 @@ public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) catch (UnauthorizedAccessException) { //service accounts won't have permission to create user folder + return GetTemporaryDirectory(); } } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index b218f1ea551..d9e295282ca 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -1267,6 +1267,8 @@ protected override void Dispose(bool disposing) RunspaceOpening = null; } + Platform.RemoveTemporaryDirectory(); + // Dispose the event manager if (this.ExecutionContext != null && this.ExecutionContext.Events != null) { diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index 96a29ff2684..d764203da25 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -604,6 +604,30 @@ internal static bool IsRunningOnProcessorArchitectureARM() #endif } + /// + /// Get a temporary directory to use, needs to be unique to avoid collision + /// + internal static string GetTemporaryDirectory() + { + string tempDir = String.Empty; + string tempPath = Path.GetTempPath(); + do + { + tempDir = Path.Combine(tempPath,System.Guid.NewGuid().ToString()); + } + while (Directory.Exists(tempDir)); + + try + { + Directory.CreateDirectory(tempDir); + } + catch (UnauthorizedAccessException) + { + tempDir = String.Empty; // will become current working directory + } + return tempDir; + } + internal static string GetHostName() { // Note: non-windows CoreCLR does not support System.Net yet diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 000bbf86103..aafb0b2649f 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -403,6 +403,12 @@ foo [int]$output | Should BeGreaterThan 0 } } + + Context "HOME environment variable" { + It "Should start if HOME is not defined" -skip:($IsWindows) { + bash -c "unset HOME;$powershell -c '1+1'" | Should BeExactly 2 + } + } } Describe "Console host api tests" -Tag CI {