//
// RegistryKey.cs: a single node in the Windows registry
//
// Author:
// Miguel de Icaza (miguel@ximian.com)
// Erik LeBel (eriklebel@yahoo.ca)
//
//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Microsoft.Win32
{
///
/// Wrapper class for Windows Registry Entry.
///
public sealed class RegistryKey : MarshalByRefObject, IDisposable
{
const char NullChar = '\0';
// Arbitrary max size for key/values names that can be fetched.
// .NET framework SDK docs say that the max name length that can
// be used is 255 characters, we'll allow for a bit more.
const int BufferMaxLength = 1024;
// FIXME must be a way to determin this dynamically?
const int Int32ByteSize = 4;
// FIXME this is hard coded on Mono, can it be determined dynamically?
readonly int NativeBytesPerCharacter = Marshal.SystemDefaultCharSize;
// FIXME this should be determined dynamically.
// It will be used to decode some return strings
// for which embeded '\0' must be preserved.
readonly Encoding Decoder = Encoding.Unicode;
IntPtr hkey; // the reg key handle
string qname; // the fully qualified registry key name
bool isRoot; // is the an instance of a root key?
IRegistryApi reg_api;
///
/// Construct an instance of a root registry key entry.
///
internal RegistryKey (RegistryHive hiveId, string keyName)
{
hkey = new IntPtr ((int)hiveId);
qname = keyName;
isRoot = true;
InitRegistryApi ();
}
///
/// Construct an instance of a registry key entry.
///
internal RegistryKey (IntPtr hkey, string keyName)
{
this.hkey = hkey;
qname = keyName;
isRoot = false;
InitRegistryApi ();
}
internal void InitRegistryApi ()
{
if (Path.DirectorySeparatorChar == '\\')
reg_api = new Win32RegistryApi ();
}
private IRegistryApi RegistryApi {
get {
if (reg_api == null)
throw new NotImplementedException ("The registry is" +
" only available on Windows.");
return reg_api;
}
}
///
/// Fetch the inetrnal registry key.
///
private IntPtr Handle {
get { return hkey; }
}
#region PublicAPI
///
/// Dispose of registry key object. Close the
/// key if it's still open.
///
void IDisposable.Dispose ()
{
Close ();
GC.SuppressFinalize (this);
}
///
/// Final cleanup of registry key object. Close the
/// key if it's still open.
///
~RegistryKey ()
{
Close ();
}
///
/// Get the fully qualified registry key name.
///
public string Name {
get { return qname; }
}
///
/// Flush the current registry state to disk.
///
public void Flush()
{
RegTrace (" +Flush");
RegistryApi.RegFlushKey (Handle);
RegTrace (" -Flush");
}
///
/// Close the current registry key. This may not
/// flush the state of the registry right away.
///
public void Close()
{
if (isRoot)
return;
RegTrace (" +Close");
RegistryApi.RegCloseKey (Handle);
hkey = IntPtr.Zero;
RegTrace (" -Close");
}
///
/// get the number of sub-keys for this key
///
public int SubKeyCount {
get {
RegTrace (" +SubKeyCount");
AssertKeyStillValid ();
int index, result;
byte[] stringBuffer = new byte [BufferMaxLength];
for (index = 0; true; index ++)
{
result = RegistryApi.RegEnumKey (Handle, index,
stringBuffer, BufferMaxLength);
if (result == Win32ResultCode.Success)
continue;
if (result == Win32ResultCode.NoMoreEntries)
break;
// something is wrong!!
RegTrace ("Win32Api::ReEnumKey result='{0}' name='{1}'",
result, Name);
GenerateException (result);
}
RegTrace (" -SubKeyCount");
return index;
}
}
///
/// get the number of values for this key
///
public int ValueCount {
get {
RegTrace (" +ValueCount");
AssertKeyStillValid ();
int index, result, type, bufferCapacity;
StringBuilder buffer = new StringBuilder (BufferMaxLength);
for (index = 0; true; index ++)
{
type = 0;
bufferCapacity = buffer.Capacity;
result = RegistryApi.RegEnumValue (Handle, index,
buffer, ref bufferCapacity,
IntPtr.Zero, ref type,
IntPtr.Zero, IntPtr.Zero);
if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData)
continue;
if (result == Win32ResultCode.NoMoreEntries)
break;
// something is wrong
RegTrace ("Win32Api::RegEnumValue result='{0}' name='{1}'",
result, Name);
GenerateException (result);
}
RegTrace (" -ValueCount");
return index;
}
}
///
/// Set a registry value.
///
public void SetValue (string name, object value)
{
RegTrace (" +SetValue");
AssertKeyStillValid ();
if (value == null)
throw new ArgumentNullException ();
Type type = value.GetType ();
int result;
if (type == typeof (int))
{
int rawValue = (int)value;
result = RegistryApi.RegSetValueEx (Handle, name,
IntPtr.Zero, RegistryApi.RegDwordType,
ref rawValue, Int32ByteSize);
}
else if (type == typeof (byte[]))
{
byte[] rawValue = (byte[]) value;
result = RegistryApi.RegSetValueEx (Handle, name,
IntPtr.Zero, RegistryApi.RegBinaryType,
rawValue, rawValue.Length);
}
else if (type == typeof (string[]))
{
string[] vals = (string[]) value;
StringBuilder fullStringValue = new StringBuilder ();
foreach (string v in vals)
{
fullStringValue.Append (v);
fullStringValue.Append (NullChar);
}
fullStringValue.Append (NullChar);
byte[] rawValue = Decoder.GetBytes (fullStringValue.ToString ());
result = RegistryApi.RegSetValueEx (Handle, name,
IntPtr.Zero, RegistryApi.RegStringArrayType,
rawValue, rawValue.Length);
}
else if (type.IsArray)
{
throw new ArgumentException ("Only string and byte arrays can written as registry values");
}
else
{
string rawValue = String.Format ("{0}{1}", value, NullChar);
result = RegistryApi.RegSetValueEx (Handle, name,
IntPtr.Zero, RegistryApi.RegStringType,
rawValue, rawValue.Length * NativeBytesPerCharacter);
}
// handle the result codes
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegSetValueEx: result: {0}", result);
GenerateException (result);
}
RegTrace (" -SetValue");
}
///
/// Open the sub key specified, for read access.
///
public RegistryKey OpenSubKey (string keyName)
{
return OpenSubKey (keyName, false);
}
///
/// Open the sub key specified.
///
public RegistryKey OpenSubKey (string keyName, bool writtable)
{
RegTrace (" +OpenSubKey");
AssertKeyStillValid ();
AssertKeyNameNotNull (keyName);
int access = RegistryApi.OpenRegKeyRead;
if (writtable) access |= RegistryApi.OpenRegKeyWrite;
IntPtr subKeyHandle;
int result = RegistryApi.RegOpenKeyEx (Handle, keyName, IntPtr.Zero,
access, out subKeyHandle);
if (result == Win32ResultCode.FileNotFound)
{
RegTrace (" -OpenSubKey");
return null;
}
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegOpenKeyEx result='{0}' key name='{1}'",
result, CombineName (keyName));
GenerateException (result);
}
RegistryKey subKey = new RegistryKey (subKeyHandle, CombineName (keyName));
RegTrace (" -OpenSubKey");
return subKey;
}
///
/// Get a registry value.
///
public object GetValue (string name)
{
RegTrace (" +GetValue");
object obj = GetValueImpl (name, false, null);
RegTrace (" -GetValue");
return obj;
}
///
/// Get a registry value.
///
public object GetValue (string name, object defaultValue)
{
RegTrace (" +GetValue");
object obj = GetValueImpl (name, true, defaultValue);
RegTrace (" -GetValue");
return obj;
}
///
/// Create a sub key.
///
public RegistryKey CreateSubKey (string keyName)
{
RegTrace (" +CreateSubKey");
AssertKeyStillValid ();
AssertKeyNameNotNull (keyName);
IntPtr subKeyHandle;
int result = RegistryApi.RegCreateKey (Handle , keyName, out subKeyHandle);
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegCreateKey: result='{0}' key name='{1}'",
result, CombineName (keyName));
GenerateException (result);
}
RegistryKey subKey = new RegistryKey (subKeyHandle, CombineName (keyName));
RegTrace (" -CreateSubKey");
return subKey;
}
///
/// Delete the specified subkey.
///
public void DeleteSubKey(string subkey)
{
DeleteSubKey (subkey, true);
}
///
/// Delete the specified subkey.
///
public void DeleteSubKey(string keyName, bool shouldThrowWhenKeyMissing)
{
RegTrace (" +DeleteSubKey");
AssertKeyStillValid ();
AssertKeyNameNotNull (keyName);
RegistryKey child = OpenSubKey (keyName);
if (child == null)
{
if (shouldThrowWhenKeyMissing)
throw new ArgumentException ("key " + keyName);
RegTrace (" -DeleteSubKey");
return;
}
if (child.SubKeyCount > 0)
throw new InvalidOperationException ("key " + keyName + " has sub keys");
child.Close ();
int result = RegistryApi.RegDeleteKey (Handle, keyName);
if (result == Win32ResultCode.FileNotFound)
{
if (shouldThrowWhenKeyMissing)
throw new ArgumentException ("key " + keyName);
RegTrace (" -DeleteSubKey");
return;
}
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegDeleteKey: result='{0}' key name='{1}'",
result, CombineName (keyName));
GenerateException (result);
}
RegTrace (" -DeleteSubKey");
}
///
/// Delete a sub tree (node, and values alike).
///
public void DeleteSubKeyTree(string keyName)
{
// Note: this is done by deleting sub-nodes recursively.
// The preformance is not very good. There may be a
// better way to implement this.
RegTrace (" +DeleteSubKeyTree");
AssertKeyStillValid ();
AssertKeyNameNotNull (keyName);
RegistryKey child = OpenSubKey (keyName, true);
if (child == null)
throw new ArgumentException ("key " + keyName);
child.DeleteChildKeysAndValues ();
child.Close ();
DeleteSubKey (keyName, false);
RegTrace (" -DeleteSubKeyTree");
}
///
/// Delete a value from the registry.
///
public void DeleteValue(string value)
{
DeleteValue (value, true);
}
///
/// Delete a value from the registry.
///
public void DeleteValue(string value, bool shouldThrowWhenKeyMissing)
{
RegTrace (" +DeleteValue");
AssertKeyStillValid ();
AssertKeyNameNotNull (value);
int result = RegistryApi.RegDeleteValue (Handle, value);
if (result == Win32ResultCode.FileNotFound)
{
if (shouldThrowWhenKeyMissing)
throw new ArgumentException ("value " + value);
RegTrace (" -DeleteValue");
return;
}
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegDeleteValue: result='{0}' value name='{1}'",
result, CombineName (value));
GenerateException (result);
}
RegTrace (" -DeleteValue");
}
///
/// Get the names of the sub keys.
///
public string[] GetSubKeyNames()
{
RegTrace (" +GetSubKeyNames");
AssertKeyStillValid ();
byte[] buffer = new byte [BufferMaxLength];
int bufferCapacity = BufferMaxLength;
ArrayList keys = new ArrayList ();
for (int index = 0; true; index ++)
{
int result = RegistryApi.RegEnumKey (Handle, index, buffer, bufferCapacity);
if (result == Win32ResultCode.Success)
{
keys.Add (DecodeString (buffer));
continue;
}
if (result == Win32ResultCode.NoMoreEntries)
break;
// should not be here!
RegTrace ("Win32Api::RegEnumKey: result='{0}' value name='{1}'",
result, CombineName (Name));
GenerateException (result);
}
RegTrace (" -GetSubKeyNames");
return (string []) keys.ToArray (typeof(String));
}
///
/// Get the names of values contained in this key.
///
public string[] GetValueNames()
{
RegTrace (" +GetValueNames");
AssertKeyStillValid ();
ArrayList values = new ArrayList ();
for (int index = 0; true; index ++)
{
StringBuilder buffer = new StringBuilder (BufferMaxLength);
int bufferCapacity = buffer.Capacity;
int type = 0;
int result = RegistryApi.RegEnumValue (Handle, index, buffer, ref bufferCapacity,
IntPtr.Zero, ref type, IntPtr.Zero, IntPtr.Zero);
if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData)
{
values.Add (buffer.ToString ());
continue;
}
if (result == Win32ResultCode.NoMoreEntries)
break;
// should not be here!
RegTrace ("RegistryApi.RegEnumValue: result code='{0}' name='{1}'",
result, CombineName (Name));
GenerateException (result);
}
RegTrace (" -GetValueNames");
return (string []) values.ToArray (typeof(String));
}
[MonoTODO]
public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey,string machineName)
{
throw new NotImplementedException ();
}
///
/// Build a string representation of the registry key.
/// Conatins the fully qualified key name, and the Hex
/// representation of the registry key handle.
///
public override string ToString()
{
return String.Format ("{0} [0x{1:X}]", Name, Handle.ToInt32 ());
}
#endregion // PublicAPI
///
/// validate that the registry key handle is still usable.
///
private void AssertKeyStillValid ()
{
if (Handle == IntPtr.Zero)
throw new ObjectDisposedException ("Microsoft.Win32.RegistryKey");
}
///
/// validate that the registry key handle is still usable, and
/// that the 'subKeyName' is not null.
///
private void AssertKeyNameNotNull (string subKeyName)
{
if (subKeyName == null)
throw new ArgumentNullException ();
}
///
/// Utility method to delelte a key's sub keys and values.
/// This method removes a level of indirection when deleting
/// key node trees.
///
private void DeleteChildKeysAndValues ()
{
RegTrace (" +DeleteChildKeysAndValues");
if (isRoot)
{
RegTrace (" -DeleteChildKeysAndValues");
return;
}
string[] subKeys = GetSubKeyNames ();
foreach (string subKey in subKeys)
{
RegistryKey sub = OpenSubKey (subKey, true);
sub.DeleteChildKeysAndValues ();
sub.Close ();
DeleteSubKey (subKey, false);
}
string[] values = GetValueNames ();
foreach (string value in values)
{
DeleteValue (value, false);
}
RegTrace (" -DeleteChildKeysAndValues");
}
///
/// Acctually read a registry value. Requires knoledge of the
/// value's type and size.
///
private object GetValueImpl (string name, bool returnDefaultValue, object defaultValue)
{
RegTrace (" +GetValueImpl");
AssertKeyStillValid ();
int type = 0;
int size = 0;
object obj = null;
int result = RegistryApi.RegQueryValueEx (Handle, name, IntPtr.Zero,
ref type, IntPtr.Zero, ref size);
if (result == Win32ResultCode.FileNotFound)
{
if (returnDefaultValue) {
RegTrace (" -GetValueImpl");
return defaultValue;
}
return null;
}
if (result != Win32ResultCode.MoreData && result != Win32ResultCode.Success )
{
RegTrace ("Win32Api::RegQueryValueEx: result='{0}' name='{1}' type='{2}' size='{3}'",
result, name, type, size);
GenerateException (result);
}
if (type == RegistryApi.RegStringType || type == RegistryApi.RegEnvironmentString)
{
byte[] data;
result = GetBinaryValue (name, type, out data, size);
obj = DecodeString (data);
}
else if (type == RegistryApi.RegDwordType)
{
int data = 0;
result = RegistryApi.RegQueryValueEx (Handle, name, IntPtr.Zero,
ref type, ref data, ref size);
obj = data;
}
else if (type == RegistryApi.RegBinaryType)
{
byte[] data;
result = GetBinaryValue (name, type, out data, size);
obj = data;
}
else if (type == RegistryApi.RegStringArrayType)
{
obj = null;
byte[] data;
result = GetBinaryValue (name, type, out data, size);
if (result == Win32ResultCode.Success)
obj = DecodeString (data).Split (NullChar);
}
else
{
// should never get here
throw new SystemException ();
}
// check result codes again:
if (result != Win32ResultCode.Success)
{
RegTrace ("Win32Api::RegQueryValueEx: result='{0}' name='{1}'",
result, name);
GenerateException (result);
}
RegTrace (" -ReadValueImpl");
return obj;
}
///
/// Get a binary value.
///
private int GetBinaryValue (string name, int type, out byte[] data, int size)
{
byte[] internalData = new byte [size];
int result = RegistryApi.RegQueryValueEx (Handle, name,
IntPtr.Zero, ref type, internalData, ref size);
data = internalData;
return result;
}
///
/// decode a byte array as a string, and strip trailing nulls
///
private string DecodeString (byte[] data)
{
string stringRep = Decoder.GetString (data);
int idx = stringRep.IndexOf (NullChar);
if (idx >= 0)
stringRep = stringRep.Substring (0, idx);
return stringRep;
}
///
/// utility: Combine the sub key name to the current name to produce a
/// fully qualified sub key name.
///
private string CombineName (string localName)
{
return String.Format ("{0}\\{1}", Name, localName);
}
///
/// convert a win32 error code into an appropriate exception.
///
private void GenerateException (int errorCode)
{
switch (errorCode) {
case Win32ResultCode.FileNotFound:
case Win32ResultCode.InvalidParameter:
throw new ArgumentException ();
case Win32ResultCode.AccessDenied:
throw new SecurityException ();
default:
// unidentified system exception
throw new SystemException ();
}
}
#if (false)
///
/// dump trace messages if this code was compiled with tracing enabled.
///
[Conditional("TRACE")]
private static void RegTrace (string message, params object[] args)
{
message = "REG " + message;
if (args.Length > 0)
message = String.Format (message, args);
Trace.WriteLine (message);
//Console.WriteLine (message);
}
#endif
private static void RegTrace (string message, params object[] args)
{
}
}
}