From c552b65f15ecada86bf6bffdad08d321d50dedc3 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 5 Dec 2016 14:42:51 -0800 Subject: [PATCH] Copy CoreFX environment variable code (dotnet/coreclr#8405) Tweak the core code to match up with what we had done in CoreFX and expose so that we can have a single source of environment truth. This is particularly important for Unix as we use a local copy of the state. Commit migrated from https://github.com/dotnet/coreclr/commit/e06afa73cc284c6659a03eb475eb426d08938e77 --- src/coreclr/src/mscorlib/model.xml | 6 + .../src/mscorlib/mscorlib.shared.sources.props | 2 + .../Runtime/Augments/EnvironmentAugments.cs | 7 + .../src/mscorlib/src/System/Environment.Unix.cs | 101 ++++ .../src/mscorlib/src/System/Environment.Win32.cs | 226 +++++++++ src/coreclr/src/mscorlib/src/System/Environment.cs | 527 ++++++--------------- 6 files changed, 498 insertions(+), 371 deletions(-) create mode 100644 src/coreclr/src/mscorlib/src/System/Environment.Unix.cs create mode 100644 src/coreclr/src/mscorlib/src/System/Environment.Win32.cs diff --git a/src/coreclr/src/mscorlib/model.xml b/src/coreclr/src/mscorlib/model.xml index c338e5c..92ef0d9 100644 --- a/src/coreclr/src/mscorlib/model.xml +++ b/src/coreclr/src/mscorlib/model.xml @@ -2015,6 +2015,12 @@ + + + + + + diff --git a/src/coreclr/src/mscorlib/mscorlib.shared.sources.props b/src/coreclr/src/mscorlib/mscorlib.shared.sources.props index 651fd3c..891f73c 100644 --- a/src/coreclr/src/mscorlib/mscorlib.shared.sources.props +++ b/src/coreclr/src/mscorlib/mscorlib.shared.sources.props @@ -768,12 +768,14 @@ + + diff --git a/src/coreclr/src/mscorlib/src/Internal/Runtime/Augments/EnvironmentAugments.cs b/src/coreclr/src/mscorlib/src/Internal/Runtime/Augments/EnvironmentAugments.cs index 2810468..b22310e 100644 --- a/src/coreclr/src/mscorlib/src/Internal/Runtime/Augments/EnvironmentAugments.cs +++ b/src/coreclr/src/mscorlib/src/Internal/Runtime/Augments/EnvironmentAugments.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; namespace Internal.Runtime.Augments { @@ -17,5 +18,11 @@ namespace Internal.Runtime.Augments public static bool HasShutdownStarted => Environment.HasShutdownStarted; public static string StackTrace => Environment.StackTrace; public static int TickCount => Environment.TickCount; + public static string GetEnvironmentVariable(string variable) => Environment.GetEnvironmentVariable(variable); + public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) => Environment.GetEnvironmentVariable(variable, target); + public static IDictionary GetEnvironmentVariables() => Environment.GetEnvironmentVariables(); + public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => Environment.GetEnvironmentVariables(target); + public static void SetEnvironmentVariable(string variable, string value) => Environment.SetEnvironmentVariable(variable, value); + public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) => Environment.SetEnvironmentVariable(variable, value, target); } } diff --git a/src/coreclr/src/mscorlib/src/System/Environment.Unix.cs b/src/coreclr/src/mscorlib/src/System/Environment.Unix.cs new file mode 100644 index 0000000..13fba7e --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Environment.Unix.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; + +namespace System +{ + public static partial class Environment + { + private static readonly unsafe Lazy> s_environ = new Lazy>(() => + { + // We cache on Unix as using the block isn't thread safe + return GetRawEnvironmentVariables(); + }); + + private static string GetEnvironmentVariableCore(string variable) + { + // Ensure variable doesn't include a null char + int nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) + { + variable = variable.Substring(0, nullEnd); + } + + // Get the value of the variable + lock (s_environ) + { + string value; + return s_environ.Value.TryGetValue(variable, out value) ? value : null; + } + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + return target == EnvironmentVariableTarget.Process ? + GetEnvironmentVariableCore(variable) : + null; + } + + private static IDictionary GetEnvironmentVariablesCore() + { + lock (s_environ) + { + return new Dictionary(s_environ.Value); + } + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + return target == EnvironmentVariableTarget.Process ? + GetEnvironmentVariablesCore() : + new Dictionary(); + } + + private static void SetEnvironmentVariableCore(string variable, string value) + { + int nullEnd; + + // Ensure variable doesn't include a null char + nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) + { + variable = variable.Substring(0, nullEnd); + } + + // Ensure value doesn't include a null char + if (value != null) + { + nullEnd = value.IndexOf('\0'); + if (nullEnd != -1) + { + value = value.Substring(0, nullEnd); + } + } + + lock (s_environ) + { + // Remove the entry if the value is null, otherwise add/overwrite it + if (value == null) + { + s_environ.Value.Remove(variable); + } + else + { + s_environ.Value[variable] = value; + } + } + } + + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + SetEnvironmentVariableCore(variable, value); + } + // other targets ignored + } + } +} \ No newline at end of file diff --git a/src/coreclr/src/mscorlib/src/System/Environment.Win32.cs b/src/coreclr/src/mscorlib/src/System/Environment.Win32.cs new file mode 100644 index 0000000..ac3c6a7 --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Environment.Win32.cs @@ -0,0 +1,226 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace System +{ + public static partial class Environment + { + private static string GetEnvironmentVariableCore(string variable) + { + if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) + { + // Environment variable accessors are not approved modern API. + // Behave as if the variable was not found in this case. + return null; + } + + StringBuilder sb = StringBuilderCache.Acquire(128); // A somewhat reasonable default size + int requiredSize = Win32Native.GetEnvironmentVariable(variable, sb, sb.Capacity); + + if (requiredSize == 0 && Marshal.GetLastWin32Error() == Win32Native.ERROR_ENVVAR_NOT_FOUND) + { + StringBuilderCache.Release(sb); + return null; + } + + while (requiredSize > sb.Capacity) + { + sb.Capacity = requiredSize; + sb.Length = 0; + requiredSize = Win32Native.GetEnvironmentVariable(variable, sb, sb.Capacity); + } + + return StringBuilderCache.GetStringAndRelease(sb); + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + return GetEnvironmentVariableCore(variable); + +#if FEATURE_WIN32_REGISTRY + RegistryKey baseKey; + string keyName; + + if (target == EnvironmentVariableTarget.Machine) + { + baseKey = Registry.LocalMachine; + keyName = @"System\CurrentControlSet\Control\Session Manager\Environment"; + } + else if (target == EnvironmentVariableTarget.User) + { + Debug.Assert(target == EnvironmentVariableTarget.User); + baseKey = Registry.CurrentUser; + keyName = "Environment"; + } + else + { + throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + } + + using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false)) + { + return environmentKey?.GetValue(variable) as string; + } +#else + throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); +#endif + } + + private static IDictionary GetEnvironmentVariablesCore() + { + if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) + { + // Environment variable accessors are not approved modern API. + // Behave as if no environment variables are defined in this case. + return new Hashtable(0); + } + + return GetRawEnvironmentVariables(); + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + return GetEnvironmentVariablesCore(); + +#if FEATURE_WIN32_REGISTRY + RegistryKey baseKey; + string keyName; + if (target == EnvironmentVariableTarget.Machine) + { + baseKey = Registry.LocalMachine; + keyName = @"System\CurrentControlSet\Control\Session Manager\Environment"; + } + else if (target == EnvironmentVariableTarget.User) + { + Debug.Assert(target == EnvironmentVariableTarget.User); + baseKey = Registry.CurrentUser; + keyName = @"Environment"; + } + else + { + throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + } + + using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false)) + { + var table = new Dictionary(); + if (environmentKey != null) + { + foreach (string name in environmentKey.GetValueNames()) + { + table.Add(name, environmentKey.GetValue(name, "").ToString()); + } + } + return table; + } +#endif // FEATURE_WIN32_REGISTRY + + throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + } + + private static void SetEnvironmentVariableCore(string variable, string value) + { + // explicitly null out value if is the empty string. + if (string.IsNullOrEmpty(value) || value[0] == '\0') + value = null; + + if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) + { + // Environment variable accessors are not approved modern API. + // so we throw PlatformNotSupportedException. + throw new PlatformNotSupportedException(); + } + + if (!Win32Native.SetEnvironmentVariable(variable, value)) + { + int errorCode = Marshal.GetLastWin32Error(); + + switch (errorCode) + { + case Win32Native.ERROR_ENVVAR_NOT_FOUND: + // Allow user to try to clear a environment variable + return; + case Win32Native.ERROR_FILENAME_EXCED_RANGE: + // The error message from Win32 is "The filename or extension is too long", + // which is not accurate. + throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue")); + default: + throw new ArgumentException(Win32Native.GetMessage(errorCode)); + } + } + } + + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + SetEnvironmentVariableCore(variable, value); + return; + } + + // explicitly null out value if is the empty string. + if (string.IsNullOrEmpty(value) || value[0] == '\0') + value = null; + +#if FEATURE_WIN32_REGISTRY + RegistryKey baseKey; + string keyName; + + if (target == EnvironmentVariableTarget.Machine) + { + baseKey = Registry.LocalMachine; + keyName = @"System\CurrentControlSet\Control\Session Manager\Environment"; + } + else if (target == EnvironmentVariableTarget.User) + { + Debug.Assert(target == EnvironmentVariableTarget.User); + + // User-wide environment variables stored in the registry are limited to 255 chars for the environment variable name. + const int MaxUserEnvVariableLength = 255; + if (variable.Length >= MaxUserEnvVariableLength) + { + throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(variable)); + } + + baseKey = Registry.CurrentUser; + keyName = "Environment"; + } + else + { + throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + } + + using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: true)) + { + if (environmentKey != null) + { + if (value == null) + { + environmentKey.DeleteValue(variable, throwOnMissingValue: false); + } + else + { + environmentKey.SetValue(variable, value); + } + } + } + + // send a WM_SETTINGCHANGE message to all windows + IntPtr r = Win32Native.SendMessageTimeout(new IntPtr(Win32Native.HWND_BROADCAST), Win32Native.WM_SETTINGCHANGE, IntPtr.Zero, "Environment", 0, 1000, IntPtr.Zero); + if (r == IntPtr.Zero) Debug.Assert(false, "SetEnvironmentVariable failed: " + Marshal.GetLastWin32Error()); + +#else // FEATURE_WIN32_REGISTRY + throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)target)); +#endif + } + } +} \ No newline at end of file diff --git a/src/coreclr/src/mscorlib/src/System/Environment.cs b/src/coreclr/src/mscorlib/src/System/Environment.cs index f55a80b..6799078 100644 --- a/src/coreclr/src/mscorlib/src/System/Environment.cs +++ b/src/coreclr/src/mscorlib/src/System/Environment.cs @@ -32,17 +32,16 @@ namespace System { using System.Diagnostics.Contracts; [ComVisible(true)] - public enum EnvironmentVariableTarget { + public enum EnvironmentVariableTarget + { Process = 0, -#if FEATURE_WIN32_REGISTRY User = 1, Machine = 2, -#endif } [ComVisible(true)] - public static class Environment { - + public static partial class Environment + { // Assume the following constants include the terminating '\0' - use <, not <= const int MaxEnvVariableValueLength = 32767; // maximum length for environment variable name and value // System environment variables are stored in the registry, and have @@ -617,119 +616,24 @@ namespace System { } #endif - /*============================GetEnvironmentVariable============================ - **Action: - **Returns: - **Arguments: - **Exceptions: - ==============================================================================*/ - [System.Security.SecuritySafeCritical] // auto-generated - public static String GetEnvironmentVariable(String variable) - { - if (variable == null) - throw new ArgumentNullException(nameof(variable)); - Contract.EndContractBlock(); - - if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) { - // Environment variable accessors are not approved modern API. - // Behave as if the variable was not found in this case. - return null; - } - -#if !FEATURE_CORECLR - (new EnvironmentPermission(EnvironmentPermissionAccess.Read, variable)).Demand(); -#endif //!FEATURE_CORECLR - - StringBuilder blob = StringBuilderCache.Acquire(128); // A somewhat reasonable default size - int requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); - - if (requiredSize == 0) { // GetEnvironmentVariable failed - if (Marshal.GetLastWin32Error() == Win32Native.ERROR_ENVVAR_NOT_FOUND) { - StringBuilderCache.Release(blob); - return null; - } - } - - while (requiredSize > blob.Capacity) { // need to retry since the environment variable might be changed - blob.Capacity = requiredSize; - blob.Length = 0; - requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); - } - return StringBuilderCache.GetStringAndRelease(blob); - } - - [System.Security.SecuritySafeCritical] // auto-generated - public static string GetEnvironmentVariable( string variable, EnvironmentVariableTarget target) - { - if (variable == null) - { - throw new ArgumentNullException(nameof(variable)); - } - Contract.EndContractBlock(); - - if (target == EnvironmentVariableTarget.Process) - { - return GetEnvironmentVariable(variable); - } - -#if FEATURE_WIN32_REGISTRY - (new EnvironmentPermission(PermissionState.Unrestricted)).Demand(); - - if( target == EnvironmentVariableTarget.Machine) { - using (RegistryKey environmentKey = - Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", false)) { - - Contract.Assert(environmentKey != null, @"HKLM\System\CurrentControlSet\Control\Session Manager\Environment is missing!"); - if (environmentKey == null) { - return null; - } - - string value = environmentKey.GetValue(variable) as string; - return value; - } - } - else if( target == EnvironmentVariableTarget.User) { - using (RegistryKey environmentKey = - Registry.CurrentUser.OpenSubKey("Environment", false)) { - - Contract.Assert(environmentKey != null, @"HKCU\Environment is missing!"); - if (environmentKey == null) { - return null; - } - - string value = environmentKey.GetValue(variable) as string; - return value; - } - } - else -#endif // FEATURE_WIN32_REGISTRY - { - throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)target)); - } - } - - /*===========================GetEnvironmentVariables============================ - **Action: Returns an IDictionary containing all enviroment variables and their values. - **Returns: An IDictionary containing all environment variables and their values. - **Arguments: None. - **Exceptions: None. - ==============================================================================*/ - [System.Security.SecurityCritical] // auto-generated private unsafe static char[] GetEnvironmentCharArray() { char[] block = null; // Make sure pStrings is not leaked with async exceptions RuntimeHelpers.PrepareConstrainedRegions(); - try { + try + { } - finally { - char * pStrings = null; + finally + { + char* pStrings = null; try { pStrings = Win32Native.GetEnvironmentStrings(); - if (pStrings == null) { + if (pStrings == null) + { throw new OutOfMemoryException(); } @@ -739,7 +643,7 @@ namespace System { // CreateProcess page (null-terminated array of null-terminated strings). // Search for terminating \0\0 (two unicode \0's). - char * p = pStrings; + char* p = pStrings; while (!(*p == '\0' && *(p + 1) == '\0')) p++; @@ -747,7 +651,7 @@ namespace System { block = new char[len]; fixed (char* pBlock = block) - String.wstrcpy(pBlock, pStrings, len); + string.wstrcpy(pBlock, pStrings, len); } finally { @@ -759,268 +663,6 @@ namespace System { return block; } - [System.Security.SecuritySafeCritical] // auto-generated - public static IDictionary GetEnvironmentVariables() - { - if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) { - // Environment variable accessors are not approved modern API. - // Behave as if no environment variables are defined in this case. - return new Hashtable(0); - } - -#if !FEATURE_CORECLR - bool isFullTrust = CodeAccessSecurityEngine.QuickCheckForAllDemands(); - StringBuilder vars = isFullTrust ? null : new StringBuilder(); - bool first = true; -#endif - - char[] block = GetEnvironmentCharArray(); - - Hashtable table = new Hashtable(20); - - // Copy strings out, parsing into pairs and inserting into the table. - // The first few environment variable entries start with an '='! - // The current working directory of every drive (except for those drives - // you haven't cd'ed into in your DOS window) are stored in the - // environment block (as =C:=pwd) and the program's exit code is - // as well (=ExitCode=00000000) Skip all that start with =. - // Read docs about Environment Blocks on MSDN's CreateProcess page. - - // Format for GetEnvironmentStrings is: - // (=HiddenVar=value\0 | Variable=value\0)* \0 - // See the description of Environment Blocks in MSDN's - // CreateProcess page (null-terminated array of null-terminated strings). - // Note the =HiddenVar's aren't always at the beginning. - - for(int i=0; i= MaxEnvVariableValueLength) { - throw new ArgumentException(Environment.GetResourceString("Argument_LongEnvVarValue")); - } - } - - if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) { - // Environment variable accessors are not approved modern API. - // so we throw PlatformNotSupportedException. - throw new PlatformNotSupportedException(); - } - - if(!Win32Native.SetEnvironmentVariable(variable, value)) { - int errorCode = Marshal.GetLastWin32Error(); - - // Allow user to try to clear a environment variable - if( errorCode == Win32Native.ERROR_ENVVAR_NOT_FOUND) { - return; - } - - // The error message from Win32 is "The filename or extension is too long", - // which is not accurate. - if( errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) { - throw new ArgumentException(Environment.GetResourceString("Argument_LongEnvVarValue")); - } - - throw new ArgumentException(Win32Native.GetMessage(errorCode)); - } - } - - private static void CheckEnvironmentVariableName(string variable) { - if (variable == null) { - throw new ArgumentNullException(nameof(variable)); - } - - if( variable.Length == 0) { - throw new ArgumentException(Environment.GetResourceString("Argument_StringZeroLength"), nameof(variable)); - } - - if( variable[0] == '\0') { - throw new ArgumentException(Environment.GetResourceString("Argument_StringFirstCharIsZero"), nameof(variable)); - } - - // Make sure the environment variable name isn't longer than the - // max limit on environment variable values. (MSDN is ambiguous - // on whether this check is necessary.) - if( variable.Length >= MaxEnvVariableValueLength ) { - throw new ArgumentException(Environment.GetResourceString("Argument_LongEnvVarValue")); - } - - if( variable.IndexOf('=') != -1) { - throw new ArgumentException(Environment.GetResourceString("Argument_IllegalEnvVarName")); - } - Contract.EndContractBlock(); - } - - [System.Security.SecuritySafeCritical] // auto-generated - public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) { - if( target == EnvironmentVariableTarget.Process) { - SetEnvironmentVariable(variable, value); - return; - } - - CheckEnvironmentVariableName(variable); - - // System-wide environment variables stored in the registry are - // limited to 1024 chars for the environment variable name. - if (variable.Length >= MaxSystemEnvVariableLength) { - throw new ArgumentException(Environment.GetResourceString("Argument_LongEnvVarName")); - } - - new EnvironmentPermission(PermissionState.Unrestricted).Demand(); - // explicitly null out value if is the empty string. - if (String.IsNullOrEmpty(value) || value[0] == '\0') { - value = null; - } -#if FEATURE_WIN32_REGISTRY - if( target == EnvironmentVariableTarget.Machine) { - using (RegistryKey environmentKey = - Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", true)) { - - Contract.Assert(environmentKey != null, @"HKLM\System\CurrentControlSet\Control\Session Manager\Environment is missing!"); - if (environmentKey != null) { - if (value == null) - environmentKey.DeleteValue(variable, false); - else - environmentKey.SetValue(variable, value); - } - } - } - else if( target == EnvironmentVariableTarget.User) { - // User-wide environment variables stored in the registry are - // limited to 255 chars for the environment variable name. - if (variable.Length >= MaxUserEnvVariableLength) { - throw new ArgumentException(Environment.GetResourceString("Argument_LongEnvVarValue")); - } - using (RegistryKey environmentKey = - Registry.CurrentUser.OpenSubKey("Environment", true)) { - Contract.Assert(environmentKey != null, @"HKCU\Environment is missing!"); - if (environmentKey != null) { - if (value == null) - environmentKey.DeleteValue(variable, false); - else - environmentKey.SetValue(variable, value); - } - } - } - else - { - throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)target)); - } - // send a WM_SETTINGCHANGE message to all windows - IntPtr r = Win32Native.SendMessageTimeout(new IntPtr(Win32Native.HWND_BROADCAST), Win32Native.WM_SETTINGCHANGE, IntPtr.Zero, "Environment", 0, 1000, IntPtr.Zero); - - if (r == IntPtr.Zero) BCLDebug.Assert(false, "SetEnvironmentVariable failed: " + Marshal.GetLastWin32Error()); - -#else // FEATURE_WIN32_REGISTRY - throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)target)); -#endif - } - - /*===============================GetLogicalDrives=============================== **Action: Retrieves the names of the logical drives on this machine in the form "C:\". **Arguments: None. @@ -1821,5 +1463,148 @@ namespace System { [MethodImplAttribute(MethodImplOptions.InternalCall)] get; } + + public static string GetEnvironmentVariable(string variable) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + return GetEnvironmentVariableCore(variable); + } + + public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + ValidateTarget(target); + + return GetEnvironmentVariableCore(variable, target); + } + + public static IDictionary GetEnvironmentVariables() + { + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + return GetEnvironmentVariablesCore(); + } + + public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) + { + ValidateTarget(target); + + return GetEnvironmentVariablesCore(target); + } + + public static void SetEnvironmentVariable(string variable, string value) + { + ValidateVariableAndValue(variable, ref value); + + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + SetEnvironmentVariableCore(variable, value); + } + + public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) + { + ValidateVariableAndValue(variable, ref value); + ValidateTarget(target); + + SetEnvironmentVariableCore(variable, value, target); + } + + private static void ValidateVariableAndValue(string variable, ref string value) + { + const int MaxEnvVariableValueLength = 32767; + + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + if (variable.Length == 0) + { + throw new ArgumentException(GetResourceString("Argument_StringZeroLength"), nameof(variable)); + } + if (variable[0] == '\0') + { + throw new ArgumentException(GetResourceString("Argument_StringFirstCharIsZero"), nameof(variable)); + } + if (variable.Length >= MaxEnvVariableValueLength) + { + throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(variable)); + } + if (variable.IndexOf('=') != -1) + { + throw new ArgumentException(GetResourceString("Argument_IllegalEnvVarName"), nameof(variable)); + } + + if (string.IsNullOrEmpty(value) || value[0] == '\0') + { + // Explicitly null out value if it's empty + value = null; + } + else if (value.Length >= MaxEnvVariableValueLength) + { + throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(value)); + } + } + + private static void ValidateTarget(EnvironmentVariableTarget target) + { + if (target != EnvironmentVariableTarget.Process && + target != EnvironmentVariableTarget.Machine && + target != EnvironmentVariableTarget.User) + { + throw new ArgumentOutOfRangeException(nameof(target), target, SR.Format(GetResourceString("Arg_EnumIllegalVal"), target)); + } + } + + private static Dictionary GetRawEnvironmentVariables() + { + // Format for GetEnvironmentStrings is: + // (=HiddenVar=value\0 | Variable=value\0)* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + // Note the =HiddenVar's aren't always at the beginning. + + // Copy strings out, parsing into pairs and inserting into the table. + // The first few environment variable entries start with an '='. + // The current working directory of every drive (except for those drives + // you haven't cd'ed into in your DOS window) are stored in the + // environment block (as =C:=pwd) and the program's exit code is + // as well (=ExitCode=00000000). + + var results = new Dictionary(); + char[] block = GetEnvironmentCharArray(); + for (int i = 0; i < block.Length; i++) + { + int startKey = i; + + // Skip to key. On some old OS, the environment block can be corrupted. + // Some will not have '=', so we need to check for '\0'. + while (block[i] != '=' && block[i] != '\0') i++; + if (block[i] == '\0') continue; + + // Skip over environment variables starting with '=' + if (i - startKey == 0) + { + while (block[i] != 0) i++; + continue; + } + + string key = new string(block, startKey, i - startKey); + i++; // skip over '=' + + int startValue = i; + while (block[i] != 0) i++; // Read to end of this entry + string value = new string(block, startValue, i - startValue); // skip over 0 handled by for loop's i++ + + results[key] = value; + } + return results; + } } } -- 2.7.4