Public/Get-MSIXVirtualRegistryKey.ps1

function Get-MSIXVirtualRegistryKey {
<#
.SYNOPSIS
    Lists the values and subkeys of a key in an MSIX registry hive file (Registry.dat / User.dat).

.DESCRIPTION
    Loads the hive file given by -HiveFilePath with RegLoadAppKey and returns one object per value
    (Type 'Value') and per subkey (Type 'Key') of -KeyPath (relative to the hive root:
    'REGISTRY\MACHINE\SOFTWARE\...' in Registry.dat, 'Software\...' in User.dat whose root is HKCU).
    Value data is decoded by its registry type:
    String/ExpandString -> string, MultiString -> string[], DWord -> UInt32, QWord -> UInt64,
    anything else -> byte[].

.EXAMPLE
    Get-MSIXVirtualRegistryKey -HiveFilePath "$pkg\Registry.dat" -KeyPath 'REGISTRY\MACHINE\SOFTWARE\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown'

.NOTES
    RegLoadAppKey works on the hive file directly (no reg load). Reading needs no administrative
    rights.
    https://www.nick-it.de
    Andreas Nick, 2024
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$HiveFilePath,

        [Parameter(Mandatory = $true)]
        [string]$KeyPath
    )

    $hKey = [IntPtr]::Zero
    $result = [Win32Apis]::RegLoadAppKey($HiveFilePath, [ref]$hKey, 0x20019, 0, 0)   # KEY_READ
    if ($result -ne 0) {
        throw "Failed to load hive '$HiveFilePath' (Win32 error $(Format-MSIXWin32Error $result))."
    }

    try {
        $subKeyHandle = [IntPtr]::Zero
        $result = [Win32Apis]::RegOpenKeyEx($hKey, $KeyPath, 0, 0x20019, [ref]$subKeyHandle)
        if ($result -ne 0) {
            throw "Failed to open key '$KeyPath' in hive '$HiveFilePath' (Win32 error $(Format-MSIXWin32Error $result))."
        }

        try {
            # --- Values ---
            # ERROR_MORE_DATA (234): retry the same index with larger buffers. lpcbData then holds the
            # required data size; value names can be up to 16383 characters.
            $nameCapacity = 1024
            $dataCapacity = 4096
            $index   = 0
            $retries = 0
            while ($true) {
                $valueName       = New-Object System.Text.StringBuilder $nameCapacity
                $valueNameLength = [uint32]$nameCapacity
                $valueData       = New-Object byte[] $dataCapacity
                $valueDataLength = [uint32]$dataCapacity
                $valueType       = [uint32]0
                $result = [Win32Apis]::RegEnumValue($subKeyHandle, [uint32]$index, $valueName, [ref]$valueNameLength,
                    [IntPtr]::Zero, [ref]$valueType, $valueData, [ref]$valueDataLength)

                if ($result -eq 234) {   # ERROR_MORE_DATA
                    if (++$retries -gt 5) {
                        throw "Value #$index of '$KeyPath' in hive '$HiveFilePath' does not fit into $dataCapacity bytes."
                    }
                    $nameCapacity = 16384
                    $dataCapacity = [Math]::Max([int]$valueDataLength, $dataCapacity * 2)
                    continue
                }
                if ($result -eq 259) { break }   # ERROR_NO_MORE_ITEMS
                if ($result -ne 0) {
                    throw "Failed to enumerate the values of '$KeyPath' in hive '$HiveFilePath' (Win32 error $(Format-MSIXWin32Error $result))."
                }

                $length = [int]$valueDataLength
                $kind   = [Microsoft.Win32.RegistryValueKind]$valueType
                switch ($kind) {
                    { $_ -in 'String', 'ExpandString' } {
                        $data = [System.Text.Encoding]::Unicode.GetString($valueData, 0, $length).TrimEnd([char]0)
                    }
                    'MultiString' {
                        $text = [System.Text.Encoding]::Unicode.GetString($valueData, 0, $length).TrimEnd([char]0)
                        if ($text) { $data = [string[]]($text -split "`0") } else { $data = [string[]]@() }
                    }
                    'DWord' {
                        if ($length -ge 4) { $data = [BitConverter]::ToUInt32($valueData, 0) } else { $data = $null }
                    }
                    'QWord' {
                        if ($length -ge 8) { $data = [BitConverter]::ToUInt64($valueData, 0) } else { $data = $null }
                    }
                    default {
                        $data = New-Object byte[] $length
                        [Array]::Copy($valueData, $data, $length)
                    }
                }

                [pscustomobject]@{
                    Type      = 'Value'
                    Name      = $valueName.ToString()
                    Data      = $data
                    ValueType = $kind
                }
                $index++
                $retries = 0
            }

            # --- Subkeys ---
            $nameCapacity = 1024
            $index   = 0
            $retries = 0
            while ($true) {
                $subKeyName       = New-Object System.Text.StringBuilder $nameCapacity
                $subKeyNameLength = [uint32]$nameCapacity
                $classLength      = [uint32]0
                $fileTime         = New-Object Win32Apis+FILETIME
                $result = [Win32Apis]::RegEnumKeyEx($subKeyHandle, [uint32]$index, $subKeyName, [ref]$subKeyNameLength,
                    [IntPtr]::Zero, $null, [ref]$classLength, [ref]$fileTime)

                if ($result -eq 234) {   # ERROR_MORE_DATA
                    if (++$retries -gt 5) {
                        throw "Subkey #$index of '$KeyPath' in hive '$HiveFilePath' has a name longer than $nameCapacity characters."
                    }
                    $nameCapacity *= 2
                    continue
                }
                if ($result -eq 259) { break }   # ERROR_NO_MORE_ITEMS
                if ($result -ne 0) {
                    throw "Failed to enumerate the subkeys of '$KeyPath' in hive '$HiveFilePath' (Win32 error $(Format-MSIXWin32Error $result))."
                }

                [pscustomobject]@{
                    Type      = 'Key'
                    Name      = $subKeyName.ToString()
                    Data      = $null
                    ValueType = $null
                }
                $index++
                $retries = 0
            }
        }
        finally {
            $null = [Win32Apis]::RegCloseKey($subKeyHandle)
        }
    }
    finally {
        $null = [Win32Apis]::RegCloseKey($hKey)
    }
}