Environment.Win32.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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.Collections;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Reflection;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using Internal.Win32;
  11. namespace System
  12. {
  13. public static partial class Environment
  14. {
  15. internal static bool IsWindows8OrAbove => WindowsVersion.IsWindows8OrAbove;
  16. private static string? GetEnvironmentVariableFromRegistry(string variable, bool fromMachine)
  17. {
  18. Debug.Assert(variable != null);
  19. #if FEATURE_APPX
  20. if (ApplicationModel.IsUap)
  21. return null; // Systems without the Windows registry pretend that it's always empty.
  22. #endif
  23. using (RegistryKey? environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: false))
  24. {
  25. return environmentKey?.GetValue(variable) as string;
  26. }
  27. }
  28. private static void SetEnvironmentVariableFromRegistry(string variable, string? value, bool fromMachine)
  29. {
  30. Debug.Assert(variable != null);
  31. #if FEATURE_APPX
  32. if (ApplicationModel.IsUap)
  33. return; // Systems without the Windows registry pretend that it's always empty.
  34. #endif
  35. const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars
  36. if (!fromMachine && variable.Length >= MaxUserEnvVariableLength)
  37. {
  38. throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(variable));
  39. }
  40. using (RegistryKey? environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: true))
  41. {
  42. if (environmentKey != null)
  43. {
  44. if (value == null)
  45. {
  46. environmentKey.DeleteValue(variable, throwOnMissingValue: false);
  47. }
  48. else
  49. {
  50. environmentKey.SetValue(variable, value);
  51. }
  52. }
  53. }
  54. // send a WM_SETTINGCHANGE message to all windows
  55. IntPtr r = Interop.User32.SendMessageTimeout(new IntPtr(Interop.User32.HWND_BROADCAST), Interop.User32.WM_SETTINGCHANGE, IntPtr.Zero, "Environment", 0, 1000, IntPtr.Zero);
  56. Debug.Assert(r != IntPtr.Zero, "SetEnvironmentVariable failed: " + Marshal.GetLastWin32Error());
  57. }
  58. private static IDictionary GetEnvironmentVariablesFromRegistry(bool fromMachine)
  59. {
  60. var results = new Hashtable();
  61. #if FEATURE_APPX
  62. if (ApplicationModel.IsUap) // Systems without the Windows registry pretend that it's always empty.
  63. return results;
  64. #endif
  65. using (RegistryKey? environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: false))
  66. {
  67. if (environmentKey != null)
  68. {
  69. foreach (string name in environmentKey.GetValueNames())
  70. {
  71. string? value = environmentKey.GetValue(name, "").ToString();
  72. try
  73. {
  74. results.Add(name, value);
  75. }
  76. catch (ArgumentException)
  77. {
  78. // Throw and catch intentionally to provide non-fatal notification about corrupted environment block
  79. }
  80. }
  81. }
  82. }
  83. return results;
  84. }
  85. private static RegistryKey? OpenEnvironmentKeyIfExists(bool fromMachine, bool writable)
  86. {
  87. RegistryKey baseKey;
  88. string keyName;
  89. if (fromMachine)
  90. {
  91. baseKey = Registry.LocalMachine;
  92. keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
  93. }
  94. else
  95. {
  96. baseKey = Registry.CurrentUser;
  97. keyName = "Environment";
  98. }
  99. return baseKey.OpenSubKey(keyName, writable: writable);
  100. }
  101. public static string UserName
  102. {
  103. get
  104. {
  105. #if FEATURE_APPX
  106. if (ApplicationModel.IsUap)
  107. return "Windows User";
  108. #endif
  109. // 40 should be enough as we're asking for the SAM compatible name (DOMAIN\User).
  110. // The max length should be 15 (domain) + 1 (separator) + 20 (name) + null. If for
  111. // some reason it isn't, we'll grow the buffer.
  112. // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
  113. // https://msdn.microsoft.com/en-us/library/ms679635.aspx
  114. Span<char> initialBuffer = stackalloc char[40];
  115. var builder = new ValueStringBuilder(initialBuffer);
  116. GetUserName(ref builder);
  117. ReadOnlySpan<char> name = builder.AsSpan();
  118. int index = name.IndexOf('\\');
  119. if (index != -1)
  120. {
  121. // In the form of DOMAIN\User, cut off DOMAIN\
  122. name = name.Slice(index + 1);
  123. }
  124. return name.ToString();
  125. }
  126. }
  127. private static void GetUserName(ref ValueStringBuilder builder)
  128. {
  129. uint size = 0;
  130. while (Interop.Secur32.GetUserNameExW(Interop.Secur32.NameSamCompatible, ref builder.GetPinnableReference(), ref size) == Interop.BOOLEAN.FALSE)
  131. {
  132. if (Marshal.GetLastWin32Error() == Interop.Errors.ERROR_MORE_DATA)
  133. {
  134. builder.EnsureCapacity(checked((int)size));
  135. }
  136. else
  137. {
  138. builder.Length = 0;
  139. return;
  140. }
  141. }
  142. builder.Length = (int)size;
  143. }
  144. public static string UserDomainName
  145. {
  146. get
  147. {
  148. #if FEATURE_APPX
  149. if (ApplicationModel.IsUap)
  150. return "Windows Domain";
  151. #endif
  152. // See the comment in UserName
  153. Span<char> initialBuffer = stackalloc char[40];
  154. var builder = new ValueStringBuilder(initialBuffer);
  155. GetUserName(ref builder);
  156. ReadOnlySpan<char> name = builder.AsSpan();
  157. int index = name.IndexOf('\\');
  158. if (index != -1)
  159. {
  160. // In the form of DOMAIN\User, cut off \User and return
  161. return name.Slice(0, index).ToString();
  162. }
  163. // In theory we should never get use out of LookupAccountNameW as the above API should
  164. // always return what we need. Can't find any clues in the historical sources, however.
  165. // Domain names aren't typically long.
  166. // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
  167. Span<char> initialDomainNameBuffer = stackalloc char[64];
  168. var domainBuilder = new ValueStringBuilder(initialBuffer);
  169. uint length = (uint)domainBuilder.Capacity;
  170. // This API will fail to return the domain name without a buffer for the SID.
  171. // SIDs are never over 68 bytes long.
  172. Span<byte> sid = stackalloc byte[68];
  173. uint sidLength = 68;
  174. while (!Interop.Advapi32.LookupAccountNameW(null, ref builder.GetPinnableReference(), ref MemoryMarshal.GetReference(sid),
  175. ref sidLength, ref domainBuilder.GetPinnableReference(), ref length, out _))
  176. {
  177. int error = Marshal.GetLastWin32Error();
  178. // The docs don't call this out clearly, but experimenting shows that the error returned is the following.
  179. if (error != Interop.Errors.ERROR_INSUFFICIENT_BUFFER)
  180. {
  181. throw new InvalidOperationException(Win32Marshal.GetMessage(error));
  182. }
  183. domainBuilder.EnsureCapacity((int)length);
  184. }
  185. domainBuilder.Length = (int)length;
  186. return domainBuilder.ToString();
  187. }
  188. }
  189. private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option)
  190. {
  191. #if FEATURE_APPX
  192. if (ApplicationModel.IsUap)
  193. return WinRTFolderPaths.GetFolderPath(folder, option);
  194. #endif
  195. // We're using SHGetKnownFolderPath instead of SHGetFolderPath as SHGetFolderPath is
  196. // capped at MAX_PATH.
  197. //
  198. // Because we validate both of the input enums we shouldn't have to care about CSIDL and flag
  199. // definitions we haven't mapped. If we remove or loosen the checks we'd have to account
  200. // for mapping here (this includes tweaking as SHGetFolderPath would do).
  201. //
  202. // The only SpecialFolderOption defines we have are equivalent to KnownFolderFlags.
  203. string folderGuid;
  204. switch (folder)
  205. {
  206. case SpecialFolder.ApplicationData:
  207. folderGuid = Interop.Shell32.KnownFolders.RoamingAppData;
  208. break;
  209. case SpecialFolder.CommonApplicationData:
  210. folderGuid = Interop.Shell32.KnownFolders.ProgramData;
  211. break;
  212. case SpecialFolder.LocalApplicationData:
  213. folderGuid = Interop.Shell32.KnownFolders.LocalAppData;
  214. break;
  215. case SpecialFolder.Cookies:
  216. folderGuid = Interop.Shell32.KnownFolders.Cookies;
  217. break;
  218. case SpecialFolder.Desktop:
  219. folderGuid = Interop.Shell32.KnownFolders.Desktop;
  220. break;
  221. case SpecialFolder.Favorites:
  222. folderGuid = Interop.Shell32.KnownFolders.Favorites;
  223. break;
  224. case SpecialFolder.History:
  225. folderGuid = Interop.Shell32.KnownFolders.History;
  226. break;
  227. case SpecialFolder.InternetCache:
  228. folderGuid = Interop.Shell32.KnownFolders.InternetCache;
  229. break;
  230. case SpecialFolder.Programs:
  231. folderGuid = Interop.Shell32.KnownFolders.Programs;
  232. break;
  233. case SpecialFolder.MyComputer:
  234. folderGuid = Interop.Shell32.KnownFolders.ComputerFolder;
  235. break;
  236. case SpecialFolder.MyMusic:
  237. folderGuid = Interop.Shell32.KnownFolders.Music;
  238. break;
  239. case SpecialFolder.MyPictures:
  240. folderGuid = Interop.Shell32.KnownFolders.Pictures;
  241. break;
  242. case SpecialFolder.MyVideos:
  243. folderGuid = Interop.Shell32.KnownFolders.Videos;
  244. break;
  245. case SpecialFolder.Recent:
  246. folderGuid = Interop.Shell32.KnownFolders.Recent;
  247. break;
  248. case SpecialFolder.SendTo:
  249. folderGuid = Interop.Shell32.KnownFolders.SendTo;
  250. break;
  251. case SpecialFolder.StartMenu:
  252. folderGuid = Interop.Shell32.KnownFolders.StartMenu;
  253. break;
  254. case SpecialFolder.Startup:
  255. folderGuid = Interop.Shell32.KnownFolders.Startup;
  256. break;
  257. case SpecialFolder.System:
  258. folderGuid = Interop.Shell32.KnownFolders.System;
  259. break;
  260. case SpecialFolder.Templates:
  261. folderGuid = Interop.Shell32.KnownFolders.Templates;
  262. break;
  263. case SpecialFolder.DesktopDirectory:
  264. folderGuid = Interop.Shell32.KnownFolders.Desktop;
  265. break;
  266. case SpecialFolder.Personal:
  267. // Same as Personal
  268. // case SpecialFolder.MyDocuments:
  269. folderGuid = Interop.Shell32.KnownFolders.Documents;
  270. break;
  271. case SpecialFolder.ProgramFiles:
  272. folderGuid = Interop.Shell32.KnownFolders.ProgramFiles;
  273. break;
  274. case SpecialFolder.CommonProgramFiles:
  275. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesCommon;
  276. break;
  277. case SpecialFolder.AdminTools:
  278. folderGuid = Interop.Shell32.KnownFolders.AdminTools;
  279. break;
  280. case SpecialFolder.CDBurning:
  281. folderGuid = Interop.Shell32.KnownFolders.CDBurning;
  282. break;
  283. case SpecialFolder.CommonAdminTools:
  284. folderGuid = Interop.Shell32.KnownFolders.CommonAdminTools;
  285. break;
  286. case SpecialFolder.CommonDocuments:
  287. folderGuid = Interop.Shell32.KnownFolders.PublicDocuments;
  288. break;
  289. case SpecialFolder.CommonMusic:
  290. folderGuid = Interop.Shell32.KnownFolders.PublicMusic;
  291. break;
  292. case SpecialFolder.CommonOemLinks:
  293. folderGuid = Interop.Shell32.KnownFolders.CommonOEMLinks;
  294. break;
  295. case SpecialFolder.CommonPictures:
  296. folderGuid = Interop.Shell32.KnownFolders.PublicPictures;
  297. break;
  298. case SpecialFolder.CommonStartMenu:
  299. folderGuid = Interop.Shell32.KnownFolders.CommonStartMenu;
  300. break;
  301. case SpecialFolder.CommonPrograms:
  302. folderGuid = Interop.Shell32.KnownFolders.CommonPrograms;
  303. break;
  304. case SpecialFolder.CommonStartup:
  305. folderGuid = Interop.Shell32.KnownFolders.CommonStartup;
  306. break;
  307. case SpecialFolder.CommonDesktopDirectory:
  308. folderGuid = Interop.Shell32.KnownFolders.PublicDesktop;
  309. break;
  310. case SpecialFolder.CommonTemplates:
  311. folderGuid = Interop.Shell32.KnownFolders.CommonTemplates;
  312. break;
  313. case SpecialFolder.CommonVideos:
  314. folderGuid = Interop.Shell32.KnownFolders.PublicVideos;
  315. break;
  316. case SpecialFolder.Fonts:
  317. folderGuid = Interop.Shell32.KnownFolders.Fonts;
  318. break;
  319. case SpecialFolder.NetworkShortcuts:
  320. folderGuid = Interop.Shell32.KnownFolders.NetHood;
  321. break;
  322. case SpecialFolder.PrinterShortcuts:
  323. folderGuid = Interop.Shell32.KnownFolders.PrintersFolder;
  324. break;
  325. case SpecialFolder.UserProfile:
  326. folderGuid = Interop.Shell32.KnownFolders.Profile;
  327. break;
  328. case SpecialFolder.CommonProgramFilesX86:
  329. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesCommonX86;
  330. break;
  331. case SpecialFolder.ProgramFilesX86:
  332. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesX86;
  333. break;
  334. case SpecialFolder.Resources:
  335. folderGuid = Interop.Shell32.KnownFolders.ResourceDir;
  336. break;
  337. case SpecialFolder.LocalizedResources:
  338. folderGuid = Interop.Shell32.KnownFolders.LocalizedResourcesDir;
  339. break;
  340. case SpecialFolder.SystemX86:
  341. folderGuid = Interop.Shell32.KnownFolders.SystemX86;
  342. break;
  343. case SpecialFolder.Windows:
  344. folderGuid = Interop.Shell32.KnownFolders.Windows;
  345. break;
  346. default:
  347. return string.Empty;
  348. }
  349. return GetKnownFolderPath(folderGuid, option);
  350. }
  351. private static string GetKnownFolderPath(string folderGuid, SpecialFolderOption option)
  352. {
  353. Guid folderId = new Guid(folderGuid);
  354. int hr = Interop.Shell32.SHGetKnownFolderPath(folderId, (uint)option, IntPtr.Zero, out string path);
  355. if (hr != 0) // Not S_OK
  356. {
  357. return string.Empty;
  358. }
  359. return path;
  360. }
  361. #if FEATURE_APPX
  362. private static class WinRTFolderPaths
  363. {
  364. private static Func<SpecialFolder, SpecialFolderOption, string>? s_winRTFolderPathsGetFolderPath;
  365. public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption option)
  366. {
  367. if (s_winRTFolderPathsGetFolderPath == null)
  368. {
  369. Type? winRtFolderPathsType = Type.GetType("System.WinRTFolderPaths, System.Runtime.WindowsRuntime, Version=4.0.14.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false);
  370. MethodInfo? getFolderPathsMethod = winRtFolderPathsType?.GetMethod("GetFolderPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(SpecialFolder), typeof(SpecialFolderOption) }, null);
  371. var d = (Func<SpecialFolder, SpecialFolderOption, string>?)getFolderPathsMethod?.CreateDelegate(typeof(Func<SpecialFolder, SpecialFolderOption, string>));
  372. s_winRTFolderPathsGetFolderPath = d ?? delegate { return string.Empty; };
  373. }
  374. return s_winRTFolderPathsGetFolderPath(folder, option);
  375. }
  376. }
  377. #endif
  378. // Seperate type so a .cctor is not created for Enviroment which then would be triggered during startup
  379. private static class WindowsVersion
  380. {
  381. // Cache the value in static readonly that can be optimized out by the JIT
  382. internal static readonly bool IsWindows8OrAbove = GetIsWindows8OrAbove();
  383. private static bool GetIsWindows8OrAbove()
  384. {
  385. ulong conditionMask = Interop.Kernel32.VerSetConditionMask(0, Interop.Kernel32.VER_MAJORVERSION, Interop.Kernel32.VER_GREATER_EQUAL);
  386. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_MINORVERSION, Interop.Kernel32.VER_GREATER_EQUAL);
  387. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMAJOR, Interop.Kernel32.VER_GREATER_EQUAL);
  388. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMINOR, Interop.Kernel32.VER_GREATER_EQUAL);
  389. // Windows 8 version is 6.2
  390. Interop.Kernel32.OSVERSIONINFOEX version = default;
  391. unsafe
  392. {
  393. version.dwOSVersionInfoSize = sizeof(Interop.Kernel32.OSVERSIONINFOEX);
  394. }
  395. version.dwMajorVersion = 6;
  396. version.dwMinorVersion = 2;
  397. version.wServicePackMajor = 0;
  398. version.wServicePackMinor = 0;
  399. return Interop.Kernel32.VerifyVersionInfoW(ref version,
  400. Interop.Kernel32.VER_MAJORVERSION | Interop.Kernel32.VER_MINORVERSION | Interop.Kernel32.VER_SERVICEPACKMAJOR | Interop.Kernel32.VER_SERVICEPACKMINOR,
  401. conditionMask);
  402. }
  403. }
  404. }
  405. }