2 // Microsoft.Win32/Win32RegistryApi.cs: wrapper for win32 registry API
5 // Erik LeBel (eriklebel@yahoo.ca)
6 // Jackson Harper (jackson@ximian.com)
7 // Miguel de Icaza (miguel@gnome.org)
9 // Copyright (C) Erik LeBel 2004
10 // (C) 2004, 2005 Novell, Inc (http://www.novell.com)
14 // Copyright (C) 2004, 2005 Novell, Inc (http://www.novell.com)
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39 using System.Collections;
40 using System.Collections.Generic;
42 using System.Runtime.InteropServices;
43 using System.Security;
45 using Microsoft.Win32.SafeHandles;
47 namespace Microsoft.Win32
50 /// Function stubs, constants and helper functions for
51 /// the Win32 registry manipulation utilities.
53 internal class Win32RegistryApi : IRegistryApi
55 // bit masks for registry key open access permissions
56 const int OpenRegKeyRead = 0x00020019;
57 const int OpenRegKeyWrite = 0x00020006;
59 // FIXME must be a way to determin this dynamically?
60 const int Int32ByteSize = 4;
61 const int Int64ByteSize = 8;
63 // FIXME this is hard coded on Mono, can it be determined dynamically?
64 readonly int NativeBytesPerCharacter = Marshal.SystemDefaultCharSize;
66 const int RegOptionsNonVolatile = 0x00000000;
67 const int RegOptionsVolatile = 0x00000001;
69 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegCreateKeyEx")]
70 static extern int RegCreateKeyEx (IntPtr keyBase, string keyName, int reserved,
71 IntPtr lpClass, int options, int access, IntPtr securityAttrs,
72 out IntPtr keyHandle, out int disposition);
74 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegCloseKey")]
75 static extern int RegCloseKey (IntPtr keyHandle);
77 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode)]
78 static extern int RegConnectRegistry (string machineName, IntPtr hKey,
79 out IntPtr keyHandle);
81 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegFlushKey")]
82 private static extern int RegFlushKey (IntPtr keyHandle);
84 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegOpenKeyEx")]
85 private static extern int RegOpenKeyEx (IntPtr keyBase,
86 string keyName, IntPtr reserved, int access,
87 out IntPtr keyHandle);
89 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegDeleteKey")]
90 private static extern int RegDeleteKey (IntPtr keyHandle, string valueName);
92 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegDeleteValue")]
93 private static extern int RegDeleteValue (IntPtr keyHandle, string valueName);
95 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegEnumKeyExW")]
96 internal unsafe static extern int RegEnumKeyEx (IntPtr keyHandle, int dwIndex,
97 char* lpName, ref int lpcbName, int[] lpReserved,
98 [Out]StringBuilder lpClass, int[] lpcbClass,
99 long[] lpftLastWriteTime);
101 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegEnumValue")]
102 internal unsafe static extern int RegEnumValue (IntPtr hKey, int dwIndex,
103 char* lpValueName, ref int lpcbValueName,
104 IntPtr lpReserved_MustBeZero, int[] lpType, byte[] lpData,
107 // [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
108 // private static extern int RegSetValueEx (IntPtr keyBase,
109 // string valueName, IntPtr reserved, RegistryValueKind type,
110 // StringBuilder data, int rawDataLength);
112 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
113 private static extern int RegSetValueEx (IntPtr keyBase,
114 string valueName, IntPtr reserved, RegistryValueKind type,
115 string data, int rawDataLength);
117 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
118 private static extern int RegSetValueEx (IntPtr keyBase,
119 string valueName, IntPtr reserved, RegistryValueKind type,
120 byte[] rawData, int rawDataLength);
122 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
123 private static extern int RegSetValueEx (IntPtr keyBase,
124 string valueName, IntPtr reserved, RegistryValueKind type,
125 ref int data, int rawDataLength);
127 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
128 private static extern int RegSetValueEx (IntPtr keyBase,
129 string valueName, IntPtr reserved, RegistryValueKind type,
130 ref long data, int rawDataLength);
132 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
133 private static extern int RegQueryValueEx (IntPtr keyBase,
134 string valueName, IntPtr reserved, ref RegistryValueKind type,
135 IntPtr zero, ref int dataSize);
137 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
138 private static extern int RegQueryValueEx (IntPtr keyBase,
139 string valueName, IntPtr reserved, ref RegistryValueKind type,
140 [Out] byte[] data, ref int dataSize);
142 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
143 private static extern int RegQueryValueEx (IntPtr keyBase,
144 string valueName, IntPtr reserved, ref RegistryValueKind type,
145 ref int data, ref int dataSize);
147 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
148 private static extern int RegQueryValueEx (IntPtr keyBase,
149 string valueName, IntPtr reserved, ref RegistryValueKind type,
150 ref long data, ref int dataSize);
152 [DllImport ("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint="RegQueryInfoKeyW")]
153 internal static extern int RegQueryInfoKey (IntPtr hKey, [Out]StringBuilder lpClass,
154 int[] lpcbClass, IntPtr lpReserved_MustBeZero, ref int lpcSubKeys,
155 int[] lpcbMaxSubKeyLen, int[] lpcbMaxClassLen,
156 ref int lpcValues, int[] lpcbMaxValueNameLen,
157 int[] lpcbMaxValueLen, int[] lpcbSecurityDescriptor,
158 int[] lpftLastWriteTime);
160 // Returns our handle from the RegistryKey
161 public IntPtr GetHandle (RegistryKey key)
163 return (IntPtr) key.InternalHandle;
166 static bool IsHandleValid (RegistryKey key)
168 return key.InternalHandle != null;
171 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
173 RegistryValueKind type = 0;
175 IntPtr handle = GetHandle (rkey);
176 int result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, IntPtr.Zero, ref size);
178 if (result == Win32ResultCode.FileNotFound || result == Win32ResultCode.MarkedForDeletion)
179 return RegistryValueKind.Unknown;
185 /// Acctually read a registry value. Requires knowledge of the
186 /// value's type and size.
188 public object GetValue (RegistryKey rkey, string name, object defaultValue, RegistryValueOptions options)
190 RegistryValueKind type = 0;
193 IntPtr handle = GetHandle (rkey);
194 int result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, IntPtr.Zero, ref size);
196 if (result == Win32ResultCode.FileNotFound || result == Win32ResultCode.MarkedForDeletion) {
200 if (result != Win32ResultCode.MoreData && result != Win32ResultCode.Success ) {
201 GenerateException (result);
204 if (type == RegistryValueKind.String) {
206 result = GetBinaryValue (rkey, name, type, out data, size);
207 obj = RegistryKey.DecodeString (data);
208 } else if (type == RegistryValueKind.ExpandString) {
210 result = GetBinaryValue (rkey, name, type, out data, size);
211 obj = RegistryKey.DecodeString (data);
212 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
213 obj = Environment.ExpandEnvironmentVariables ((string) obj);
214 } else if (type == RegistryValueKind.DWord) {
216 result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, ref data, ref size);
218 } else if (type == RegistryValueKind.QWord) {
220 result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, ref data, ref size);
222 } else if (type == RegistryValueKind.Binary) {
224 result = GetBinaryValue (rkey, name, type, out data, size);
226 } else if (type == RegistryValueKind.MultiString) {
229 result = GetBinaryValue (rkey, name, type, out data, size);
231 if (result == Win32ResultCode.Success)
232 obj = RegistryKey.DecodeString (data).Split ('\0');
234 // should never get here
235 throw new SystemException ();
238 // check result codes again:
239 if (result != Win32ResultCode.Success)
241 GenerateException (result);
249 // This version has to do extra checking, make sure that the requested
250 // valueKind matches the type of the value being stored
252 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
254 Type type = value.GetType ();
255 IntPtr handle = GetHandle (rkey);
258 case RegistryValueKind.QWord:
260 long rawValue = Convert.ToInt64 (value);
261 CheckResult (RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.QWord, ref rawValue, Int64ByteSize));
263 } catch (OverflowException) {
266 case RegistryValueKind.DWord:
268 int rawValue = Convert.ToInt32 (value);
269 CheckResult (RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.DWord, ref rawValue, Int32ByteSize));
271 } catch (OverflowException) {
274 case RegistryValueKind.Binary:
275 if (type == typeof (byte[])) {
276 byte[] rawValue = (byte[]) value;
277 CheckResult (RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.Binary, rawValue, rawValue.Length));
281 case RegistryValueKind.MultiString:
282 if (type == typeof (string[])) {
283 string[] vals = (string[]) value;
284 StringBuilder fullStringValue = new StringBuilder ();
285 foreach (string v in vals)
287 fullStringValue.Append (v);
288 fullStringValue.Append ('\0');
290 fullStringValue.Append ('\0');
292 byte[] rawValue = Encoding.Unicode.GetBytes (fullStringValue.ToString ());
294 CheckResult (RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.MultiString, rawValue, rawValue.Length));
298 case RegistryValueKind.String:
299 case RegistryValueKind.ExpandString:
300 if (type == typeof (string)) {
301 string rawValue = String.Format ("{0}{1}", value, '\0');
302 CheckResult (RegSetValueEx (handle, name, IntPtr.Zero, valueKind, rawValue,
303 rawValue.Length * NativeBytesPerCharacter));
309 throw new ArgumentException ("Only string and byte arrays can written as registry values");
314 throw new ArgumentException ("Type does not match the valueKind");
317 public void SetValue (RegistryKey rkey, string name, object value)
319 Type type = value.GetType ();
321 IntPtr handle = GetHandle (rkey);
323 if (type == typeof (int)) {
324 int rawValue = (int)value;
325 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.DWord, ref rawValue, Int32ByteSize);
326 } else if (type == typeof (byte[])) {
327 byte[] rawValue = (byte[]) value;
328 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.Binary, rawValue, rawValue.Length);
329 } else if (type == typeof (string[])) {
330 string[] vals = (string[]) value;
331 StringBuilder fullStringValue = new StringBuilder ();
332 foreach (string v in vals)
334 fullStringValue.Append (v);
335 fullStringValue.Append ('\0');
337 fullStringValue.Append ('\0');
339 byte[] rawValue = Encoding.Unicode.GetBytes (fullStringValue.ToString ());
341 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.MultiString, rawValue, rawValue.Length);
342 } else if (type.IsArray) {
343 throw new ArgumentException ("Only string and byte arrays can written as registry values");
345 string rawValue = String.Format ("{0}{1}", value, '\0');
346 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.String, rawValue,
347 rawValue.Length * NativeBytesPerCharacter);
350 // handle the result codes
351 if (result != Win32ResultCode.Success)
353 GenerateException (result);
358 /// Get a binary value.
360 private int GetBinaryValue (RegistryKey rkey, string name, RegistryValueKind type, out byte[] data, int size)
362 byte[] internalData = new byte [size];
363 IntPtr handle = GetHandle (rkey);
364 int result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, internalData, ref size);
369 // MSDN defines the following limits for registry key names & values:
370 // Key Name: 255 characters
371 // Value name: 16,383 Unicode characters
372 // Value: either 1 MB or current available memory, depending on registry format.
373 private const int MaxKeyLength = 255;
374 private const int MaxValueLength = 16383;
376 public int SubKeyCount (RegistryKey rkey)
380 int ret = RegQueryInfoKey (GetHandle (rkey),
384 ref subkeys, // subkeys
393 if (ret != Win32ResultCode.Success)
394 GenerateException (ret);
398 public int ValueCount (RegistryKey rkey)
402 int ret = RegQueryInfoKey (GetHandle (rkey),
409 ref values, // values
414 if (ret != Win32ResultCode.Success)
415 GenerateException (ret);
419 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
421 IntPtr handle = new IntPtr ((int) hKey);
424 int result = RegConnectRegistry (machineName, handle, out keyHandle);
425 if (result != Win32ResultCode.Success)
426 GenerateException (result);
428 return new RegistryKey (hKey, keyHandle, true);
431 public RegistryKey OpenSubKey (RegistryKey rkey, string keyName, bool writable)
433 int access = OpenRegKeyRead;
434 if (writable) access |= OpenRegKeyWrite;
435 IntPtr handle = GetHandle (rkey);
438 int result = RegOpenKeyEx (handle, keyName, IntPtr.Zero, access, out subKeyHandle);
440 if (result == Win32ResultCode.FileNotFound || result == Win32ResultCode.MarkedForDeletion)
443 if (result != Win32ResultCode.Success)
444 GenerateException (result);
446 return new RegistryKey (subKeyHandle, CombineName (rkey, keyName), writable);
449 public void Flush (RegistryKey rkey)
451 if (!IsHandleValid (rkey))
453 IntPtr handle = GetHandle (rkey);
454 RegFlushKey (handle);
457 public void Close (RegistryKey rkey)
459 if (!IsHandleValid (rkey))
461 SafeRegistryHandle safe_handle = rkey.Handle;
462 if (safe_handle != null) {
463 // closes the unmanaged pointer for us.
464 safe_handle.Close ();
467 IntPtr handle = GetHandle (rkey);
468 RegCloseKey (handle);
471 public RegistryKey FromHandle (SafeRegistryHandle handle)
473 // At this point we can't tell whether the key is writable
474 // or not (nor the name), so we let the error check code handle it later, as
476 return new RegistryKey (handle.DangerousGetHandle (), String.Empty, true);
479 public RegistryKey CreateSubKey (RegistryKey rkey, string keyName)
481 IntPtr handle = GetHandle (rkey);
484 int result = RegCreateKeyEx (handle , keyName, 0, IntPtr.Zero,
485 RegOptionsNonVolatile,
486 OpenRegKeyRead | OpenRegKeyWrite, IntPtr.Zero, out subKeyHandle, out disposition);
488 if (result != Win32ResultCode.Success) {
489 GenerateException (result);
492 return new RegistryKey (subKeyHandle, CombineName (rkey, keyName),
496 public RegistryKey CreateSubKey (RegistryKey rkey, string keyName, RegistryOptions options)
498 IntPtr handle = GetHandle (rkey);
501 int result = RegCreateKeyEx (handle , keyName, 0, IntPtr.Zero,
502 options == RegistryOptions.Volatile ? RegOptionsVolatile : RegOptionsNonVolatile,
503 OpenRegKeyRead | OpenRegKeyWrite, IntPtr.Zero, out subKeyHandle, out disposition);
505 if (result != Win32ResultCode.Success)
506 GenerateException (result);
508 return new RegistryKey (subKeyHandle, CombineName (rkey, keyName),
512 public void DeleteKey (RegistryKey rkey, string keyName, bool shouldThrowWhenKeyMissing)
514 IntPtr handle = GetHandle (rkey);
515 int result = RegDeleteKey (handle, keyName);
517 if (result == Win32ResultCode.FileNotFound) {
518 if (shouldThrowWhenKeyMissing)
519 throw new ArgumentException ("key " + keyName);
523 if (result != Win32ResultCode.Success)
524 GenerateException (result);
527 public void DeleteValue (RegistryKey rkey, string value, bool shouldThrowWhenKeyMissing)
529 IntPtr handle = GetHandle (rkey);
530 int result = RegDeleteValue (handle, value);
532 if (result == Win32ResultCode.MarkedForDeletion)
535 if (result == Win32ResultCode.FileNotFound){
536 if (shouldThrowWhenKeyMissing)
537 throw new ArgumentException ("value " + value);
541 if (result != Win32ResultCode.Success)
542 GenerateException (result);
545 public unsafe string [] GetSubKeyNames (RegistryKey rkey)
547 int subkeys = SubKeyCount (rkey);
548 var names = new string [subkeys]; // Returns 0-length array if empty.
551 var hkey = GetHandle (rkey);
552 char[] name = new char [MaxKeyLength + 1];
555 fixed (char* namePtr = &name [0]) {
556 for (int i = 0; i < subkeys; i++) {
557 namelen = name.Length; // Don't remove this. The API's doesn't work if this is not properly initialised.
558 int ret = RegEnumKeyEx (hkey,
568 GenerateException (ret);
569 names [i] = new String (namePtr);
577 public unsafe string [] GetValueNames (RegistryKey rkey)
579 int values = ValueCount (rkey);
580 String[] names = new String [values];
583 IntPtr hkey = GetHandle (rkey);
584 char[] name = new char [MaxValueLength + 1];
587 fixed (char* namePtr = &name [0]) {
588 for (int i = 0; i < values; i++) {
589 namelen = name.Length;
591 int ret = RegEnumValue (hkey,
600 if (ret != Win32ResultCode.Success && ret != Win32Native.ERROR_MORE_DATA)
601 GenerateException (ret);
603 names [i] = new String (namePtr);
611 private void CheckResult (int result)
613 if (result != Win32ResultCode.Success) {
614 GenerateException (result);
619 /// convert a win32 error code into an appropriate exception.
621 private void GenerateException (int errorCode)
624 case Win32ResultCode.FileNotFound:
625 case Win32ResultCode.InvalidParameter:
626 throw new ArgumentException ();
627 case Win32ResultCode.AccessDenied:
628 throw new SecurityException ();
629 case Win32ResultCode.NetworkPathNotFound:
630 throw new IOException ("The network path was not found.");
631 case Win32ResultCode.InvalidHandle:
632 throw new IOException ("Invalid handle.");
633 case Win32ResultCode.MarkedForDeletion:
634 throw RegistryKey.CreateMarkedForDeletionException ();
635 case Win32ResultCode.ChildMustBeVolatile:
636 throw new IOException ("Cannot create a stable subkey under a volatile parent key.");
638 // unidentified system exception
639 throw new SystemException ();
643 public string ToString (RegistryKey rkey)
649 /// utility: Combine the sub key name to the current name to produce a
650 /// fully qualified sub key name.
652 internal static string CombineName (RegistryKey rkey, string localName)
654 return String.Concat (rkey.Name, "\\", localName);