Pete Hinchley: Create a New Registry Hive using PowerShell

This post assumes you know something about the Windows registry, and in particular, registry hives. If not, I recommend you take up some background reading.

Earlier this week I found myself thinking about registry hives, and in particular, about creating a new hive using PowerShell. A quick search of the interwebs unveiled a rather novel approach from Sergey Babkin. The technique uses bcdedit to create a small hive that is then "cleansed" to yield a new empty hive. It's definitely an interesting approach, but after digging further, I discovered that it was possible to explicitly save an existing registry key as a hive using the RegSaveKey function. I couldn't find any PowerShell-based examples of this technique, probably because calling Win32 functions from PowerShell can be a little tedious, so I decided to write my own script.

Here is a summary of the approach:

  1. Create a new registry key. This key will become the root of the new hive.
  2. Add any sub-keys and values required in the new hive.
  3. Enable the SeBackupPrivilege. This privilege is required to call the RegSaveKey function.
  4. Call RegSaveKey, providing the path to the registry key that will become the root of the new hive, and the path to where the hive is to be saved.
  5. Delete the temporary registry key used for creating the hive.
  6. Remove the SeBackupPrivilege.

I have specifically separated out the code for enabling/disabling privileges. This is a generic .NET class that might prove useful in other PowerShell projects (in fact, I have used the class in a previous post to enable the SeTimeZonePrivilege).

The script creates a temporary registry key of HKLM\Software\Hive, adds a value under the key named Foo (set to Bar), and then saves the key as a new hive at C:\Temp\Hive.dat.

Add-Type -TypeDefinition @'
using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Privileges {
  public class TokenPrivileges {
    [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
    public static extern int OpenProcessToken(int ProcessHandle, int DesiredAccess, ref int tokenhandle);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern int GetCurrentProcess();

    [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
    public static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
    public static extern int AdjustTokenPrivileges(int tokenhandle, int disableprivs, [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGE Newstate, int bufferlength, int PreivousState, int Returnlength);

    public const int TOKEN_ASSIGN_PRIMARY = 0x00000001;
    public const int TOKEN_DUPLICATE = 0x00000002;
    public const int TOKEN_IMPERSONATE = 0x00000004;
    public const int TOKEN_QUERY = 0x00000008;
    public const int TOKEN_QUERY_SOURCE = 0x00000010;
    public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    public const int TOKEN_ADJUST_GROUPS = 0x00000040;
    public const int TOKEN_ADJUST_DEFAULT = 0x00000080;

    public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
    public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
    public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
    public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;

    public static void EnablePrivilege(string privilege) {
      var token = 0;

      var TP = new TOKEN_PRIVILEGE();
      var LD = new LUID();

      OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token);
      LookupPrivilegeValue(null, privilege, ref LD);
      TP.PrivilegeCount = 1;

      var luidAndAtt = new LUID_AND_ATTRIBUTES {Attributes = SE_PRIVILEGE_ENABLED, Luid = LD};
      TP.Privilege = luidAndAtt;

      AdjustTokenPrivileges(token, 0, ref TP, 1024, 0, 0);
    }

    public static void DisablePrivilege(string privilege) {
      var token = 0;

      var TP = new TOKEN_PRIVILEGE();
      var LD = new LUID();

      OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token);
      LookupPrivilegeValue(null, privilege, ref LD);
      TP.PrivilegeCount = 1;

      var luidAndAtt = new LUID_AND_ATTRIBUTES {Luid = LD};
      TP.Privilege = luidAndAtt;

      AdjustTokenPrivileges(token, 0, ref TP, 1024, 0, 0);
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct LUID {
      internal uint LowPart;
      internal uint HighPart;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct LUID_AND_ATTRIBUTES {
      internal LUID Luid;
      internal uint Attributes;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct TOKEN_PRIVILEGE {
      internal uint PrivilegeCount;
      internal LUID_AND_ATTRIBUTES Privilege;
    }
  }
}
'@

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Registry {
  public class Utils {
    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int RegCloseKey(UIntPtr hKey);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult);

    [DllImport("advapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
    private static extern uint RegSaveKey(UIntPtr hKey, string lpFile, IntPtr lpSecurityAttributes);

    private static int KEY_READ = 131097;
    private static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);

    public static uint SaveKey(string key, string path) {
      UIntPtr hKey = UIntPtr.Zero;
      RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, out hKey);
      uint result = RegSaveKey(hKey, path, IntPtr.Zero);
      RegCloseKey(hKey);
      return result;
    }
  }
}
'@

# Enable the privileges required to save the hive.
[Privileges.TokenPrivileges]::EnablePrivilege('SeBackupPrivilege') | Out-Null

# Build the hive.
new-item -path HKLM:\Software -name Hive | Out-Null
new-itemproperty -path HKLM:\Software\Hive -name Foo -value Bar -type string | Out-Null

# Export the hive.
[Registry.Utils]::SaveKey("SOFTWARE\\HIVE", "C:\\TEMP\\HIVE.DAT")

# Clean up.
remove-item -path HKLM:\Software\Hive -force

# Disable the privileges required to save the hive.
[Privileges.TokenPrivileges]::DisablePrivilege('SeBackupPrivilege') | Out-Null

As to what you do with the new hive, well that's entirely up to you. If you want the hive to always be available, you will need to load it into the registry (possibly using the RegLoadKey function) each time the system is started (as loading a hive isn't persistent).

As a side note, you can see the list of loaded hives by reviewing the values under HKLM\SYSTEM\CurrentControlSet\Control\hivelist.