RegistryKey.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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;
  5. using System.Buffers;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Security;
  10. using Internal.Win32.SafeHandles;
  11. //
  12. // A minimal version of RegistryKey that supports just what CoreLib needs.
  13. //
  14. // Internal.Win32 namespace avoids confusion with the public standalone Microsoft.Win32.Registry implementation
  15. // that lives in corefx.
  16. //
  17. namespace Internal.Win32
  18. {
  19. internal sealed class RegistryKey : IDisposable
  20. {
  21. // MSDN defines the following limits for registry key names & values:
  22. // Key Name: 255 characters
  23. // Value name: 16,383 Unicode characters
  24. // Value: either 1 MB or current available memory, depending on registry format.
  25. private const int MaxKeyLength = 255;
  26. private const int MaxValueLength = 16383;
  27. private SafeRegistryHandle _hkey = null;
  28. private RegistryKey(SafeRegistryHandle hkey)
  29. {
  30. _hkey = hkey;
  31. }
  32. void IDisposable.Dispose()
  33. {
  34. if (_hkey != null)
  35. {
  36. _hkey.Dispose();
  37. }
  38. }
  39. public void DeleteValue(string name, bool throwOnMissingValue)
  40. {
  41. int errorCode = Interop.Advapi32.RegDeleteValue(_hkey, name);
  42. //
  43. // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE
  44. // This still means the name doesn't exist. We need to be consistent with previous OS.
  45. //
  46. if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND ||
  47. errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
  48. {
  49. if (throwOnMissingValue)
  50. {
  51. throw new ArgumentException(SR.Arg_RegSubKeyValueAbsent);
  52. }
  53. else
  54. {
  55. // Otherwise, reset and just return giving no indication to the user.
  56. // (For compatibility)
  57. errorCode = 0;
  58. }
  59. }
  60. // We really should throw an exception here if errorCode was bad,
  61. // but we can't for compatibility reasons.
  62. Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode);
  63. }
  64. internal static RegistryKey OpenBaseKey(IntPtr hKey)
  65. {
  66. return new RegistryKey(new SafeRegistryHandle(hKey, false));
  67. }
  68. public RegistryKey OpenSubKey(string name)
  69. {
  70. return OpenSubKey(name, false);
  71. }
  72. public RegistryKey OpenSubKey(string name, bool writable)
  73. {
  74. // Make sure that the name does not contain double slahes
  75. Debug.Assert(name.IndexOf("\\\\") == -1);
  76. int ret = Interop.Advapi32.RegOpenKeyEx(_hkey,
  77. name,
  78. 0,
  79. writable ?
  80. Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE :
  81. Interop.Advapi32.RegistryOperations.KEY_READ,
  82. out SafeRegistryHandle result);
  83. if (ret == 0 && !result.IsInvalid)
  84. {
  85. return new RegistryKey(result);
  86. }
  87. // Return null if we didn't find the key.
  88. if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
  89. {
  90. // We need to throw SecurityException here for compatibility reasons,
  91. // although UnauthorizedAccessException will make more sense.
  92. throw new SecurityException(SR.Security_RegistryPermission);
  93. }
  94. return null;
  95. }
  96. public string[] GetSubKeyNames()
  97. {
  98. var names = new List<string>();
  99. char[] name = ArrayPool<char>.Shared.Rent(MaxKeyLength + 1);
  100. try
  101. {
  102. int result;
  103. int nameLength = name.Length;
  104. while ((result = Interop.Advapi32.RegEnumKeyEx(
  105. _hkey,
  106. names.Count,
  107. name,
  108. ref nameLength,
  109. null,
  110. null,
  111. null,
  112. null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
  113. {
  114. switch (result)
  115. {
  116. case Interop.Errors.ERROR_SUCCESS:
  117. names.Add(new string(name, 0, nameLength));
  118. nameLength = name.Length;
  119. break;
  120. default:
  121. // Throw the error
  122. Win32Error(result, null);
  123. break;
  124. }
  125. }
  126. }
  127. finally
  128. {
  129. ArrayPool<char>.Shared.Return(name);
  130. }
  131. return names.ToArray();
  132. }
  133. public unsafe string[] GetValueNames()
  134. {
  135. var names = new List<string>();
  136. // Names in the registry aren't usually very long, although they can go to as large
  137. // as 16383 characters (MaxValueLength).
  138. //
  139. // Every call to RegEnumValue will allocate another buffer to get the data from
  140. // NtEnumerateValueKey before copying it back out to our passed in buffer. This can
  141. // add up quickly- we'll try to keep the memory pressure low and grow the buffer
  142. // only if needed.
  143. char[] name = ArrayPool<char>.Shared.Rent(100);
  144. try
  145. {
  146. int result;
  147. int nameLength = name.Length;
  148. while ((result = Interop.Advapi32.RegEnumValue(
  149. _hkey,
  150. names.Count,
  151. name,
  152. ref nameLength,
  153. IntPtr.Zero,
  154. null,
  155. null,
  156. null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
  157. {
  158. switch (result)
  159. {
  160. // The size is only ever reported back correctly in the case
  161. // of ERROR_SUCCESS. It will almost always be changed, however.
  162. case Interop.Errors.ERROR_SUCCESS:
  163. names.Add(new string(name, 0, nameLength));
  164. break;
  165. case Interop.Errors.ERROR_MORE_DATA:
  166. {
  167. char[] oldName = name;
  168. int oldLength = oldName.Length;
  169. name = null;
  170. ArrayPool<char>.Shared.Return(oldName);
  171. name = ArrayPool<char>.Shared.Rent(checked(oldLength * 2));
  172. }
  173. break;
  174. default:
  175. // Throw the error
  176. Win32Error(result, null);
  177. break;
  178. }
  179. // Always set the name length back to the buffer size
  180. nameLength = name.Length;
  181. }
  182. }
  183. finally
  184. {
  185. if (name != null)
  186. ArrayPool<char>.Shared.Return(name);
  187. }
  188. return names.ToArray();
  189. }
  190. public object GetValue(string name)
  191. {
  192. return GetValue(name, null);
  193. }
  194. public object GetValue(string name, object defaultValue)
  195. {
  196. object data = defaultValue;
  197. int type = 0;
  198. int datasize = 0;
  199. int ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, (byte[])null, ref datasize);
  200. if (ret != 0)
  201. {
  202. // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data).
  203. // Some OS's returned ERROR_MORE_DATA even in success cases, so we
  204. // want to continue on through the function.
  205. if (ret != Interop.Errors.ERROR_MORE_DATA)
  206. return data;
  207. }
  208. if (datasize < 0)
  209. {
  210. // unexpected code path
  211. Debug.Fail("[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize");
  212. datasize = 0;
  213. }
  214. switch (type)
  215. {
  216. case Interop.Advapi32.RegistryValues.REG_NONE:
  217. case Interop.Advapi32.RegistryValues.REG_DWORD_BIG_ENDIAN:
  218. case Interop.Advapi32.RegistryValues.REG_BINARY:
  219. {
  220. byte[] blob = new byte[datasize];
  221. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  222. data = blob;
  223. }
  224. break;
  225. case Interop.Advapi32.RegistryValues.REG_QWORD:
  226. { // also REG_QWORD_LITTLE_ENDIAN
  227. if (datasize > 8)
  228. {
  229. // prevent an AV in the edge case that datasize is larger than sizeof(long)
  230. goto case Interop.Advapi32.RegistryValues.REG_BINARY;
  231. }
  232. long blob = 0;
  233. Debug.Assert(datasize == 8, "datasize==8");
  234. // Here, datasize must be 8 when calling this
  235. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, ref blob, ref datasize);
  236. data = blob;
  237. }
  238. break;
  239. case Interop.Advapi32.RegistryValues.REG_DWORD:
  240. { // also REG_DWORD_LITTLE_ENDIAN
  241. if (datasize > 4)
  242. {
  243. // prevent an AV in the edge case that datasize is larger than sizeof(int)
  244. goto case Interop.Advapi32.RegistryValues.REG_QWORD;
  245. }
  246. int blob = 0;
  247. Debug.Assert(datasize == 4, "datasize==4");
  248. // Here, datasize must be four when calling this
  249. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, ref blob, ref datasize);
  250. data = blob;
  251. }
  252. break;
  253. case Interop.Advapi32.RegistryValues.REG_SZ:
  254. {
  255. if (datasize % 2 == 1)
  256. {
  257. // handle the case where the registry contains an odd-byte length (corrupt data?)
  258. try
  259. {
  260. datasize = checked(datasize + 1);
  261. }
  262. catch (OverflowException e)
  263. {
  264. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  265. }
  266. }
  267. char[] blob = new char[datasize / 2];
  268. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  269. if (blob.Length > 0 && blob[blob.Length - 1] == (char)0)
  270. {
  271. data = new string(blob, 0, blob.Length - 1);
  272. }
  273. else
  274. {
  275. // in the very unlikely case the data is missing null termination,
  276. // pass in the whole char[] to prevent truncating a character
  277. data = new string(blob);
  278. }
  279. }
  280. break;
  281. case Interop.Advapi32.RegistryValues.REG_EXPAND_SZ:
  282. {
  283. if (datasize % 2 == 1)
  284. {
  285. // handle the case where the registry contains an odd-byte length (corrupt data?)
  286. try
  287. {
  288. datasize = checked(datasize + 1);
  289. }
  290. catch (OverflowException e)
  291. {
  292. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  293. }
  294. }
  295. char[] blob = new char[datasize / 2];
  296. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  297. if (blob.Length > 0 && blob[blob.Length - 1] == (char)0)
  298. {
  299. data = new string(blob, 0, blob.Length - 1);
  300. }
  301. else
  302. {
  303. // in the very unlikely case the data is missing null termination,
  304. // pass in the whole char[] to prevent truncating a character
  305. data = new string(blob);
  306. }
  307. data = Environment.ExpandEnvironmentVariables((string)data);
  308. }
  309. break;
  310. case Interop.Advapi32.RegistryValues.REG_MULTI_SZ:
  311. {
  312. if (datasize % 2 == 1)
  313. {
  314. // handle the case where the registry contains an odd-byte length (corrupt data?)
  315. try
  316. {
  317. datasize = checked(datasize + 1);
  318. }
  319. catch (OverflowException e)
  320. {
  321. throw new IOException(SR.Arg_RegGetOverflowBug, e);
  322. }
  323. }
  324. char[] blob = new char[datasize / 2];
  325. ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, ref type, blob, ref datasize);
  326. // make sure the string is null terminated before processing the data
  327. if (blob.Length > 0 && blob[blob.Length - 1] != (char)0)
  328. {
  329. Array.Resize(ref blob, blob.Length + 1);
  330. }
  331. string[] strings = Array.Empty<string>();
  332. int stringsCount = 0;
  333. int cur = 0;
  334. int len = blob.Length;
  335. while (ret == 0 && cur < len)
  336. {
  337. int nextNull = cur;
  338. while (nextNull < len && blob[nextNull] != (char)0)
  339. {
  340. nextNull++;
  341. }
  342. string toAdd = null;
  343. if (nextNull < len)
  344. {
  345. Debug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0");
  346. if (nextNull - cur > 0)
  347. {
  348. toAdd = new string(blob, cur, nextNull - cur);
  349. }
  350. else
  351. {
  352. // we found an empty string. But if we're at the end of the data,
  353. // it's just the extra null terminator.
  354. if (nextNull != len - 1)
  355. {
  356. toAdd = string.Empty;
  357. }
  358. }
  359. }
  360. else
  361. {
  362. toAdd = new string(blob, cur, len - cur);
  363. }
  364. cur = nextNull + 1;
  365. if (toAdd != null)
  366. {
  367. if (strings.Length == stringsCount)
  368. {
  369. Array.Resize(ref strings, stringsCount > 0 ? stringsCount * 2 : 4);
  370. }
  371. strings[stringsCount++] = toAdd;
  372. }
  373. }
  374. Array.Resize(ref strings, stringsCount);
  375. data = strings;
  376. }
  377. break;
  378. case Interop.Advapi32.RegistryValues.REG_LINK:
  379. default:
  380. break;
  381. }
  382. return data;
  383. }
  384. // The actual api is SetValue(string name, object value) but we only need to set Strings
  385. // so this is a cut-down version that supports on that.
  386. internal void SetValue(string name, string value)
  387. {
  388. if (value == null)
  389. throw new ArgumentNullException(nameof(value));
  390. if (name != null && name.Length > MaxValueLength)
  391. throw new ArgumentException(SR.Arg_RegValStrLenBug, nameof(name));
  392. int ret = Interop.Advapi32.RegSetValueEx(_hkey,
  393. name,
  394. 0,
  395. Interop.Advapi32.RegistryValues.REG_SZ,
  396. value,
  397. checked(value.Length * 2 + 2));
  398. if (ret != 0)
  399. {
  400. Win32Error(ret, null);
  401. }
  402. }
  403. internal void Win32Error(int errorCode, string str)
  404. {
  405. switch (errorCode)
  406. {
  407. case Interop.Errors.ERROR_ACCESS_DENIED:
  408. if (str != null)
  409. throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str));
  410. else
  411. throw new UnauthorizedAccessException();
  412. case Interop.Errors.ERROR_FILE_NOT_FOUND:
  413. throw new IOException(SR.Arg_RegKeyNotFound, errorCode);
  414. default:
  415. throw new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode);
  416. }
  417. }
  418. }
  419. internal static class Registry
  420. {
  421. /// <summary>Current User Key. This key should be used as the root for all user specific settings.</summary>
  422. public static readonly RegistryKey CurrentUser = RegistryKey.OpenBaseKey(unchecked((IntPtr)(int)0x80000001));
  423. /// <summary>Local Machine key. This key should be used as the root for all machine specific settings.</summary>
  424. public static readonly RegistryKey LocalMachine = RegistryKey.OpenBaseKey(unchecked((IntPtr)(int)0x80000002));
  425. }
  426. }