Environment.Win32.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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. var builder = new ValueStringBuilder(stackalloc char[40]);
  115. GetUserName(ref builder);
  116. ReadOnlySpan<char> name = builder.AsSpan();
  117. int index = name.IndexOf('\\');
  118. if (index != -1)
  119. {
  120. // In the form of DOMAIN\User, cut off DOMAIN\
  121. name = name.Slice(index + 1);
  122. }
  123. string result = name.ToString();
  124. builder.Dispose();
  125. return result;
  126. }
  127. }
  128. private static void GetUserName(ref ValueStringBuilder builder)
  129. {
  130. uint size = 0;
  131. while (Interop.Secur32.GetUserNameExW(Interop.Secur32.NameSamCompatible, ref builder.GetPinnableReference(), ref size) == Interop.BOOLEAN.FALSE)
  132. {
  133. if (Marshal.GetLastWin32Error() == Interop.Errors.ERROR_MORE_DATA)
  134. {
  135. builder.EnsureCapacity(checked((int)size));
  136. }
  137. else
  138. {
  139. builder.Length = 0;
  140. return;
  141. }
  142. }
  143. builder.Length = (int)size;
  144. }
  145. public static string UserDomainName
  146. {
  147. get
  148. {
  149. #if FEATURE_APPX
  150. if (ApplicationModel.IsUap)
  151. return "Windows Domain";
  152. #endif
  153. // See the comment in UserName
  154. var builder = new ValueStringBuilder(stackalloc char[40]);
  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. builder.Length = index;
  162. return builder.ToString();
  163. }
  164. // In theory we should never get use out of LookupAccountNameW as the above API should
  165. // always return what we need. Can't find any clues in the historical sources, however.
  166. // Domain names aren't typically long.
  167. // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
  168. var domainBuilder = new ValueStringBuilder(stackalloc char[64]);
  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. builder.Dispose();
  186. domainBuilder.Length = (int)length;
  187. return domainBuilder.ToString();
  188. }
  189. }
  190. private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option)
  191. {
  192. #if FEATURE_APPX
  193. if (ApplicationModel.IsUap)
  194. return WinRTFolderPaths.GetFolderPath(folder, option);
  195. #endif
  196. // We're using SHGetKnownFolderPath instead of SHGetFolderPath as SHGetFolderPath is
  197. // capped at MAX_PATH.
  198. //
  199. // Because we validate both of the input enums we shouldn't have to care about CSIDL and flag
  200. // definitions we haven't mapped. If we remove or loosen the checks we'd have to account
  201. // for mapping here (this includes tweaking as SHGetFolderPath would do).
  202. //
  203. // The only SpecialFolderOption defines we have are equivalent to KnownFolderFlags.
  204. string folderGuid;
  205. switch (folder)
  206. {
  207. case SpecialFolder.ApplicationData:
  208. folderGuid = Interop.Shell32.KnownFolders.RoamingAppData;
  209. break;
  210. case SpecialFolder.CommonApplicationData:
  211. folderGuid = Interop.Shell32.KnownFolders.ProgramData;
  212. break;
  213. case SpecialFolder.LocalApplicationData:
  214. folderGuid = Interop.Shell32.KnownFolders.LocalAppData;
  215. break;
  216. case SpecialFolder.Cookies:
  217. folderGuid = Interop.Shell32.KnownFolders.Cookies;
  218. break;
  219. case SpecialFolder.Desktop:
  220. folderGuid = Interop.Shell32.KnownFolders.Desktop;
  221. break;
  222. case SpecialFolder.Favorites:
  223. folderGuid = Interop.Shell32.KnownFolders.Favorites;
  224. break;
  225. case SpecialFolder.History:
  226. folderGuid = Interop.Shell32.KnownFolders.History;
  227. break;
  228. case SpecialFolder.InternetCache:
  229. folderGuid = Interop.Shell32.KnownFolders.InternetCache;
  230. break;
  231. case SpecialFolder.Programs:
  232. folderGuid = Interop.Shell32.KnownFolders.Programs;
  233. break;
  234. case SpecialFolder.MyComputer:
  235. folderGuid = Interop.Shell32.KnownFolders.ComputerFolder;
  236. break;
  237. case SpecialFolder.MyMusic:
  238. folderGuid = Interop.Shell32.KnownFolders.Music;
  239. break;
  240. case SpecialFolder.MyPictures:
  241. folderGuid = Interop.Shell32.KnownFolders.Pictures;
  242. break;
  243. case SpecialFolder.MyVideos:
  244. folderGuid = Interop.Shell32.KnownFolders.Videos;
  245. break;
  246. case SpecialFolder.Recent:
  247. folderGuid = Interop.Shell32.KnownFolders.Recent;
  248. break;
  249. case SpecialFolder.SendTo:
  250. folderGuid = Interop.Shell32.KnownFolders.SendTo;
  251. break;
  252. case SpecialFolder.StartMenu:
  253. folderGuid = Interop.Shell32.KnownFolders.StartMenu;
  254. break;
  255. case SpecialFolder.Startup:
  256. folderGuid = Interop.Shell32.KnownFolders.Startup;
  257. break;
  258. case SpecialFolder.System:
  259. folderGuid = Interop.Shell32.KnownFolders.System;
  260. break;
  261. case SpecialFolder.Templates:
  262. folderGuid = Interop.Shell32.KnownFolders.Templates;
  263. break;
  264. case SpecialFolder.DesktopDirectory:
  265. folderGuid = Interop.Shell32.KnownFolders.Desktop;
  266. break;
  267. case SpecialFolder.Personal:
  268. // Same as Personal
  269. // case SpecialFolder.MyDocuments:
  270. folderGuid = Interop.Shell32.KnownFolders.Documents;
  271. break;
  272. case SpecialFolder.ProgramFiles:
  273. folderGuid = Interop.Shell32.KnownFolders.ProgramFiles;
  274. break;
  275. case SpecialFolder.CommonProgramFiles:
  276. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesCommon;
  277. break;
  278. case SpecialFolder.AdminTools:
  279. folderGuid = Interop.Shell32.KnownFolders.AdminTools;
  280. break;
  281. case SpecialFolder.CDBurning:
  282. folderGuid = Interop.Shell32.KnownFolders.CDBurning;
  283. break;
  284. case SpecialFolder.CommonAdminTools:
  285. folderGuid = Interop.Shell32.KnownFolders.CommonAdminTools;
  286. break;
  287. case SpecialFolder.CommonDocuments:
  288. folderGuid = Interop.Shell32.KnownFolders.PublicDocuments;
  289. break;
  290. case SpecialFolder.CommonMusic:
  291. folderGuid = Interop.Shell32.KnownFolders.PublicMusic;
  292. break;
  293. case SpecialFolder.CommonOemLinks:
  294. folderGuid = Interop.Shell32.KnownFolders.CommonOEMLinks;
  295. break;
  296. case SpecialFolder.CommonPictures:
  297. folderGuid = Interop.Shell32.KnownFolders.PublicPictures;
  298. break;
  299. case SpecialFolder.CommonStartMenu:
  300. folderGuid = Interop.Shell32.KnownFolders.CommonStartMenu;
  301. break;
  302. case SpecialFolder.CommonPrograms:
  303. folderGuid = Interop.Shell32.KnownFolders.CommonPrograms;
  304. break;
  305. case SpecialFolder.CommonStartup:
  306. folderGuid = Interop.Shell32.KnownFolders.CommonStartup;
  307. break;
  308. case SpecialFolder.CommonDesktopDirectory:
  309. folderGuid = Interop.Shell32.KnownFolders.PublicDesktop;
  310. break;
  311. case SpecialFolder.CommonTemplates:
  312. folderGuid = Interop.Shell32.KnownFolders.CommonTemplates;
  313. break;
  314. case SpecialFolder.CommonVideos:
  315. folderGuid = Interop.Shell32.KnownFolders.PublicVideos;
  316. break;
  317. case SpecialFolder.Fonts:
  318. folderGuid = Interop.Shell32.KnownFolders.Fonts;
  319. break;
  320. case SpecialFolder.NetworkShortcuts:
  321. folderGuid = Interop.Shell32.KnownFolders.NetHood;
  322. break;
  323. case SpecialFolder.PrinterShortcuts:
  324. folderGuid = Interop.Shell32.KnownFolders.PrintersFolder;
  325. break;
  326. case SpecialFolder.UserProfile:
  327. folderGuid = Interop.Shell32.KnownFolders.Profile;
  328. break;
  329. case SpecialFolder.CommonProgramFilesX86:
  330. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesCommonX86;
  331. break;
  332. case SpecialFolder.ProgramFilesX86:
  333. folderGuid = Interop.Shell32.KnownFolders.ProgramFilesX86;
  334. break;
  335. case SpecialFolder.Resources:
  336. folderGuid = Interop.Shell32.KnownFolders.ResourceDir;
  337. break;
  338. case SpecialFolder.LocalizedResources:
  339. folderGuid = Interop.Shell32.KnownFolders.LocalizedResourcesDir;
  340. break;
  341. case SpecialFolder.SystemX86:
  342. folderGuid = Interop.Shell32.KnownFolders.SystemX86;
  343. break;
  344. case SpecialFolder.Windows:
  345. folderGuid = Interop.Shell32.KnownFolders.Windows;
  346. break;
  347. default:
  348. return string.Empty;
  349. }
  350. return GetKnownFolderPath(folderGuid, option);
  351. }
  352. private static string GetKnownFolderPath(string folderGuid, SpecialFolderOption option)
  353. {
  354. Guid folderId = new Guid(folderGuid);
  355. int hr = Interop.Shell32.SHGetKnownFolderPath(folderId, (uint)option, IntPtr.Zero, out string path);
  356. if (hr != 0) // Not S_OK
  357. {
  358. return string.Empty;
  359. }
  360. return path;
  361. }
  362. #if FEATURE_APPX
  363. private static class WinRTFolderPaths
  364. {
  365. private static Func<SpecialFolder, SpecialFolderOption, string>? s_winRTFolderPathsGetFolderPath;
  366. public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption option)
  367. {
  368. if (s_winRTFolderPathsGetFolderPath == null)
  369. {
  370. Type? winRtFolderPathsType = Type.GetType("System.WinRTFolderPaths, System.Runtime.WindowsRuntime, Version=4.0.14.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false);
  371. MethodInfo? getFolderPathsMethod = winRtFolderPathsType?.GetMethod("GetFolderPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(SpecialFolder), typeof(SpecialFolderOption) }, null);
  372. var d = (Func<SpecialFolder, SpecialFolderOption, string>?)getFolderPathsMethod?.CreateDelegate(typeof(Func<SpecialFolder, SpecialFolderOption, string>));
  373. s_winRTFolderPathsGetFolderPath = d ?? delegate { return string.Empty; };
  374. }
  375. return s_winRTFolderPathsGetFolderPath(folder, option);
  376. }
  377. }
  378. #endif
  379. // Seperate type so a .cctor is not created for Enviroment which then would be triggered during startup
  380. private static class WindowsVersion
  381. {
  382. // Cache the value in static readonly that can be optimized out by the JIT
  383. internal static readonly bool IsWindows8OrAbove = GetIsWindows8OrAbove();
  384. private static bool GetIsWindows8OrAbove()
  385. {
  386. ulong conditionMask = Interop.Kernel32.VerSetConditionMask(0, Interop.Kernel32.VER_MAJORVERSION, Interop.Kernel32.VER_GREATER_EQUAL);
  387. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_MINORVERSION, Interop.Kernel32.VER_GREATER_EQUAL);
  388. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMAJOR, Interop.Kernel32.VER_GREATER_EQUAL);
  389. conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMINOR, Interop.Kernel32.VER_GREATER_EQUAL);
  390. // Windows 8 version is 6.2
  391. Interop.Kernel32.OSVERSIONINFOEX version = default;
  392. unsafe
  393. {
  394. version.dwOSVersionInfoSize = sizeof(Interop.Kernel32.OSVERSIONINFOEX);
  395. }
  396. version.dwMajorVersion = 6;
  397. version.dwMinorVersion = 2;
  398. version.wServicePackMajor = 0;
  399. version.wServicePackMinor = 0;
  400. return Interop.Kernel32.VerifyVersionInfoW(ref version,
  401. Interop.Kernel32.VER_MAJORVERSION | Interop.Kernel32.VER_MINORVERSION | Interop.Kernel32.VER_SERVICEPACKMAJOR | Interop.Kernel32.VER_SERVICEPACKMINOR,
  402. conditionMask);
  403. }
  404. }
  405. }
  406. }