///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Tools.WindowsInstallerXml.Serialize;
using Registry=Microsoft.Win32.Registry;
using RegistryKey=Microsoft.Win32.RegistryKey;
namespace JetBrains.Build.Omea.Resolved.Infra
{
///
/// Harvest WiX authoring from the registry.
///
public sealed class WixRegistryHarvester : IDisposable
{
#region Data
private readonly string remappedPath;
#endregion
#region Init
///
/// Instantiate a new RegistryHarvester.
///
/// Set to true to remap the entire registry to a private location for this process.
public WixRegistryHarvester(bool remap)
{
// create a path in the registry for redirected keys which is process-specific
if(remap)
{
remappedPath = String.Concat(@"SOFTWARE\WiX\heat\", Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
// remove the previous remapped key if it exists
RemoveRemappedKey();
// remap the registry roots supported by MSI (the order in which the roots are remapped is important)
RemapRegistryKey(NativeMethods.HkeyClassesRoot, String.Concat(remappedPath, @"\\HKEY_CLASSES_ROOT"));
RemapRegistryKey(NativeMethods.HkeyCurrentUser, String.Concat(remappedPath, @"\\HKEY_CURRENT_USER"));
RemapRegistryKey(NativeMethods.HkeyUsers, String.Concat(remappedPath, @"\\HKEY_USERS"));
RemapRegistryKey(NativeMethods.HkeyLocalMachine, String.Concat(remappedPath, @"\\HKEY_LOCAL_MACHINE"));
}
}
#endregion
#region Operations
///
/// Harvest all registry roots supported by Windows Installer.
///
/// The registry keys and values in the registry.
public static RegistryValue[] HarvestRegistry()
{
var registryValues = new ArrayList();
HarvestRegistryKey(Registry.ClassesRoot, registryValues);
HarvestRegistryKey(Registry.CurrentUser, registryValues);
HarvestRegistryKey(Registry.LocalMachine, registryValues);
HarvestRegistryKey(Registry.Users, registryValues);
return (RegistryValue[])registryValues.ToArray(typeof(RegistryValue));
}
///
/// Close the RegistryHarvester and remove any remapped registry keys.
///
public void Close()
{
NativeMethods.OverrideRegistryKey(NativeMethods.HkeyClassesRoot, IntPtr.Zero);
NativeMethods.OverrideRegistryKey(NativeMethods.HkeyCurrentUser, IntPtr.Zero);
NativeMethods.OverrideRegistryKey(NativeMethods.HkeyLocalMachine, IntPtr.Zero);
NativeMethods.OverrideRegistryKey(NativeMethods.HkeyUsers, IntPtr.Zero);
RemoveRemappedKey();
}
#endregion
#region Implementation
///
/// Gets the parts of a registry key's path.
///
/// The registry key path.
/// The root and key parts of the registry key path.
private static string[] GetPathParts(string path)
{
return path.Split(@"\".ToCharArray(), 2);
}
///
/// Harvest a registry key.
///
/// The registry key to harvest.
/// The collected registry values.
private static void HarvestRegistryKey(RegistryKey registryKey, ArrayList registryValues)
{
// harvest the sub-keys
foreach(string subKeyName in registryKey.GetSubKeyNames())
{
using(RegistryKey subKey = registryKey.OpenSubKey(subKeyName))
HarvestRegistryKey(subKey, registryValues);
}
string[] parts = GetPathParts(registryKey.Name);
RegistryRootType root;
switch(parts[0])
{
case "HKEY_CLASSES_ROOT":
root = RegistryRootType.HKCR;
break;
case "HKEY_CURRENT_USER":
root = RegistryRootType.HKCU;
break;
case "HKEY_LOCAL_MACHINE":
root = RegistryRootType.HKLM;
break;
case "HKEY_USERS":
root = RegistryRootType.HKU;
break;
default:
throw new InvalidOperationException(string.Format("Unexpected root."));
}
// harvest the values
foreach(string valueName in registryKey.GetValueNames())
{
var registryValue = new RegistryValue();
registryValue.Action = RegistryValue.ActionType.write;
registryValue.Root = root;
if(1 < parts.Length)
registryValue.Key = parts[1];
if(null != valueName && 0 < valueName.Length)
registryValue.Name = valueName;
object value = registryKey.GetValue(valueName);
if(value == null)
throw new InvalidOperationException(string.Format("Value is Null."));
if(value is byte[]) // binary
{
var hexadecimalValue = new StringBuilder();
// convert the byte array to hexadecimal
foreach(byte byteValue in (byte[])value)
hexadecimalValue.Append(byteValue.ToString("X2", CultureInfo.InvariantCulture.NumberFormat));
registryValue.Type = RegistryValue.TypeType.binary;
registryValue.Value = hexadecimalValue.ToString();
}
else if(value is int) // integer
{
registryValue.Type = RegistryValue.TypeType.integer;
registryValue.Value = ((int)value).ToString(CultureInfo.InvariantCulture);
}
else if(value is string[]) // multi-string
{
registryValue.Type = RegistryValue.TypeType.multiString;
foreach(string multiStringValueContent in (string[])value)
{
var multiStringValue = new MultiStringValue();
multiStringValue.Content = multiStringValueContent;
registryValue.AddChild(multiStringValue);
}
}
else if(value is string) // string, expandable (there is no way to differentiate a string and expandable value in .NET 1.1)
{
registryValue.Type = RegistryValue.TypeType.@string;
registryValue.Value = (string)value;
}
else
throw new InvalidOperationException(string.Format("Value is {0}.", value.GetType().AssemblyQualifiedName));
registryValues.Add(registryValue);
}
}
///
/// Remap a registry key to an alternative location.
///
/// The registry key to remap.
/// The path to remap the registry key to under HKLM.
private static void RemapRegistryKey(UIntPtr registryKey, string remappedPath)
{
IntPtr remappedKey = IntPtr.Zero;
try
{
remappedKey = NativeMethods.OpenRegistryKey(NativeMethods.HkeyCurrentUser, remappedPath);
NativeMethods.OverrideRegistryKey(registryKey, remappedKey);
}
finally
{
if(IntPtr.Zero != remappedKey)
NativeMethods.CloseRegistryKey(remappedKey);
}
}
///
/// Remove the remapped registry key.
///
private void RemoveRemappedKey()
{
try
{
Registry.LocalMachine.DeleteSubKeyTree(remappedPath);
}
catch(ArgumentException)
{
// ignore the error where the key does not exist
}
}
#endregion
#region IDisposable Members
///
/// Dispose the RegistryHarvester.
///
public void Dispose()
{
Close();
}
#endregion
#region NativeMethods Type
///
/// The native methods for re-mapping registry keys.
///
private sealed class NativeMethods
{
#region Data
private const uint GenericAll = 0x10000000;
private const uint GenericExecute = 0x20000000;
private const uint GenericRead = 0x80000000;
private const uint GenericWrite = 0x40000000;
private const uint StandardRightsAll = 0x001F0000;
internal static readonly UIntPtr HkeyClassesRoot = (UIntPtr)0x80000000;
internal static readonly UIntPtr HkeyCurrentUser = (UIntPtr)0x80000001;
internal static readonly UIntPtr HkeyLocalMachine = (UIntPtr)0x80000002;
internal static readonly UIntPtr HkeyUsers = (UIntPtr)0x80000003;
#endregion
#region Implementation
///
/// Closes a previously open registry key.
///
/// Handle to key to close.
internal static void CloseRegistryKey(IntPtr key)
{
if(0 != RegCloseKey(key))
throw new Exception();
}
///
/// Opens a registry key.
///
/// Base key to open.
/// Path to subkey to open.
/// Handle to new key.
internal static IntPtr OpenRegistryKey(UIntPtr key, string path)
{
IntPtr newKey;
uint disposition;
uint sam = StandardRightsAll | GenericRead | GenericWrite | GenericExecute | GenericAll;
if(0 != RegCreateKeyEx(key, path, 0, null, 0, sam, 0, out newKey, out disposition))
throw new Exception();
return newKey;
}
///
/// Override a registry key.
///
/// Handle of the key to override.
/// Handle to override key.
internal static void OverrideRegistryKey(UIntPtr key, IntPtr newKey)
{
if(0 != RegOverridePredefKey(key, newKey))
throw new Exception();
}
///
/// Interop to RegCloseKey.
///
/// Handle to key to close.
/// 0 if success.
[DllImport("advapi32.dll", EntryPoint = "RegCloseKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern int RegCloseKey(IntPtr key);
///
/// Interop to RegCreateKeyW.
///
/// Handle to base key.
/// Subkey to create.
/// Always 0
/// Just pass null.
/// Just pass 0.
/// Rights to registry key.
/// Just pass null.
/// Opened key.
/// Whether key was opened or created.
/// Handle to registry key.
[DllImport("advapi32.dll", EntryPoint = "RegCreateKeyExW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern int RegCreateKeyEx(UIntPtr key, string subkey, uint reserved, string className, uint options, uint desiredSam, uint securityAttributes, out IntPtr openedKey, out uint disposition);
///
/// Interop to RegOverridePredefKey.
///
/// Handle to key to override.
/// Handle to override key.
/// 0 if success.
[DllImport("advapi32.dll", EntryPoint = "RegOverridePredefKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern int RegOverridePredefKey(UIntPtr key, IntPtr newKey);
#endregion
}
#endregion
}
}