Environment.Variables.Windows.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Buffers;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. namespace System
  11. {
  12. public static partial class Environment
  13. {
  14. private static string GetEnvironmentVariableCore(string variable)
  15. {
  16. Span<char> buffer = stackalloc char[128]; // a somewhat reasonable default size
  17. int requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);
  18. if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND)
  19. {
  20. return null;
  21. }
  22. if (requiredSize <= buffer.Length)
  23. {
  24. return new string(buffer.Slice(0, requiredSize));
  25. }
  26. char[] chars = ArrayPool<char>.Shared.Rent(requiredSize);
  27. try
  28. {
  29. buffer = chars;
  30. requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);
  31. if ((requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND) ||
  32. requiredSize > buffer.Length)
  33. {
  34. return null;
  35. }
  36. return new string(buffer.Slice(0, requiredSize));
  37. }
  38. finally
  39. {
  40. ArrayPool<char>.Shared.Return(chars);
  41. }
  42. }
  43. private static void SetEnvironmentVariableCore(string variable, string value)
  44. {
  45. if (!Interop.Kernel32.SetEnvironmentVariable(variable, value))
  46. {
  47. int errorCode = Marshal.GetLastWin32Error();
  48. switch (errorCode)
  49. {
  50. case Interop.Errors.ERROR_ENVVAR_NOT_FOUND:
  51. // Allow user to try to clear a environment variable
  52. return;
  53. case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
  54. // The error message from Win32 is "The filename or extension is too long",
  55. // which is not accurate.
  56. throw new ArgumentException(SR.Format(SR.Argument_LongEnvVarValue));
  57. case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
  58. case Interop.Errors.ERROR_NO_SYSTEM_RESOURCES:
  59. throw new OutOfMemoryException(Interop.Kernel32.GetMessage(errorCode));
  60. default:
  61. throw new ArgumentException(Interop.Kernel32.GetMessage(errorCode));
  62. }
  63. }
  64. }
  65. public static unsafe IDictionary GetEnvironmentVariables()
  66. {
  67. char* pStrings = Interop.Kernel32.GetEnvironmentStrings();
  68. if (pStrings == null)
  69. {
  70. throw new OutOfMemoryException();
  71. }
  72. try
  73. {
  74. // Format for GetEnvironmentStrings is:
  75. // [=HiddenVar=value\0]* [Variable=value\0]* \0
  76. // See the description of Environment Blocks in MSDN's
  77. // CreateProcess page (null-terminated array of null-terminated strings).
  78. // Search for terminating \0\0 (two unicode \0's).
  79. char* p = pStrings;
  80. while (!(*p == '\0' && *(p + 1) == '\0'))
  81. {
  82. p++;
  83. }
  84. Span<char> block = new Span<char>(pStrings, (int)(p - pStrings + 1));
  85. // Format for GetEnvironmentStrings is:
  86. // (=HiddenVar=value\0 | Variable=value\0)* \0
  87. // See the description of Environment Blocks in MSDN's
  88. // CreateProcess page (null-terminated array of null-terminated strings).
  89. // Note the =HiddenVar's aren't always at the beginning.
  90. // Copy strings out, parsing into pairs and inserting into the table.
  91. // The first few environment variable entries start with an '='.
  92. // The current working directory of every drive (except for those drives
  93. // you haven't cd'ed into in your DOS window) are stored in the
  94. // environment block (as =C:=pwd) and the program's exit code is
  95. // as well (=ExitCode=00000000).
  96. var results = new Hashtable();
  97. for (int i = 0; i < block.Length; i++)
  98. {
  99. int startKey = i;
  100. // Skip to key. On some old OS, the environment block can be corrupted.
  101. // Some will not have '=', so we need to check for '\0'.
  102. while (block[i] != '=' && block[i] != '\0')
  103. {
  104. i++;
  105. }
  106. if (block[i] == '\0')
  107. {
  108. continue;
  109. }
  110. // Skip over environment variables starting with '='
  111. if (i - startKey == 0)
  112. {
  113. while (block[i] != 0)
  114. {
  115. i++;
  116. }
  117. continue;
  118. }
  119. string key = new string(block.Slice(startKey, i - startKey));
  120. i++; // skip over '='
  121. int startValue = i;
  122. while (block[i] != 0)
  123. {
  124. i++; // Read to end of this entry
  125. }
  126. string value = new string(block.Slice(startValue, i - startValue)); // skip over 0 handled by for loop's i++
  127. try
  128. {
  129. results.Add(key, value);
  130. }
  131. catch (ArgumentException)
  132. {
  133. // Throw and catch intentionally to provide non-fatal notification about corrupted environment block
  134. }
  135. }
  136. return results;
  137. }
  138. finally
  139. {
  140. bool success = Interop.Kernel32.FreeEnvironmentStrings(pStrings);
  141. Debug.Assert(success);
  142. }
  143. }
  144. }
  145. }