Skip to content

Instantly share code, notes, and snippets.

@jborean93
Created April 29, 2021 03:34
Show Gist options
  • Save jborean93/7dc4a3546f1d65159fb9cb5c1eb82446 to your computer and use it in GitHub Desktop.
Save jborean93/7dc4a3546f1d65159fb9cb5c1eb82446 to your computer and use it in GitHub Desktop.
Gets detailed information about a registry key
# Copyright: (c) 2021, Jordan Borean (@jborean93) <[email protected]>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
Function Get-RegKeyInfo {
<#
.SYNOPSIS
Gets details about a registry key.
.DESCRIPTION
Gets very low level details about a registry key.
.PARAMETER Path
The path to the registry key to get the details for. This should be a string with the hive and key path split by
':', e.g. HKLM:\Software\Microsoft, HKEY_CURRENT_USER:\Console, etc. The Hive can be in the short form like HKLM or
the long form HKEY_LOCAL_MACHINE.
.EXAMPLE
Get-RegKeyInfo -Path HKLM:\SYSTEM\CurrentControlSet
#>
[CmdletBinding()]
param (
[Parameter(
Mandatory = $true,
Position = 0,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
[String[]]
$Path
)
begin {
Add-Type -TypeDefinition @'
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace Registry
{
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential)]
public struct KEY_BASIC_INFORMATION
{
public Int64 LastWriteTime;
public UInt32 TitleIndex;
public Int32 NameLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public char[] Name;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_FLAGS_INFORMATION
{
// This struct isn't really documented and most of the snippets online just show the UserFlags field. For
// whatever reason it seems to be 12 bytes in size with the flags in the 2nd integer value. The others I
// have no idea what they are for.
public UInt32 Reserved1;
public KeyFlags UserFlags;
public UInt32 Reserved2;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_FULL_INFORMATION
{
public Int64 LastWriteTime;
public UInt32 TitleIndex;
public Int32 ClassOffset;
public Int32 ClassLength;
public Int32 SubKeys;
public Int32 MaxNameLen;
public Int32 MaxClassLen;
public Int32 Values;
public Int32 MaxValueNameLen;
public Int32 MaxValueDataLen;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public char[] Class;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_HANDLE_TAGS_INFORMATION
{
public UInt32 HandleTags;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_LAYER_INFORMATION
{
public UInt32 IsTombstone;
public UInt32 IsSupersedeLocal;
public UInt32 IsSupersedeTree;
public UInt32 ClassIsInherited;
public UInt32 Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_TRUST_INFORMATION
{
public UInt32 TrustedKey;
public UInt32 Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_VIRTUALIZATION_INFORMATION
{
public UInt32 VirtualizationCandidate;
public UInt32 VirtualizationEnabled;
public UInt32 VirtualTarget;
public UInt32 VirtualStore;
public UInt32 VirtualSource;
public UInt32 Reserved;
}
public enum KeyInformationClass : uint
{
Basic = 0,
Node = 1,
Full = 2,
Name = 3,
Cached = 4,
Flags = 5,
Virtualization = 6,
HandleTags = 7,
Trust = 8,
Layer = 9,
}
}
internal class NativeMethods
{
[DllImport("NtDll.dll")]
public static extern UInt32 NtQueryKey(
SafeHandle KeyHandle,
NativeHelpers.KeyInformationClass KeyInformationClass,
IntPtr KeyInformation,
Int32 Length,
out Int32 ResultLength
);
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode)]
public static extern Int32 RegOpenKeyExW(
SafeHandle hKey,
string lpSubKey,
KeyOptions ulOptions,
KeyAccessRights samDesired,
out SafeRegistryHandle phkResult
);
[DllImport("NtDll.dll")]
public static extern Int32 RtlNtStatusToDosError(
UInt32 Status
);
}
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeMemoryBuffer() : base(true) { }
public SafeMemoryBuffer(int cb) : base(true)
{
base.SetHandle(Marshal.AllocHGlobal(cb));
}
public SafeMemoryBuffer(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
[Flags]
public enum KeyAccessRights : uint
{
QueryValue = 0x00000001,
SetValue = 0x00000002,
CreateSubKey = 0x00000004,
EnumerateSubKeys = 0x00000008,
Notify = 0x00000010,
CreateLink = 0x00000020,
Wow6464Key = 0x00000100,
Wow6432Key = 0x00000200,
Delete = 0x00010000,
ReadControl = 0x00020000,
WriteDAC = 0x00040000,
WriteOwner = 0x00080000,
StandardRightsRequired = Delete | ReadControl | WriteDAC | WriteOwner,
AccessSystemSecurity = 0x01000000,
Read = ReadControl | QueryValue | EnumerateSubKeys | Notify,
Execute = Read,
Write = ReadControl | SetValue | CreateSubKey,
AllAccess = StandardRightsRequired | 0x3F
}
[Flags]
public enum KeyFlags : uint
{
None = 0x00000000,
Volatile = 0x00000001,
Symlink = 0x00000002,
}
[Flags]
public enum KeyOptions : uint
{
None = 0x00000000,
Volatile = 0x00000001,
CreateLink = 0x00000002,
BackupRestore = 0x00000004,
OpenLink = 0x00000008,
}
public class KeyInformation
{
public DateTime LastWriteTime { get; internal set; }
public UInt32 TitleIndex { get; internal set; }
public string Name { get; internal set; }
public string Class { get; internal set; }
public Int32 SubKeys { get; internal set; }
public Int32 ValueCount { get; internal set ; }
public KeyFlags Flags { get; internal set; }
public bool VirtualizationCandidate { get; internal set; }
public bool VirtualizationEnabled { get; internal set; }
public bool VirtualTarget { get; internal set; }
public bool VirtualStore { get; internal set; }
public bool VirtualSource { get; internal set; }
public UInt32 HandleTags { get; internal set; }
public bool TrustedKey { get; internal set; }
/* Parameter is invalid
public bool IsTombstone { get; internal set; }
public bool IsSupersedeLocal { get; internal set; }
public bool IsSupersedeTree { get; internal set; }
public bool ClassIsInherited { get; internal set; }
*/
}
public class Key
{
public static SafeRegistryHandle OpenKey(SafeHandle key, string subKey, KeyOptions options,
KeyAccessRights access)
{
SafeRegistryHandle handle;
Int32 res = NativeMethods.RegOpenKeyExW(key, subKey, options, access, out handle);
if (res != 0)
throw new Win32Exception(res);
return handle;
}
public static KeyInformation QueryInformation(SafeHandle handle)
{
KeyInformation info = new KeyInformation();
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Basic))
{
var obj = (NativeHelpers.KEY_BASIC_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_BASIC_INFORMATION));
IntPtr nameBuffer = IntPtr.Add(buffer.DangerousGetHandle(), 16);
byte[] nameBytes = new byte[obj.NameLength];
Marshal.Copy(nameBuffer, nameBytes, 0, nameBytes.Length);
info.LastWriteTime = DateTime.FromFileTimeUtc(obj.LastWriteTime);
info.TitleIndex = obj.TitleIndex;
info.Name = Encoding.Unicode.GetString(nameBytes, 0, nameBytes.Length);
}
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Full))
{
var obj = (NativeHelpers.KEY_FULL_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_FULL_INFORMATION));
IntPtr classBuffer = IntPtr.Add(buffer.DangerousGetHandle(), obj.ClassOffset);
byte[] classBytes = new byte[obj.ClassLength];
Marshal.Copy(classBuffer, classBytes, 0, classBytes.Length);
info.Class = Encoding.Unicode.GetString(classBytes, 0, classBytes.Length);
info.SubKeys = obj.SubKeys;
info.ValueCount = obj.Values;
}
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Flags))
{
var obj = (NativeHelpers.KEY_FLAGS_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_FLAGS_INFORMATION));
info.Flags = obj.UserFlags;
}
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Virtualization))
{
var obj = (NativeHelpers.KEY_VIRTUALIZATION_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_VIRTUALIZATION_INFORMATION));
info.VirtualizationCandidate = obj.VirtualizationCandidate == 1;
info.VirtualizationEnabled = obj.VirtualizationEnabled == 1;
info.VirtualTarget = obj.VirtualTarget == 1;
info.VirtualStore = obj.VirtualStore == 1;
info.VirtualSource = obj.VirtualSource == 1;
}
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.HandleTags))
{
var obj = (NativeHelpers.KEY_HANDLE_TAGS_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_HANDLE_TAGS_INFORMATION));
info.HandleTags = obj.HandleTags;
}
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Trust))
{
var obj = (NativeHelpers.KEY_TRUST_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_TRUST_INFORMATION));
info.TrustedKey = obj.TrustedKey == 1;
}
/* Parameter is invalid
using (var buffer = NtQueryKey(handle, NativeHelpers.KeyInformationClass.Layer))
{
var obj = (NativeHelpers.KEY_LAYER_INFORMATION)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.KEY_LAYER_INFORMATION));
info.IsTombstone = obj.IsTombstone == 1;
info.IsSupersedeLocal = obj.IsSupersedeLocal == 1;
info.IsSupersedeTree = obj.IsSupersedeTree == 1;
info.ClassIsInherited = obj.ClassIsInherited == 1;
}
*/
return info;
}
private static SafeMemoryBuffer NtQueryKey(SafeHandle handle, NativeHelpers.KeyInformationClass infoClass)
{
int resultLength;
UInt32 res = NativeMethods.NtQueryKey(handle, infoClass, IntPtr.Zero, 0, out resultLength);
// STATUS_BUFFER_OVERFLOW or STATUS_BUFFER_TOO_SMALL
if (!(res == 0x80000005 || res == 0xC0000023))
throw new Win32Exception(NativeMethods.RtlNtStatusToDosError(res));
SafeMemoryBuffer buffer = new SafeMemoryBuffer(resultLength);
try
{
res = NativeMethods.NtQueryKey(handle, infoClass, buffer.DangerousGetHandle(), resultLength,
out resultLength);
if (res != 0)
throw new Win32Exception(NativeMethods.RtlNtStatusToDosError(res));
}
catch
{
buffer.Dispose();
throw;
}
return buffer;
}
}
}
'@
}
process {
$resolvedPaths = $Path
foreach ($regPath in $resolvedPaths) {
if (-not $regPath.Contains(':')) {
$exp = [ArgumentException]"Registry path must contain hive and keys split by :"
$PSCmdlet.WriteError([Management.Automation.ErrorRecord]::new(
$exp, $exp.GetType().FullName, 'InvalidArgument', $regPath
))
continue
}
$hive, $subKey = $regPath -split ':', 2
$hiveId = switch ($hive) {
{ $_ -in @('HKCR', 'HKEY_CLASES_ROOT') } { 0x80000000 }
{ $_ -in @('HKCU', 'HKEY_CURRENT_USER') } { 0x80000001 }
{ $_ -in @('HKLM', 'HKEY_LOCAL_MACHINE') } { 0x80000002 }
{ $_ -in @('HKU', 'HKEY_USERS') } { 0x80000003 }
{ $_ -in @('HKPD', 'HKEY_PERFORMANCE_DATA') } { 0x80000004 }
{ $_ -in @('HKPT', 'HKEY_PERFORMANCE_TEXT') } { 0x80000050 }
{ $_ -in @('HKPN', 'HKEY_PERFORMANCE_NLSTEXT') } { 0x80000060 }
{ $_ -in @('HKCC', 'HKEY_CURRENT_CONFIG') } { 0x80000005 }
{ $_ -in @('HKDD', 'HKEY_DYN_DATA') } { 0x80000006 }
{ $_ -in @('HKCULS', 'HKEY_CURRENT_USER_LOCAL_SETTINGS') } { 0x80000007 }
}
if (-not $hiveId) {
$exp = [ArgumentException]"Registry hive path is invalid"
$PSCmdlet.WriteError([Management.Automation.ErrorRecord]::new(
$exp, $exp.GetType().FullName, 'InvalidArgument', $regPath
))
continue
}
if ($subKey.StartsWith('\')) {
$subKey = $subKey.Substring(1)
}
$hive = [Microsoft.Win32.SafeHandles.SafeRegistryHandle]::new([IntPtr]::new($hiveId), $false)
$key = $null
try {
# We can't use the PowerShell provider because it doesn't set OpenLink which means we couldn't detect
# if the path was a link as the handle would be for the target.
$key = [Registry.Key]::OpenKey($hive, $subKey, 'OpenLink', 'QueryValue')
[Registry.Key]::QueryInformation($key)
}
catch {
$PSCmdlet.WriteError([Management.Automation.ErrorRecord]::new(
$_.Exception, $_.Exception.GetType().FullName, 'NotSpecified', $regPath
))
continue
}
finally {
$key.Dispose()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment