2026-05-17 23:39:41 +02:00
function Get-NormalizedRegistryValueName {
param (
[ AllowNull ( ) ]
$ValueName
)
if ( [ string ] :: IsNullOrEmpty ( [ string ] $ValueName ) ) {
return ''
}
return [ string ] $ValueName
}
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
2026-05-17 23:39:41 +02:00
$operationKeyPath = [ string ] $Operation . KeyPath
2026-05-17 18:45:51 +02:00
switch ( $valueType ) {
'DWord' {
$unsigned = [ uint32 ] $Operation . ValueData
$value = [ BitConverter ] :: ToInt32 ( [ BitConverter ] :: GetBytes ( $unsigned ) , 0 )
return @ { Name = $valueName ; Kind = [ Microsoft.Win32.RegistryValueKind ] :: DWord ; 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
}
default {
2026-05-17 23:39:41 +02:00
throw " Unsupported value type ' $valueType ' while applying reg operation for ' $operationKeyPath ' "
2026-05-17 18:45:51 +02:00
}
}
}
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 }
}
2026-05-17 23:39:41 +02:00
function Invoke-RegistryDeleteValueOperation {
param (
[ Parameter ( Mandatory ) ]
$Operation ,
[ Parameter ( Mandatory ) ]
$KeyInfo
)
if ( $null -eq $KeyInfo . Key ) {
2026-05-17 23:43:35 +02:00
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation . ValueName
$displayValueName = if ( [ string ] :: IsNullOrEmpty ( $valueName ) ) { '(Default)' } else { $valueName }
Write-Verbose " Unable to find or open key ' $( $Operation . KeyPath ) ' and value ' $displayValueName ' "
2026-05-17 23:39:41 +02:00
return
}
try {
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation . ValueName
$KeyInfo . Key . DeleteValue ( $valueName , $false )
}
finally {
$KeyInfo . Key . Close ( )
}
}
function Invoke-RegistrySetValueOperation {
param (
[ Parameter ( Mandatory ) ]
$Operation ,
[ Parameter ( Mandatory ) ]
$KeyInfo
)
if ( $null -eq $KeyInfo . Key ) {
throw [ System.UnauthorizedAccessException ] :: new ( " Unable to open or create registry key ' $( $Operation . KeyPath ) ' " )
}
try {
$setArgs = Convert-RegOperationToValueKind -Operation $Operation
$KeyInfo . Key . SetValue ( $setArgs . Name , $setArgs . Value , $setArgs . Kind )
}
finally {
$KeyInfo . Key . Close ( )
}
}
function Write-RegistryOperationAccessDeniedWarning {
param (
[ Parameter ( Mandatory ) ]
$Operation ,
[ Parameter ( Mandatory ) ]
[ string ] $ExceptionMessage
)
$keyPath = [ string ] $Operation . KeyPath
$operationType = [ string ] $Operation . OperationType
if ( $operationType -eq 'SetValue' -or $operationType -eq 'DeleteValue' ) {
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation . ValueName
$displayValueName = if ( [ string ] :: IsNullOrEmpty ( $valueName ) ) { '(Default)' } else { $valueName }
Write-Warning " Skipping operation ' $operationType ' on key ' $keyPath ' value ' $displayValueName ' due to access restrictions: $ExceptionMessage "
return
}
Write-Warning " Skipping operation ' $operationType ' on key ' $keyPath ' due to access restrictions: $ExceptionMessage "
}
function Invoke-RegistryOperation {
param (
[ Parameter ( Mandatory ) ]
$Operation ,
[ Parameter ( Mandatory ) ]
[ string ] $RegFilePath
)
$operationType = [ string ] $Operation . OperationType
$isSetValueOperation = $operationType -eq 'SetValue'
$isDeleteKeyOperation = $operationType -eq 'DeleteKey'
$keyInfo = Get-RegistryKeyForOperation -RegistryPath $Operation . KeyPath -CreateIfMissing: $isSetValueOperation -OpenKey: ( -not $isDeleteKeyOperation )
switch ( $operationType ) {
'DeleteKey' {
if ( $null -ne $keyInfo . SubKeyPath ) {
Remove-RegistrySubKeyTreeIfExists -RootKey $keyInfo . RootKey -SubKeyPath $keyInfo . SubKeyPath
}
}
'DeleteValue' {
Invoke-RegistryDeleteValueOperation -Operation $Operation -KeyInfo $keyInfo
}
'SetValue' {
Invoke-RegistrySetValueOperation -Operation $Operation -KeyInfo $keyInfo
}
default {
throw " Unsupported reg operation type ' $( $Operation . OperationType ) ' in ' $RegFilePath ' "
}
}
}
2026-05-17 18:45:51 +02:00
function Invoke-RegistryOperationsFromRegFile {
param (
[ Parameter ( Mandatory ) ]
[ string ] $RegFilePath
)
2026-05-17 21:39:35 +02:00
$accessDeniedCount = 0
2026-05-17 23:16:32 +02:00
$operations = @ ( Get-RegFileOperations -regFilePath $RegFilePath )
$totalOperations = $operations . Count
2026-05-17 18:45:51 +02:00
2026-05-17 23:16:32 +02:00
foreach ( $operation in $operations ) {
2026-05-17 20:05:20 +02:00
try {
2026-05-17 23:39:41 +02:00
Invoke-RegistryOperation -Operation $operation -RegFilePath $RegFilePath
2026-05-17 20:05:20 +02:00
}
catch [ System.UnauthorizedAccessException], [System.Security.SecurityException ] {
2026-05-17 21:39:35 +02:00
$accessDeniedCount + +
2026-05-17 23:39:41 +02:00
Write-RegistryOperationAccessDeniedWarning -Operation $operation -ExceptionMessage $_ . Exception . Message
2026-05-17 18:45:51 +02:00
}
}
2026-05-17 20:05:20 +02:00
2026-05-17 23:16:32 +02:00
if ( $totalOperations -gt 0 -and $accessDeniedCount -eq $totalOperations ) {
throw " Registry fallback import could not apply any operations in ' $RegFilePath ' because all $accessDeniedCount operation(s) were blocked by access restrictions. "
}
2026-05-17 21:39:35 +02:00
if ( $accessDeniedCount -gt 0 ) {
Write-Warning " Registry fallback import completed with $accessDeniedCount access-restricted operation(s) skipped in ' $RegFilePath '. "
2026-05-17 20:05:20 +02:00
}
2026-05-17 18:45:51 +02:00
}