diff options
Diffstat (limited to 'src/mscorlib/src/System/Environment.cs')
-rw-r--r-- | src/mscorlib/src/System/Environment.cs | 476 |
1 files changed, 115 insertions, 361 deletions
diff --git a/src/mscorlib/src/System/Environment.cs b/src/mscorlib/src/System/Environment.cs index 8fa4ce4ff5..b9070ae713 100644 --- a/src/mscorlib/src/System/Environment.cs +++ b/src/mscorlib/src/System/Environment.cs @@ -11,7 +11,9 @@ ** ** ============================================================*/ -namespace System { + +namespace System +{ using System.IO; using System.Security; using System.Resources; @@ -37,145 +39,37 @@ namespace System { Machine = 2, } - public static partial class Environment + internal 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 + private const int MaxEnvVariableValueLength = 32767; // maximum length for environment variable name and value // System environment variables are stored in the registry, and have // a size restriction that is separate from both normal environment // variables and registry value name lengths, according to MSDN. // MSDN doesn't detail whether the name is limited to 1024, or whether // that includes the contents of the environment variable. - const int MaxSystemEnvVariableLength = 1024; - const int MaxUserEnvVariableLength = 255; + private const int MaxSystemEnvVariableLength = 1024; + private const int MaxUserEnvVariableLength = 255; + private const int MaxMachineNameLength = 256; - internal sealed class ResourceHelper + // Looks up the resource string value for key. + // + // if you change this method's signature then you must change the code that calls it + // in excep.cpp and probably you will have to visit mscorlib.h to add the new signature + // as well as metasig.h to create the new signature type + internal static String GetResourceStringLocal(String key) { - internal ResourceHelper(String name) { - m_name = name; - } - - private String m_name; - private ResourceManager SystemResMgr; - - // To avoid infinite loops when calling GetResourceString. See comments - // in GetResourceString for this field. - private List<string> currentlyLoading; - - // process-wide state (since this is only used in one domain), - // used to avoid the TypeInitialization infinite recusion - // in GetResourceStringCode - internal bool resourceManagerInited = false; - - // Is this thread currently doing infinite resource lookups? - private int infinitelyRecursingCount; - - internal String GetResourceString(String key) { - if (key == null || key.Length == 0) { - Debug.Assert(false, "Environment::GetResourceString with null or empty key. Bug in caller, or weird recursive loading problem?"); - return "[Resource lookup failed - null or empty resource name]"; - } - - // We have a somewhat common potential for infinite - // loops with mscorlib's ResourceManager. If "potentially dangerous" - // code throws an exception, we will get into an infinite loop - // inside the ResourceManager and this "potentially dangerous" code. - // Potentially dangerous code includes the IO package, CultureInfo, - // parts of the loader, some parts of Reflection, Security (including - // custom user-written permissions that may parse an XML file at - // class load time), assembly load event handlers, etc. Essentially, - // this is not a bounded set of code, and we need to fix the problem. - // Fortunately, this is limited to mscorlib's error lookups and is NOT - // a general problem for all user code using the ResourceManager. - - // The solution is to make sure only one thread at a time can call - // GetResourceString. Also, since resource lookups can be - // reentrant, if the same thread comes into GetResourceString - // twice looking for the exact same resource name before - // returning, we're going into an infinite loop and we should - // return a bogus string. - - bool lockTaken = false; - try - { - Monitor.Enter(this, ref lockTaken); - - // Are we recursively looking up the same resource? Note - our backout code will set - // the ResourceHelper's currentlyLoading stack to null if an exception occurs. - if (currentlyLoading != null && currentlyLoading.Count > 0 && currentlyLoading.LastIndexOf(key) != -1) - { - // We can start infinitely recursing for one resource lookup, - // then during our failure reporting, start infinitely recursing again. - // avoid that. - if (infinitelyRecursingCount > 0) - { - return "[Resource lookup failed - infinite recursion or critical failure detected.]"; - } - infinitelyRecursingCount++; - - // Note: our infrastructure for reporting this exception will again cause resource lookup. - // This is the most direct way of dealing with that problem. - string message = $"Infinite recursion during resource lookup within {System.CoreLib.Name}. This may be a bug in {System.CoreLib.Name}, or potentially in certain extensibility points such as assembly resolve events or CultureInfo names. Resource name: {key}"; - Assert.Fail("[Recursive resource lookup bug]", message, Assert.COR_E_FAILFAST, System.Diagnostics.StackTrace.TraceFormat.NoResourceLookup); - Environment.FailFast(message); - } - if (currentlyLoading == null) - currentlyLoading = new List<string>(); - - // Call class constructors preemptively, so that we cannot get into an infinite - // loop constructing a TypeInitializationException. If this were omitted, - // we could get the Infinite recursion assert above by failing type initialization - // between the Push and Pop calls below. - if (!resourceManagerInited) - { - RuntimeHelpers.RunClassConstructor(typeof(ResourceManager).TypeHandle); - RuntimeHelpers.RunClassConstructor(typeof(ResourceReader).TypeHandle); - RuntimeHelpers.RunClassConstructor(typeof(RuntimeResourceSet).TypeHandle); - RuntimeHelpers.RunClassConstructor(typeof(BinaryReader).TypeHandle); - resourceManagerInited = true; - } - - currentlyLoading.Add(key); // Push - - if (SystemResMgr == null) - { - SystemResMgr = new ResourceManager(m_name, typeof(Object).Assembly); - } - string s = SystemResMgr.GetString(key, null); - currentlyLoading.RemoveAt(currentlyLoading.Count - 1); // Pop - - Debug.Assert(s != null, "Managed resource string lookup failed. Was your resource name misspelled? Did you rebuild mscorlib after adding a resource to resources.txt? Debug this w/ cordbg and bug whoever owns the code that called Environment.GetResourceString. Resource name was: \"" + key + "\""); - return s; - } - catch - { - if (lockTaken) - { - // Backout code - throw away potentially corrupt state - SystemResMgr = null; - currentlyLoading = null; - } - throw; - } - finally - { - if (lockTaken) - { - Monitor.Exit(this); - } - } - } + return SR.GetResourceString(key); } - private static volatile ResourceHelper m_resHelper; // Doesn't need to be initialized as they're zero-init. - - private const int MaxMachineNameLength = 256; - // Private object for locking instead of locking on a public type for SQL reliability work. private static Object s_InternalSyncObject; - private static Object InternalSyncObject { - get { - if (s_InternalSyncObject == null) { + private static Object InternalSyncObject + { + get + { + if (s_InternalSyncObject == null) + { Object o = new Object(); Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null); } @@ -192,25 +86,28 @@ namespace System { **Arguments: None **Exceptions: None ==============================================================================*/ - public static extern int TickCount { + public static extern int TickCount + { [MethodImplAttribute(MethodImplOptions.InternalCall)] get; } - + // Terminates this process with the given exit code. [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] [SuppressUnmanagedCodeSecurity] internal static extern void _Exit(int exitCode); - public static void Exit(int exitCode) { + public static void Exit(int exitCode) + { _Exit(exitCode); } - public static extern int ExitCode { + public static extern int ExitCode + { [MethodImplAttribute(MethodImplOptions.InternalCall)] get; - + [MethodImplAttribute(MethodImplOptions.InternalCall)] set; } @@ -246,22 +143,26 @@ namespace System { ==============================================================================*/ internal static String CurrentDirectory { - get{ + get + { return Directory.GetCurrentDirectory(); } - set { + set + { Directory.SetCurrentDirectory(value); } } // Returns the system directory (ie, C:\WinNT\System32). - internal static String SystemDirectory { - get { + internal static String SystemDirectory + { + get + { StringBuilder sb = new StringBuilder(Path.MaxPath); int r = Win32Native.GetSystemDirectory(sb, Path.MaxPath); Debug.Assert(r < Path.MaxPath, "r < Path.MaxPath"); - if (r==0) __Error.WinIOError(); + if (r == 0) __Error.WinIOError(); String path = sb.ToString(); return path; @@ -274,16 +175,11 @@ namespace System { throw new ArgumentNullException(nameof(name)); Contract.EndContractBlock(); - if (name.Length == 0) { + if (name.Length == 0) + { return name; } - if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) { - // Environment variable accessors are not approved modern API. - // Behave as if no variables are defined in this case. - return name; - } - int currentSize = 100; StringBuilder blob = new StringBuilder(currentSize); // A somewhat reasonable default size @@ -314,8 +210,9 @@ namespace System { size = Win32Native.ExpandEnvironmentStrings(name, blob, currentSize); if (size == 0) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - - while (size > currentSize) { + + while (size > currentSize) + { currentSize = size; blob.Capacity = currentSize; blob.Length = 0; @@ -329,32 +226,14 @@ namespace System { return blob.ToString(); } - public static String MachineName { - get { - - // UWP Debug scenarios - if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode()) - { - // Getting Computer Name is not a supported scenario on Store apps. - throw new PlatformNotSupportedException(); - } - - // In future release of operating systems, you might be able to rename a machine without - // rebooting. Therefore, don't cache this machine name. - StringBuilder buf = new StringBuilder(MaxMachineNameLength); - int len = MaxMachineNameLength; - if (Win32Native.GetComputerName(buf, ref len) == 0) - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ComputerName")); - return buf.ToString(); - } - } - [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] [SuppressUnmanagedCodeSecurity] private static extern Int32 GetProcessorCount(); - public static int ProcessorCount { - get { + public static int ProcessorCount + { + get + { return GetProcessorCount(); } } @@ -381,7 +260,7 @@ namespace System { * So our best bet is to simply use the commandLine that was used to invoke the process. * in case it is present. */ - if(s_CommandLineArgs != null) + if (s_CommandLineArgs != null) return (string[])s_CommandLineArgs.Clone(); return GetCommandLineArgsNative(); @@ -450,91 +329,70 @@ namespace System { **Arguments: None. **Exceptions: None. ==============================================================================*/ - public static String NewLine { - get { + public static String NewLine + { + get + { Contract.Ensures(Contract.Result<String>() != null); -#if !PLATFORM_UNIX +#if PLATFORM_WINDOWS return "\r\n"; #else return "\n"; -#endif // !PLATFORM_UNIX +#endif // PLATFORM_WINDOWS } } - + /*===================================Version==================================== **Action: Returns the COM+ version struct, describing the build number. **Returns: **Arguments: **Exceptions: ==============================================================================*/ - public static Version Version { - get { - + public static Version Version + { + get + { // Previously this represented the File version of mscorlib.dll. Many other libraries in the framework and outside took dependencies on the first three parts of this version // remaining constant throughout 4.x. From 4.0 to 4.5.2 this was fine since the file version only incremented the last part.Starting with 4.6 we switched to a file versioning // scheme that matched the product version. In order to preserve compatibility with existing libraries, this needs to be hard-coded. - - return new Version(4,0,30319,42000); - } - } - - /*==================================OSVersion=================================== - **Action: - **Returns: - **Arguments: - **Exceptions: - ==============================================================================*/ - internal static OperatingSystem OSVersion { - get { - Contract.Ensures(Contract.Result<OperatingSystem>() != null); - - if (m_os==null) { // We avoid the lock since we don't care if two threads will set this at the same time. - - Microsoft.Win32.Win32Native.OSVERSIONINFO osvi = new Microsoft.Win32.Win32Native.OSVERSIONINFO(); - if (!GetVersion(osvi)) { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_GetVersion")); - } - - Microsoft.Win32.Win32Native.OSVERSIONINFOEX osviEx = new Microsoft.Win32.Win32Native.OSVERSIONINFOEX(); - if (!GetVersionEx(osviEx)) - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_GetVersion")); -#if PLATFORM_UNIX - PlatformID id = PlatformID.Unix; -#else - PlatformID id = PlatformID.Win32NT; -#endif // PLATFORM_UNIX - - Version v = new Version(osvi.MajorVersion, osvi.MinorVersion, osvi.BuildNumber, (osviEx.ServicePackMajor << 16) |osviEx.ServicePackMinor); - m_os = new OperatingSystem(id, v, osvi.CSDVersion); - } - Debug.Assert(m_os != null, "m_os != null"); - return m_os; + return new Version(4, 0, 30319, 42000); } } +#if !FEATURE_PAL + private static Lazy<bool> s_IsWindows8OrAbove = new Lazy<bool>(() => + { + ulong conditionMask = Win32Native.VerSetConditionMask(0, Win32Native.VER_MAJORVERSION, Win32Native.VER_GREATER_EQUAL); + conditionMask = Win32Native.VerSetConditionMask(conditionMask, Win32Native.VER_MINORVERSION, Win32Native.VER_GREATER_EQUAL); + conditionMask = Win32Native.VerSetConditionMask(conditionMask, Win32Native.VER_SERVICEPACKMAJOR, Win32Native.VER_GREATER_EQUAL); + conditionMask = Win32Native.VerSetConditionMask(conditionMask, Win32Native.VER_SERVICEPACKMINOR, Win32Native.VER_GREATER_EQUAL); - internal static bool IsWindows8OrAbove { - get { - return true; - } - } - + // Windows 8 version is 6.2 + var version = new Win32Native.OSVERSIONINFOEX { MajorVersion = 6, MinorVersion = 2, ServicePackMajor = 0, ServicePackMinor = 0 }; + + return Win32Native.VerifyVersionInfoW(version, + Win32Native.VER_MAJORVERSION | Win32Native.VER_MINORVERSION | Win32Native.VER_SERVICEPACKMAJOR | Win32Native.VER_SERVICEPACKMINOR, + conditionMask); + }); + internal static bool IsWindows8OrAbove => s_IsWindows8OrAbove.Value; +#endif + #if FEATURE_COMINTEROP - internal static bool IsWinRTSupported { - get { - return true; - } - } -#endif // FEATURE_COMINTEROP - + // Does the current version of Windows have Windows Runtime suppport? + private static Lazy<bool> s_IsWinRTSupported = new Lazy<bool>(() => + { + return WinRTSupported(); + }); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern bool GetVersion(Microsoft.Win32.Win32Native.OSVERSIONINFO osVer); + internal static bool IsWinRTSupported => s_IsWinRTSupported.Value; - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern bool GetVersionEx(Microsoft.Win32.Win32Native.OSVERSIONINFOEX osVer); + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool WinRTSupported(); +#endif // FEATURE_COMINTEROP /*==================================StackTrace================================== @@ -543,11 +401,13 @@ namespace System { **Arguments: **Exceptions: ==============================================================================*/ - public static String StackTrace { - get { + public static String StackTrace + { + [MethodImpl(MethodImplOptions.NoInlining)] // Prevent inlining from affecting where the stacktrace starts + get + { Contract.Ensures(Contract.Result<String>() != null); - - return GetStackTrace(null, true); + return Internal.Runtime.Augments.EnvironmentAugments.StackTrace; } } @@ -564,104 +424,19 @@ namespace System { st = new StackTrace(e, needFileInfo); // Do no include a trailing newline for backwards compatibility - return st.ToString( System.Diagnostics.StackTrace.TraceFormat.Normal ); - } - - private static void InitResourceHelper() { - // Only the default AppDomain should have a ResourceHelper. All calls to - // GetResourceString from any AppDomain delegate to GetResourceStringLocal - // in the default AppDomain via the fcall GetResourceFromDefault. - - bool tookLock = false; - RuntimeHelpers.PrepareConstrainedRegions(); - try { - - Monitor.Enter(Environment.InternalSyncObject, ref tookLock); - - if (m_resHelper == null) { - ResourceHelper rh = new ResourceHelper(System.CoreLib.Name); - - System.Threading.Thread.MemoryBarrier(); - m_resHelper =rh; - } - } - finally { - if (tookLock) - Monitor.Exit(Environment.InternalSyncObject); - } - } - - // Looks up the resource string value for key. - // - // if you change this method's signature then you must change the code that calls it - // in excep.cpp and probably you will have to visit mscorlib.h to add the new signature - // as well as metasig.h to create the new signature type - // NoInlining causes the caller and callee to not be inlined in mscorlib as it is an assumption of StackCrawlMark use - [MethodImpl(MethodImplOptions.NoInlining)] - internal static String GetResourceStringLocal(String key) { - if (m_resHelper == null) - InitResourceHelper(); - - return m_resHelper.GetResourceString(key); - } - - internal static String GetResourceString(String key) { - return GetResourceStringLocal(key); - } - - // The reason the following overloads exist are to reduce code bloat. - // Since GetResourceString is basically only called when exceptions are - // thrown, we want the code size to be as small as possible. - // Using the params object[] overload works against this since the - // initialization of the array is done inline in the caller at the IL - // level. So we have overloads that simply wrap the params one, and - // the methods they call through to are tagged as NoInlining. - // In mscorlib NoInlining causes the caller and callee to not be inlined - // as it is an assumption of StackCrawlMark use so it is not added - // directly to these methods, but to the ones they call. - // That way they do not bloat either the IL or the generated asm. - - internal static string GetResourceString(string key, object val0) - { - return GetResourceStringFormatted(key, new object[] { val0 }); - } - - internal static string GetResourceString(string key, object val0, object val1) - { - return GetResourceStringFormatted(key, new object[] { val0, val1 }); - } - - internal static string GetResourceString(string key, object val0, object val1, object val2) - { - return GetResourceStringFormatted(key, new object[] { val0, val1, val2 }); - } - - internal static string GetResourceString(string key, object val0, object val1, object val2, object val3) - { - return GetResourceStringFormatted(key, new object[] { val0, val1, val2, val3 }); - } - - internal static String GetResourceString(string key, params object[] values) - { - return GetResourceStringFormatted(key, values); + return st.ToString(System.Diagnostics.StackTrace.TraceFormat.Normal); } - // NoInlining causes the caller and callee to not be inlined in mscorlib as it is an assumption of StackCrawlMark use - [MethodImpl(MethodImplOptions.NoInlining)] - private static String GetResourceStringFormatted(string key, params object[] values) + public static extern bool HasShutdownStarted { - string rs = GetResourceString(key); - return String.Format(CultureInfo.CurrentCulture, rs, values); - } - - public static extern bool HasShutdownStarted { - [MethodImplAttribute(MethodImplOptions.InternalCall)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] get; } internal static bool UserInteractive { - get { + get + { return true; } } @@ -684,11 +459,11 @@ namespace System { // TODO: Consider flushing the executionIdCache on Wait operations or similar // actions that are likely to result in changing the executing core [ThreadStatic] - static int t_executionIdCache; + private static int t_executionIdCache; - const int ExecutionIdCacheShift = 16; - const int ExecutionIdCacheCountDownMask = (1 << ExecutionIdCacheShift) - 1; - const int ExecutionIdRefreshRate = 5000; + private const int ExecutionIdCacheShift = 16; + private const int ExecutionIdCacheCountDownMask = (1 << ExecutionIdCacheShift) - 1; + private const int ExecutionIdRefreshRate = 5000; private static int RefreshExecutionId() { @@ -784,19 +559,19 @@ namespace System { } if (variable.Length == 0) { - throw new ArgumentException(GetResourceString("Argument_StringZeroLength"), nameof(variable)); + throw new ArgumentException(SR.Argument_StringZeroLength, nameof(variable)); } if (variable[0] == '\0') { - throw new ArgumentException(GetResourceString("Argument_StringFirstCharIsZero"), nameof(variable)); + throw new ArgumentException(SR.Argument_StringFirstCharIsZero, nameof(variable)); } if (variable.Length >= MaxEnvVariableValueLength) { - throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(variable)); + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(variable)); } if (variable.IndexOf('=') != -1) { - throw new ArgumentException(GetResourceString("Argument_IllegalEnvVarName"), nameof(variable)); + throw new ArgumentException(SR.Argument_IllegalEnvVarName, nameof(variable)); } if (string.IsNullOrEmpty(value) || value[0] == '\0') @@ -806,7 +581,7 @@ namespace System { } else if (value.Length >= MaxEnvVariableValueLength) { - throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(value)); + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); } } @@ -816,7 +591,7 @@ namespace System { target != EnvironmentVariableTarget.Machine && target != EnvironmentVariableTarget.User) { - throw new ArgumentOutOfRangeException(nameof(target), target, SR.Format(GetResourceString("Arg_EnumIllegalVal"), target)); + throw new ArgumentOutOfRangeException(nameof(target), target, SR.Format(SR.Arg_EnumIllegalVal, target)); } } @@ -867,13 +642,6 @@ namespace System { 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); @@ -917,7 +685,7 @@ namespace System { } else { - throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)target)); } using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false)) @@ -929,13 +697,6 @@ namespace System { 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 Dictionary<string, string>(0); - } - return GetRawEnvironmentVariables(); } @@ -963,7 +724,7 @@ namespace System { } else { - throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)target)); } using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false)) @@ -987,13 +748,6 @@ namespace System { 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(); @@ -1006,7 +760,7 @@ namespace System { 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")); + throw new ArgumentException(SR.Format(SR.Argument_LongEnvVarValue)); default: throw new ArgumentException(Win32Native.GetMessage(errorCode)); } @@ -1045,7 +799,7 @@ namespace System { const int MaxUserEnvVariableLength = 255; if (variable.Length >= MaxUserEnvVariableLength) { - throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(variable)); + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(variable)); } baseKey = Registry.CurrentUser; @@ -1053,7 +807,7 @@ namespace System { } else { - throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target)); + throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)target)); } using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: true)) @@ -1066,7 +820,7 @@ namespace System { } else { - environmentKey.SetValue(variable, value); + environmentKey.SetStringValue(variable, value); } } } |