2 // RegistryKey.cs: a single node in the Windows registry
5 // Miguel de Icaza (miguel@ximian.com)
6 // Erik LeBel (eriklebel@yahoo.ca)
10 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Diagnostics;
36 using System.Runtime.InteropServices;
37 using System.Security;
40 namespace Microsoft.Win32
43 /// Wrapper class for Windows Registry Entry.
45 public sealed class RegistryKey : MarshalByRefObject, IDisposable
47 const char NullChar = '\0';
49 // Arbitrary max size for key/values names that can be fetched.
50 // .NET framework SDK docs say that the max name length that can
51 // be used is 255 characters, we'll allow for a bit more.
52 const int BufferMaxLength = 1024;
54 // FIXME must be a way to determin this dynamically?
55 const int Int32ByteSize = 4;
57 // FIXME this is hard coded on Mono, can it be determined dynamically?
58 readonly int NativeBytesPerCharacter = Marshal.SystemDefaultCharSize;
60 // FIXME this should be determined dynamically.
61 // It will be used to decode some return strings
62 // for which embeded '\0' must be preserved.
63 readonly Encoding Decoder = Encoding.Unicode;
66 IntPtr hkey; // the reg key handle
67 string qname; // the fully qualified registry key name
68 bool isRoot; // is the an instance of a root key?
73 /// Construct an instance of a root registry key entry.
75 internal RegistryKey (RegistryHive hiveId, string keyName)
77 hkey = new IntPtr ((int)hiveId);
85 /// Construct an instance of a registry key entry.
87 internal RegistryKey (IntPtr hkey, string keyName)
96 internal void InitRegistryApi ()
98 if (Path.DirectorySeparatorChar == '\\')
99 reg_api = new Win32RegistryApi ();
102 private IRegistryApi RegistryApi {
105 throw new NotImplementedException ("The registry is" +
106 " only available on Windows.");
112 /// Fetch the inetrnal registry key.
114 private IntPtr Handle {
122 /// Dispose of registry key object. Close the
123 /// key if it's still open.
125 void IDisposable.Dispose ()
128 GC.SuppressFinalize (this);
133 /// Final cleanup of registry key object. Close the
134 /// key if it's still open.
143 /// Get the fully qualified registry key name.
146 get { return qname; }
151 /// Flush the current registry state to disk.
155 RegTrace (" +Flush");
156 RegistryApi.RegFlushKey (Handle);
157 RegTrace (" -Flush");
162 /// Close the current registry key. This may not
163 /// flush the state of the registry right away.
170 RegTrace (" +Close");
171 RegistryApi.RegCloseKey (Handle);
173 RegTrace (" -Close");
178 /// get the number of sub-keys for this key
180 public int SubKeyCount {
182 RegTrace (" +SubKeyCount");
183 AssertKeyStillValid ();
186 byte[] stringBuffer = new byte [BufferMaxLength];
188 for (index = 0; true; index ++)
190 result = RegistryApi.RegEnumKey (Handle, index,
191 stringBuffer, BufferMaxLength);
193 if (result == Win32ResultCode.Success)
196 if (result == Win32ResultCode.NoMoreEntries)
199 // something is wrong!!
200 RegTrace ("Win32Api::ReEnumKey result='{0}' name='{1}'",
202 GenerateException (result);
205 RegTrace (" -SubKeyCount");
212 /// get the number of values for this key
214 public int ValueCount {
216 RegTrace (" +ValueCount");
217 AssertKeyStillValid ();
219 int index, result, type, bufferCapacity;
220 StringBuilder buffer = new StringBuilder (BufferMaxLength);
222 for (index = 0; true; index ++)
225 bufferCapacity = buffer.Capacity;
226 result = RegistryApi.RegEnumValue (Handle, index,
227 buffer, ref bufferCapacity,
228 IntPtr.Zero, ref type,
229 IntPtr.Zero, IntPtr.Zero);
231 if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData)
234 if (result == Win32ResultCode.NoMoreEntries)
237 // something is wrong
238 RegTrace ("Win32Api::RegEnumValue result='{0}' name='{1}'",
240 GenerateException (result);
243 RegTrace (" -ValueCount");
250 /// Set a registry value.
252 public void SetValue (string name, object value)
254 RegTrace (" +SetValue");
255 AssertKeyStillValid ();
258 throw new ArgumentNullException ();
260 Type type = value.GetType ();
263 if (type == typeof (int))
265 int rawValue = (int)value;
266 result = RegistryApi.RegSetValueEx (Handle, name,
267 IntPtr.Zero, RegistryApi.RegDwordType,
268 ref rawValue, Int32ByteSize);
270 else if (type == typeof (byte[]))
272 byte[] rawValue = (byte[]) value;
273 result = RegistryApi.RegSetValueEx (Handle, name,
274 IntPtr.Zero, RegistryApi.RegBinaryType,
275 rawValue, rawValue.Length);
277 else if (type == typeof (string[]))
279 string[] vals = (string[]) value;
280 StringBuilder fullStringValue = new StringBuilder ();
281 foreach (string v in vals)
283 fullStringValue.Append (v);
284 fullStringValue.Append (NullChar);
286 fullStringValue.Append (NullChar);
288 byte[] rawValue = Decoder.GetBytes (fullStringValue.ToString ());
290 result = RegistryApi.RegSetValueEx (Handle, name,
291 IntPtr.Zero, RegistryApi.RegStringArrayType,
292 rawValue, rawValue.Length);
294 else if (type.IsArray)
296 throw new ArgumentException ("Only string and byte arrays can written as registry values");
300 string rawValue = String.Format ("{0}{1}", value, NullChar);
301 result = RegistryApi.RegSetValueEx (Handle, name,
302 IntPtr.Zero, RegistryApi.RegStringType,
303 rawValue, rawValue.Length * NativeBytesPerCharacter);
306 // handle the result codes
307 if (result != Win32ResultCode.Success)
309 RegTrace ("Win32Api::RegSetValueEx: result: {0}", result);
310 GenerateException (result);
313 RegTrace (" -SetValue");
318 /// Open the sub key specified, for read access.
320 public RegistryKey OpenSubKey (string keyName)
322 return OpenSubKey (keyName, false);
327 /// Open the sub key specified.
329 public RegistryKey OpenSubKey (string keyName, bool writtable)
331 RegTrace (" +OpenSubKey");
332 AssertKeyStillValid ();
333 AssertKeyNameNotNull (keyName);
335 int access = RegistryApi.OpenRegKeyRead;
336 if (writtable) access |= RegistryApi.OpenRegKeyWrite;
339 int result = RegistryApi.RegOpenKeyEx (Handle, keyName, IntPtr.Zero,
340 access, out subKeyHandle);
342 if (result == Win32ResultCode.FileNotFound)
344 RegTrace (" -OpenSubKey");
348 if (result != Win32ResultCode.Success)
350 RegTrace ("Win32Api::RegOpenKeyEx result='{0}' key name='{1}'",
351 result, CombineName (keyName));
352 GenerateException (result);
355 RegistryKey subKey = new RegistryKey (subKeyHandle, CombineName (keyName));
356 RegTrace (" -OpenSubKey");
362 /// Get a registry value.
364 public object GetValue (string name)
366 RegTrace (" +GetValue");
367 object obj = GetValueImpl (name, false, null);
368 RegTrace (" -GetValue");
374 /// Get a registry value.
376 public object GetValue (string name, object defaultValue)
378 RegTrace (" +GetValue");
379 object obj = GetValueImpl (name, true, defaultValue);
380 RegTrace (" -GetValue");
386 /// Create a sub key.
388 public RegistryKey CreateSubKey (string keyName)
390 RegTrace (" +CreateSubKey");
391 AssertKeyStillValid ();
392 AssertKeyNameNotNull (keyName);
395 int result = RegistryApi.RegCreateKey (Handle , keyName, out subKeyHandle);
397 if (result != Win32ResultCode.Success)
399 RegTrace ("Win32Api::RegCreateKey: result='{0}' key name='{1}'",
400 result, CombineName (keyName));
401 GenerateException (result);
404 RegistryKey subKey = new RegistryKey (subKeyHandle, CombineName (keyName));
405 RegTrace (" -CreateSubKey");
411 /// Delete the specified subkey.
413 public void DeleteSubKey(string subkey)
415 DeleteSubKey (subkey, true);
420 /// Delete the specified subkey.
422 public void DeleteSubKey(string keyName, bool shouldThrowWhenKeyMissing)
424 RegTrace (" +DeleteSubKey");
425 AssertKeyStillValid ();
426 AssertKeyNameNotNull (keyName);
428 RegistryKey child = OpenSubKey (keyName);
432 if (shouldThrowWhenKeyMissing)
433 throw new ArgumentException ("key " + keyName);
434 RegTrace (" -DeleteSubKey");
438 if (child.SubKeyCount > 0)
439 throw new InvalidOperationException ("key " + keyName + " has sub keys");
443 int result = RegistryApi.RegDeleteKey (Handle, keyName);
444 if (result == Win32ResultCode.FileNotFound)
446 if (shouldThrowWhenKeyMissing)
447 throw new ArgumentException ("key " + keyName);
448 RegTrace (" -DeleteSubKey");
452 if (result != Win32ResultCode.Success)
454 RegTrace ("Win32Api::RegDeleteKey: result='{0}' key name='{1}'",
455 result, CombineName (keyName));
456 GenerateException (result);
459 RegTrace (" -DeleteSubKey");
464 /// Delete a sub tree (node, and values alike).
466 public void DeleteSubKeyTree(string keyName)
468 // Note: this is done by deleting sub-nodes recursively.
469 // The preformance is not very good. There may be a
470 // better way to implement this.
471 RegTrace (" +DeleteSubKeyTree");
472 AssertKeyStillValid ();
473 AssertKeyNameNotNull (keyName);
475 RegistryKey child = OpenSubKey (keyName, true);
477 throw new ArgumentException ("key " + keyName);
479 child.DeleteChildKeysAndValues ();
481 DeleteSubKey (keyName, false);
482 RegTrace (" -DeleteSubKeyTree");
487 /// Delete a value from the registry.
489 public void DeleteValue(string value)
491 DeleteValue (value, true);
496 /// Delete a value from the registry.
498 public void DeleteValue(string value, bool shouldThrowWhenKeyMissing)
500 RegTrace (" +DeleteValue");
501 AssertKeyStillValid ();
502 AssertKeyNameNotNull (value);
504 int result = RegistryApi.RegDeleteValue (Handle, value);
506 if (result == Win32ResultCode.FileNotFound)
508 if (shouldThrowWhenKeyMissing)
509 throw new ArgumentException ("value " + value);
510 RegTrace (" -DeleteValue");
514 if (result != Win32ResultCode.Success)
516 RegTrace ("Win32Api::RegDeleteValue: result='{0}' value name='{1}'",
517 result, CombineName (value));
518 GenerateException (result);
521 RegTrace (" -DeleteValue");
526 /// Get the names of the sub keys.
528 public string[] GetSubKeyNames()
530 RegTrace (" +GetSubKeyNames");
531 AssertKeyStillValid ();
533 byte[] buffer = new byte [BufferMaxLength];
534 int bufferCapacity = BufferMaxLength;
535 ArrayList keys = new ArrayList ();
537 for (int index = 0; true; index ++)
539 int result = RegistryApi.RegEnumKey (Handle, index, buffer, bufferCapacity);
541 if (result == Win32ResultCode.Success)
543 keys.Add (DecodeString (buffer));
547 if (result == Win32ResultCode.NoMoreEntries)
550 // should not be here!
551 RegTrace ("Win32Api::RegEnumKey: result='{0}' value name='{1}'",
552 result, CombineName (Name));
553 GenerateException (result);
556 RegTrace (" -GetSubKeyNames");
557 return (string []) keys.ToArray (typeof(String));
562 /// Get the names of values contained in this key.
564 public string[] GetValueNames()
566 RegTrace (" +GetValueNames");
567 AssertKeyStillValid ();
569 ArrayList values = new ArrayList ();
571 for (int index = 0; true; index ++)
573 StringBuilder buffer = new StringBuilder (BufferMaxLength);
574 int bufferCapacity = buffer.Capacity;
577 int result = RegistryApi.RegEnumValue (Handle, index, buffer, ref bufferCapacity,
578 IntPtr.Zero, ref type, IntPtr.Zero, IntPtr.Zero);
580 if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData)
582 values.Add (buffer.ToString ());
586 if (result == Win32ResultCode.NoMoreEntries)
589 // should not be here!
590 RegTrace ("RegistryApi.RegEnumValue: result code='{0}' name='{1}'",
591 result, CombineName (Name));
592 GenerateException (result);
595 RegTrace (" -GetValueNames");
596 return (string []) values.ToArray (typeof(String));
601 public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey,string machineName)
603 throw new NotImplementedException ();
608 /// Build a string representation of the registry key.
609 /// Conatins the fully qualified key name, and the Hex
610 /// representation of the registry key handle.
612 public override string ToString()
614 return String.Format ("{0} [0x{1:X}]", Name, Handle.ToInt32 ());
617 #endregion // PublicAPI
621 /// validate that the registry key handle is still usable.
623 private void AssertKeyStillValid ()
625 if (Handle == IntPtr.Zero)
626 throw new ObjectDisposedException ("Microsoft.Win32.RegistryKey");
631 /// validate that the registry key handle is still usable, and
632 /// that the 'subKeyName' is not null.
634 private void AssertKeyNameNotNull (string subKeyName)
636 if (subKeyName == null)
637 throw new ArgumentNullException ();
642 /// Utility method to delelte a key's sub keys and values.
643 /// This method removes a level of indirection when deleting
646 private void DeleteChildKeysAndValues ()
648 RegTrace (" +DeleteChildKeysAndValues");
651 RegTrace (" -DeleteChildKeysAndValues");
655 string[] subKeys = GetSubKeyNames ();
656 foreach (string subKey in subKeys)
658 RegistryKey sub = OpenSubKey (subKey, true);
659 sub.DeleteChildKeysAndValues ();
661 DeleteSubKey (subKey, false);
664 string[] values = GetValueNames ();
665 foreach (string value in values)
667 DeleteValue (value, false);
670 RegTrace (" -DeleteChildKeysAndValues");
675 /// Acctually read a registry value. Requires knoledge of the
676 /// value's type and size.
678 private object GetValueImpl (string name, bool returnDefaultValue, object defaultValue)
680 RegTrace (" +GetValueImpl");
681 AssertKeyStillValid ();
687 int result = RegistryApi.RegQueryValueEx (Handle, name, IntPtr.Zero,
688 ref type, IntPtr.Zero, ref size);
690 if (returnDefaultValue && result == Win32ResultCode.FileNotFound)
692 RegTrace (" -GetValueImpl");
696 if (result != Win32ResultCode.MoreData && result != Win32ResultCode.Success )
698 RegTrace ("Win32Api::RegQueryValueEx: result='{0}' name='{1}' type='{2}' size='{3}'",
699 result, name, type, size);
700 GenerateException (result);
703 if (type == RegistryApi.RegStringType || type == RegistryApi.RegEnvironmentString)
706 result = GetBinaryValue (name, type, out data, size);
707 obj = DecodeString (data);
709 else if (type == RegistryApi.RegDwordType)
712 result = RegistryApi.RegQueryValueEx (Handle, name, IntPtr.Zero,
713 ref type, ref data, ref size);
716 else if (type == RegistryApi.RegBinaryType)
719 result = GetBinaryValue (name, type, out data, size);
722 else if (type == RegistryApi.RegStringArrayType)
726 result = GetBinaryValue (name, type, out data, size);
728 if (result == Win32ResultCode.Success)
729 obj = DecodeString (data).Split (NullChar);
733 // should never get here
734 throw new SystemException ();
737 // check result codes again:
738 if (result != Win32ResultCode.Success)
740 RegTrace ("Win32Api::RegQueryValueEx: result='{0}' name='{1}'",
742 GenerateException (result);
745 RegTrace (" -ReadValueImpl");
751 /// Get a binary value.
753 private int GetBinaryValue (string name, int type, out byte[] data, int size)
755 byte[] internalData = new byte [size];
756 int result = RegistryApi.RegQueryValueEx (Handle, name,
757 IntPtr.Zero, ref type, internalData, ref size);
764 /// decode a byte array as a string, and strip trailing nulls
766 private string DecodeString (byte[] data)
768 string stringRep = Decoder.GetString (data);
769 return stringRep.TrimEnd (NullChar);
774 /// utility: Combine the sub key name to the current name to produce a
775 /// fully qualified sub key name.
777 private string CombineName (string localName)
779 return String.Format ("{0}\\{1}", Name, localName);
784 /// convert a win32 error code into an appropriate exception.
786 private void GenerateException (int errorCode)
789 case Win32ResultCode.FileNotFound:
790 case Win32ResultCode.InvalidParameter:
791 throw new ArgumentException ();
793 case Win32ResultCode.AccessDenied:
794 throw new SecurityException ();
797 // unidentified system exception
798 throw new SystemException ();
804 /// dump trace messages if this code was compiled with tracing enabled.
806 [Conditional("TRACE")]
807 private static void RegTrace (string message, params object[] args)
809 message = "REG " + message;
811 message = String.Format (message, args);
813 Trace.WriteLine (message);
814 //Console.WriteLine (message);
817 private static void RegTrace (string message, params object[] args)