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.
5 using System
.Collections
;
6 using System
.Diagnostics
;
8 using System
.Reflection
;
9 using System
.Runtime
.InteropServices
;
15 public static partial class Environment
17 internal static bool IsWindows8OrAbove
=> WindowsVersion
.IsWindows8OrAbove
;
19 private static string? GetEnvironmentVariableFromRegistry(string variable
, bool fromMachine
)
21 Debug
.Assert(variable
!= null);
24 if (ApplicationModel
.IsUap
)
25 return null; // Systems without the Windows registry pretend that it's always empty.
28 using (RegistryKey
? environmentKey
= OpenEnvironmentKeyIfExists(fromMachine
, writable
: false))
30 return environmentKey
?.GetValue(variable
) as string;
34 private static void SetEnvironmentVariableFromRegistry(string variable
, string? value, bool fromMachine
)
36 Debug
.Assert(variable
!= null);
39 if (ApplicationModel
.IsUap
)
40 return; // Systems without the Windows registry pretend that it's always empty.
43 const int MaxUserEnvVariableLength
= 255; // User-wide env vars stored in the registry have names limited to 255 chars
44 if (!fromMachine
&& variable
.Length
>= MaxUserEnvVariableLength
)
46 throw new ArgumentException(SR
.Argument_LongEnvVarValue
, nameof(variable
));
49 using (RegistryKey
? environmentKey
= OpenEnvironmentKeyIfExists(fromMachine
, writable
: true))
51 if (environmentKey
!= null)
55 environmentKey
.DeleteValue(variable
, throwOnMissingValue
: false);
59 environmentKey
.SetValue(variable
, value);
64 // send a WM_SETTINGCHANGE message to all windows
65 IntPtr r
= Interop
.User32
.SendMessageTimeout(new IntPtr(Interop
.User32
.HWND_BROADCAST
), Interop
.User32
.WM_SETTINGCHANGE
, IntPtr
.Zero
, "Environment", 0, 1000, IntPtr
.Zero
);
66 Debug
.Assert(r
!= IntPtr
.Zero
, "SetEnvironmentVariable failed: " + Marshal
.GetLastWin32Error());
69 private static IDictionary
GetEnvironmentVariablesFromRegistry(bool fromMachine
)
71 var results
= new Hashtable();
73 if (ApplicationModel
.IsUap
) // Systems without the Windows registry pretend that it's always empty.
77 using (RegistryKey
? environmentKey
= OpenEnvironmentKeyIfExists(fromMachine
, writable
: false))
79 if (environmentKey
!= null)
81 foreach (string name
in environmentKey
.GetValueNames())
83 string? value = environmentKey
.GetValue(name
, "").ToString();
86 results
.Add(name
, value);
88 catch (ArgumentException
)
90 // Throw and catch intentionally to provide non-fatal notification about corrupted environment block
99 private static RegistryKey
? OpenEnvironmentKeyIfExists(bool fromMachine
, bool writable
)
106 baseKey
= Registry
.LocalMachine
;
107 keyName
= @"System\CurrentControlSet\Control\Session Manager\Environment";
111 baseKey
= Registry
.CurrentUser
;
112 keyName
= "Environment";
115 return baseKey
.OpenSubKey(keyName
, writable
: writable
);
118 public static string UserName
123 if (ApplicationModel
.IsUap
)
124 return "Windows User";
127 // 40 should be enough as we're asking for the SAM compatible name (DOMAIN\User).
128 // The max length should be 15 (domain) + 1 (separator) + 20 (name) + null. If for
129 // some reason it isn't, we'll grow the buffer.
131 // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
132 // https://msdn.microsoft.com/en-us/library/ms679635.aspx
134 Span
<char> initialBuffer
= stackalloc char[40];
135 var builder
= new ValueStringBuilder(initialBuffer
);
136 GetUserName(ref builder
);
138 ReadOnlySpan
<char> name
= builder
.AsSpan();
139 int index
= name
.IndexOf('\\');
142 // In the form of DOMAIN\User, cut off DOMAIN\
143 name
= name
.Slice(index
+ 1);
146 return name
.ToString();
150 private static void GetUserName(ref ValueStringBuilder builder
)
153 while (Interop
.Secur32
.GetUserNameExW(Interop
.Secur32
.NameSamCompatible
, ref builder
.GetPinnableReference(), ref size
) == Interop
.BOOLEAN
.FALSE
)
155 if (Marshal
.GetLastWin32Error() == Interop
.Errors
.ERROR_MORE_DATA
)
157 builder
.EnsureCapacity(checked((int)size
));
166 builder
.Length
= (int)size
;
169 public static string UserDomainName
174 if (ApplicationModel
.IsUap
)
175 return "Windows Domain";
178 // See the comment in UserName
179 Span
<char> initialBuffer
= stackalloc char[40];
180 var builder
= new ValueStringBuilder(initialBuffer
);
181 GetUserName(ref builder
);
183 ReadOnlySpan
<char> name
= builder
.AsSpan();
184 int index
= name
.IndexOf('\\');
187 // In the form of DOMAIN\User, cut off \User and return
188 return name
.Slice(0, index
).ToString();
191 // In theory we should never get use out of LookupAccountNameW as the above API should
192 // always return what we need. Can't find any clues in the historical sources, however.
194 // Domain names aren't typically long.
195 // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
196 Span
<char> initialDomainNameBuffer
= stackalloc char[64];
197 var domainBuilder
= new ValueStringBuilder(initialBuffer
);
198 uint length
= (uint)domainBuilder
.Capacity
;
200 // This API will fail to return the domain name without a buffer for the SID.
201 // SIDs are never over 68 bytes long.
202 Span
<byte> sid
= stackalloc byte[68];
205 while (!Interop
.Advapi32
.LookupAccountNameW(null, ref builder
.GetPinnableReference(), ref MemoryMarshal
.GetReference(sid
),
206 ref sidLength
, ref domainBuilder
.GetPinnableReference(), ref length
, out _
))
208 int error
= Marshal
.GetLastWin32Error();
210 // The docs don't call this out clearly, but experimenting shows that the error returned is the following.
211 if (error
!= Interop
.Errors
.ERROR_INSUFFICIENT_BUFFER
)
213 throw new InvalidOperationException(Win32Marshal
.GetMessage(error
));
216 domainBuilder
.EnsureCapacity((int)length
);
219 domainBuilder
.Length
= (int)length
;
220 return domainBuilder
.ToString();
224 private static string GetFolderPathCore(SpecialFolder folder
, SpecialFolderOption option
)
227 if (ApplicationModel
.IsUap
)
228 return WinRTFolderPaths
.GetFolderPath(folder
, option
);
231 // We're using SHGetKnownFolderPath instead of SHGetFolderPath as SHGetFolderPath is
232 // capped at MAX_PATH.
234 // Because we validate both of the input enums we shouldn't have to care about CSIDL and flag
235 // definitions we haven't mapped. If we remove or loosen the checks we'd have to account
236 // for mapping here (this includes tweaking as SHGetFolderPath would do).
238 // The only SpecialFolderOption defines we have are equivalent to KnownFolderFlags.
244 case SpecialFolder
.ApplicationData
:
245 folderGuid
= Interop
.Shell32
.KnownFolders
.RoamingAppData
;
247 case SpecialFolder
.CommonApplicationData
:
248 folderGuid
= Interop
.Shell32
.KnownFolders
.ProgramData
;
250 case SpecialFolder
.LocalApplicationData
:
251 folderGuid
= Interop
.Shell32
.KnownFolders
.LocalAppData
;
253 case SpecialFolder
.Cookies
:
254 folderGuid
= Interop
.Shell32
.KnownFolders
.Cookies
;
256 case SpecialFolder
.Desktop
:
257 folderGuid
= Interop
.Shell32
.KnownFolders
.Desktop
;
259 case SpecialFolder
.Favorites
:
260 folderGuid
= Interop
.Shell32
.KnownFolders
.Favorites
;
262 case SpecialFolder
.History
:
263 folderGuid
= Interop
.Shell32
.KnownFolders
.History
;
265 case SpecialFolder
.InternetCache
:
266 folderGuid
= Interop
.Shell32
.KnownFolders
.InternetCache
;
268 case SpecialFolder
.Programs
:
269 folderGuid
= Interop
.Shell32
.KnownFolders
.Programs
;
271 case SpecialFolder
.MyComputer
:
272 folderGuid
= Interop
.Shell32
.KnownFolders
.ComputerFolder
;
274 case SpecialFolder
.MyMusic
:
275 folderGuid
= Interop
.Shell32
.KnownFolders
.Music
;
277 case SpecialFolder
.MyPictures
:
278 folderGuid
= Interop
.Shell32
.KnownFolders
.Pictures
;
280 case SpecialFolder
.MyVideos
:
281 folderGuid
= Interop
.Shell32
.KnownFolders
.Videos
;
283 case SpecialFolder
.Recent
:
284 folderGuid
= Interop
.Shell32
.KnownFolders
.Recent
;
286 case SpecialFolder
.SendTo
:
287 folderGuid
= Interop
.Shell32
.KnownFolders
.SendTo
;
289 case SpecialFolder
.StartMenu
:
290 folderGuid
= Interop
.Shell32
.KnownFolders
.StartMenu
;
292 case SpecialFolder
.Startup
:
293 folderGuid
= Interop
.Shell32
.KnownFolders
.Startup
;
295 case SpecialFolder
.System
:
296 folderGuid
= Interop
.Shell32
.KnownFolders
.System
;
298 case SpecialFolder
.Templates
:
299 folderGuid
= Interop
.Shell32
.KnownFolders
.Templates
;
301 case SpecialFolder
.DesktopDirectory
:
302 folderGuid
= Interop
.Shell32
.KnownFolders
.Desktop
;
304 case SpecialFolder
.Personal
:
306 // case SpecialFolder.MyDocuments:
307 folderGuid
= Interop
.Shell32
.KnownFolders
.Documents
;
309 case SpecialFolder
.ProgramFiles
:
310 folderGuid
= Interop
.Shell32
.KnownFolders
.ProgramFiles
;
312 case SpecialFolder
.CommonProgramFiles
:
313 folderGuid
= Interop
.Shell32
.KnownFolders
.ProgramFilesCommon
;
315 case SpecialFolder
.AdminTools
:
316 folderGuid
= Interop
.Shell32
.KnownFolders
.AdminTools
;
318 case SpecialFolder
.CDBurning
:
319 folderGuid
= Interop
.Shell32
.KnownFolders
.CDBurning
;
321 case SpecialFolder
.CommonAdminTools
:
322 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonAdminTools
;
324 case SpecialFolder
.CommonDocuments
:
325 folderGuid
= Interop
.Shell32
.KnownFolders
.PublicDocuments
;
327 case SpecialFolder
.CommonMusic
:
328 folderGuid
= Interop
.Shell32
.KnownFolders
.PublicMusic
;
330 case SpecialFolder
.CommonOemLinks
:
331 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonOEMLinks
;
333 case SpecialFolder
.CommonPictures
:
334 folderGuid
= Interop
.Shell32
.KnownFolders
.PublicPictures
;
336 case SpecialFolder
.CommonStartMenu
:
337 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonStartMenu
;
339 case SpecialFolder
.CommonPrograms
:
340 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonPrograms
;
342 case SpecialFolder
.CommonStartup
:
343 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonStartup
;
345 case SpecialFolder
.CommonDesktopDirectory
:
346 folderGuid
= Interop
.Shell32
.KnownFolders
.PublicDesktop
;
348 case SpecialFolder
.CommonTemplates
:
349 folderGuid
= Interop
.Shell32
.KnownFolders
.CommonTemplates
;
351 case SpecialFolder
.CommonVideos
:
352 folderGuid
= Interop
.Shell32
.KnownFolders
.PublicVideos
;
354 case SpecialFolder
.Fonts
:
355 folderGuid
= Interop
.Shell32
.KnownFolders
.Fonts
;
357 case SpecialFolder
.NetworkShortcuts
:
358 folderGuid
= Interop
.Shell32
.KnownFolders
.NetHood
;
360 case SpecialFolder
.PrinterShortcuts
:
361 folderGuid
= Interop
.Shell32
.KnownFolders
.PrintersFolder
;
363 case SpecialFolder
.UserProfile
:
364 folderGuid
= Interop
.Shell32
.KnownFolders
.Profile
;
366 case SpecialFolder
.CommonProgramFilesX86
:
367 folderGuid
= Interop
.Shell32
.KnownFolders
.ProgramFilesCommonX86
;
369 case SpecialFolder
.ProgramFilesX86
:
370 folderGuid
= Interop
.Shell32
.KnownFolders
.ProgramFilesX86
;
372 case SpecialFolder
.Resources
:
373 folderGuid
= Interop
.Shell32
.KnownFolders
.ResourceDir
;
375 case SpecialFolder
.LocalizedResources
:
376 folderGuid
= Interop
.Shell32
.KnownFolders
.LocalizedResourcesDir
;
378 case SpecialFolder
.SystemX86
:
379 folderGuid
= Interop
.Shell32
.KnownFolders
.SystemX86
;
381 case SpecialFolder
.Windows
:
382 folderGuid
= Interop
.Shell32
.KnownFolders
.Windows
;
388 return GetKnownFolderPath(folderGuid
, option
);
391 private static string GetKnownFolderPath(string folderGuid
, SpecialFolderOption option
)
393 Guid folderId
= new Guid(folderGuid
);
395 int hr
= Interop
.Shell32
.SHGetKnownFolderPath(folderId
, (uint)option
, IntPtr
.Zero
, out string path
);
396 if (hr
!= 0) // Not S_OK
405 private static class WinRTFolderPaths
407 private static Func
<SpecialFolder
, SpecialFolderOption
, string>? s_winRTFolderPathsGetFolderPath
;
409 public static string GetFolderPath(SpecialFolder folder
, SpecialFolderOption option
)
411 if (s_winRTFolderPathsGetFolderPath
== null)
413 Type
? winRtFolderPathsType
= Type
.GetType("System.WinRTFolderPaths, System.Runtime.WindowsRuntime, Version=4.0.14.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError
: false);
414 MethodInfo
? getFolderPathsMethod
= winRtFolderPathsType
?.GetMethod("GetFolderPath", BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
, null, new Type
[] { typeof(SpecialFolder), typeof(SpecialFolderOption) }
, null);
415 var d
= (Func
<SpecialFolder
, SpecialFolderOption
, string>?)getFolderPathsMethod
?.CreateDelegate(typeof(Func
<SpecialFolder
, SpecialFolderOption
, string>));
416 s_winRTFolderPathsGetFolderPath
= d
?? delegate { return string.Empty; }
;
419 return s_winRTFolderPathsGetFolderPath(folder
, option
);
424 // Seperate type so a .cctor is not created for Enviroment which then would be triggered during startup
425 private static class WindowsVersion
427 // Cache the value in static readonly that can be optimized out by the JIT
428 internal static readonly bool IsWindows8OrAbove
= GetIsWindows8OrAbove();
430 private static bool GetIsWindows8OrAbove()
432 ulong conditionMask
= Interop
.Kernel32
.VerSetConditionMask(0, Interop
.Kernel32
.VER_MAJORVERSION
, Interop
.Kernel32
.VER_GREATER_EQUAL
);
433 conditionMask
= Interop
.Kernel32
.VerSetConditionMask(conditionMask
, Interop
.Kernel32
.VER_MINORVERSION
, Interop
.Kernel32
.VER_GREATER_EQUAL
);
434 conditionMask
= Interop
.Kernel32
.VerSetConditionMask(conditionMask
, Interop
.Kernel32
.VER_SERVICEPACKMAJOR
, Interop
.Kernel32
.VER_GREATER_EQUAL
);
435 conditionMask
= Interop
.Kernel32
.VerSetConditionMask(conditionMask
, Interop
.Kernel32
.VER_SERVICEPACKMINOR
, Interop
.Kernel32
.VER_GREATER_EQUAL
);
437 // Windows 8 version is 6.2
438 Interop
.Kernel32
.OSVERSIONINFOEX version
= default;
441 version
.dwOSVersionInfoSize
= sizeof(Interop
.Kernel32
.OSVERSIONINFOEX
);
443 version
.dwMajorVersion
= 6;
444 version
.dwMinorVersion
= 2;
445 version
.wServicePackMajor
= 0;
446 version
.wServicePackMinor
= 0;
448 return Interop
.Kernel32
.VerifyVersionInfoW(ref version
,
449 Interop
.Kernel32
.VER_MAJORVERSION
| Interop
.Kernel32
.VER_MINORVERSION
| Interop
.Kernel32
.VER_SERVICEPACKMAJOR
| Interop
.Kernel32
.VER_SERVICEPACKMINOR
,