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
)