2026-05-17 18:45:51 +02:00
function Convert-RegOperationToValueKind {
param (
[ Parameter ( Mandatory ) ]
$Operation
)
$valueName = if ( [ string ] :: IsNullOrEmpty ( [ string ] $Operation . ValueName ) ) { '' } else { [ string ] $Operation . ValueName }
$valueType = [ string ] $Operation . ValueType
switch ( $valueType ) {
'DWord' {
$unsigned = [ uint32 ] $Operation . ValueData
$value = [ BitConverter ] :: ToInt32 ( [ BitConverter ] :: GetBytes ( $unsigned ) , 0 )
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: DWord ; Value = $value }
}
'QWord' {
$unsigned = [ uint64 ] $Operation . ValueData
$value = [ BitConverter ] :: ToInt64 ( [ BitConverter ] :: GetBytes ( $unsigned ) , 0 )
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: QWord ; Value = $value }
}
'String' {
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: String ; Value = [ string ] $Operation . ValueData }
}
2026-05-17 20:00:40 +02:00
'Binary' {
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: Binary ; Value = [ byte[] ] $Operation . ValueData }
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:00:40 +02:00
'Hex0' {
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: None ; Value = [ byte[] ] $Operation . ValueData }
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:00:40 +02:00
'Hex3' {
2026-05-17 18:45:51 +02:00
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: Binary ; Value = [ byte[] ] $Operation . ValueData }
}
2026-05-17 20:00:40 +02:00
'HexB' {
$qwordBytes = [ byte[] ] $Operation . ValueData
if ( $qwordBytes . Count -gt 8 ) {
throw " Unsupported hex value type ' $valueType ' with invalid byte count ' $( $qwordBytes . Count ) ' while applying reg operation for ' $( $Operation . KeyPath ) '. "
}
if ( $qwordBytes . Count -lt 8 ) {
$paddedBytes = New-Object byte [ ] 8
[ Array ] :: Copy ( $qwordBytes , $paddedBytes , $qwordBytes . Count )
$qwordBytes = $paddedBytes
}
$unsigned = [ BitConverter ] :: ToUInt64 ( $qwordBytes , 0 )
$signed = [ BitConverter ] :: ToInt64 ( [ BitConverter ] :: GetBytes ( $unsigned ) , 0 )
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: QWord ; Value = $signed }
}
2026-05-17 18:45:51 +02:00
default {
if ( $valueType -like 'Hex*' ) {
throw " Unsupported hex value type ' $valueType ' while applying reg operation for ' $( $Operation . KeyPath ) '. "
}
throw " Unsupported value type ' $valueType ' while applying reg operation for ' $( $Operation . KeyPath ) ' "
}
}
}
function Remove-RegistrySubKeyTreeIfExists {
param (
[ Parameter ( Mandatory ) ]
[ Microsoft.Win32.RegistryKey ] $RootKey ,
[ Parameter ( Mandatory ) ]
[ string ] $SubKeyPath
)
try {
$RootKey . DeleteSubKeyTree ( $SubKeyPath , $false )
}
2026-05-17 20:05:20 +02:00
catch [ System.UnauthorizedAccessException], [System.Security.SecurityException ] {
throw
}
2026-05-17 18:45:51 +02:00
catch {
# Best-effort cleanup only; missing keys are fine.
}
}
function Get-RegistryKeyForOperation {
param (
[ Parameter ( Mandatory ) ]
[ string ] $RegistryPath ,
2026-05-17 20:06:14 +02:00
[ switch ] $CreateIfMissing ,
[ bool ] $OpenKey = $true
2026-05-17 18:45:51 +02:00
)
$parts = Split-RegistryPath -path $RegistryPath
if ( -not $parts ) {
throw " Unsupported registry path: $RegistryPath "
}
$rootKey = Get-RegistryRootKey -hiveName $parts . Hive
if ( -not $rootKey ) {
throw " Unsupported registry hive ' $( $parts . Hive ) ' in path ' $RegistryPath ' "
}
$subKeyPath = $parts . SubKey
if ( [ string ] :: IsNullOrWhiteSpace ( $subKeyPath ) ) {
return [ PSCustomObject ] @ { RootKey = $rootKey ; SubKeyPath = $null ; Key = $rootKey }
}
2026-05-17 20:06:14 +02:00
if ( -not $OpenKey ) {
return [ PSCustomObject ] @ { RootKey = $rootKey ; SubKeyPath = $subKeyPath ; Key = $null }
}
2026-05-17 18:45:51 +02:00
$key = if ( $CreateIfMissing ) {
$rootKey . CreateSubKey ( $subKeyPath )
}
else {
$rootKey . OpenSubKey ( $subKeyPath , $true )
}
return [ PSCustomObject ] @ { RootKey = $rootKey ; SubKeyPath = $subKeyPath ; Key = $key }
}
function Invoke-RegistryOperationsFromRegFile {
param (
[ Parameter ( Mandatory ) ]
[ string ] $RegFilePath
)
2026-05-17 20:05:20 +02:00
$accessDeniedFailures = New-Object 'System.Collections.Generic.List[object]'
2026-05-17 18:45:51 +02:00
2026-05-17 20:05:20 +02:00
foreach ( $operation in @ ( Get-RegFileOperations -regFilePath $RegFilePath ) ) {
try {
2026-05-17 20:06:14 +02:00
$keyInfo = Get-RegistryKeyForOperation -RegistryPath $operation . KeyPath -CreateIfMissing: ( $operation . OperationType -eq 'SetValue' ) -OpenKey: ( $operation . OperationType -ne 'DeleteKey' )
2026-05-17 20:05:20 +02:00
switch ( $operation . OperationType ) {
'DeleteKey' {
2026-05-17 20:06:14 +02:00
if ( $null -ne $keyInfo . SubKeyPath ) {
Remove-RegistrySubKeyTreeIfExists -RootKey $keyInfo . RootKey -SubKeyPath $keyInfo . SubKeyPath
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:05:20 +02:00
}
'DeleteValue' {
if ( $null -ne $keyInfo . Key ) {
try {
$valueName = if ( [ string ] :: IsNullOrEmpty ( [ string ] $operation . ValueName ) ) { '' } else { [ string ] $operation . ValueName }
$keyInfo . Key . DeleteValue ( $valueName , $false )
}
finally {
$keyInfo . Key . Close ( )
}
2026-05-17 18:45:51 +02:00
}
}
2026-05-17 20:05:20 +02:00
'SetValue' {
if ( $null -eq $keyInfo . Key ) {
throw " Unable to open or create registry key ' $( $operation . KeyPath ) ' "
}
2026-05-17 18:45:51 +02:00
try {
2026-05-17 20:05:20 +02:00
$setArgs = Convert-RegOperationToValueKind -Operation $operation
$keyInfo . Key . SetValue ( $setArgs . Name , $setArgs . Value , $setArgs . Kind )
2026-05-17 18:45:51 +02:00
}
finally {
$keyInfo . Key . Close ( )
}
}
2026-05-17 20:05:20 +02:00
default {
throw " Unsupported reg operation type ' $( $operation . OperationType ) ' in ' $RegFilePath ' "
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:05:20 +02:00
}
}
catch [ System.UnauthorizedAccessException], [System.Security.SecurityException ] {
$valueDisplay = if ( $operation . OperationType -eq 'SetValue' -or $operation . OperationType -eq 'DeleteValue' ) {
if ( [ string ] :: IsNullOrEmpty ( [ string ] $operation . ValueName ) ) { '(Default)' } else { [ string ] $operation . ValueName }
}
else {
''
}
2026-05-17 18:45:51 +02:00
2026-05-17 20:05:20 +02:00
$failure = [ PSCustomObject ] @ {
OperationType = [ string ] $operation . OperationType
KeyPath = [ string ] $operation . KeyPath
ValueName = $valueDisplay
Error = $_ . Exception . Message
}
$accessDeniedFailures . Add ( $failure )
if ( [ string ] :: IsNullOrEmpty ( $valueDisplay ) ) {
Write-Warning ( " Skipping operation '{0}' on key '{1}' due to access restrictions: {2} " -f $failure . OperationType , $failure . KeyPath , $failure . Error )
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:05:20 +02:00
else {
Write-Warning ( " Skipping operation '{0}' on key '{1}' value '{2}' due to access restrictions: {3} " -f $failure . OperationType , $failure . KeyPath , $failure . ValueName , $failure . Error )
2026-05-17 18:45:51 +02:00
}
2026-05-17 20:05:20 +02:00
continue
2026-05-17 18:45:51 +02:00
}
}
2026-05-17 20:05:20 +02:00
if ( $accessDeniedFailures . Count -gt 0 ) {
Write-Warning ( " Registry fallback import completed with $( $accessDeniedFailures . Count ) access-restricted operation(s) skipped in ' $RegFilePath '. " )
}
2026-05-17 18:45:51 +02:00
}
function Invoke-RegistryImportViaPowerShell {
param (
[ Parameter ( Mandatory ) ]
[ string ] $RegFilePath ,
[ switch ] $UseOfflineHive ,
[ string ] $OfflineHiveDatPath
)
$applyScript = {
param ( $targetRegFilePath )
Invoke-RegistryOperationsFromRegFile -RegFilePath $targetRegFilePath
}
if ( $UseOfflineHive ) {
if ( Get-Command -Name Invoke-WithLoadedBackupHive -ErrorAction SilentlyContinue ) {
return Invoke-WithLoadedBackupHive -ScriptBlock $applyScript -ArgumentObject $RegFilePath
}
if ( [ string ] :: IsNullOrWhiteSpace ( $OfflineHiveDatPath ) ) {
throw " Offline hive path was not provided for fallback import of ' $RegFilePath ' "
}
$global:LASTEXITCODE = 0
reg load " HKU\Default " $OfflineHiveDatPath | Out-Null
$loadExitCode = $LASTEXITCODE
if ( $loadExitCode -ne 0 ) {
throw " Failed to load user hive at ' $OfflineHiveDatPath ' for fallback import (exit code: $loadExitCode ) "
}
try {
return & $applyScript $RegFilePath
}
finally {
$global:LASTEXITCODE = 0
reg unload " HKU\Default " | Out-Null
$unloadExitCode = $LASTEXITCODE
if ( $unloadExitCode -ne 0 ) {
Write-Warning " Fallback import completed, but unloading HKU\Default failed (exit code: $unloadExitCode ). "
}
}
}
return & $applyScript $RegFilePath
}