Environment.Variables.Windows.cs 6.3 KB

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