noid-privacy/Modules/SecurityBaseline/Private/Restore-RegistryPolicies.ps1

231 lines
11 KiB
PowerShell

<#
.SYNOPSIS
Restore registry policies from backup
.DESCRIPTION
Restores all registry keys/values from a backup JSON file.
Handles non-existent keys/values correctly.
.PARAMETER BackupPath
Path to backup JSON file created by Backup-RegistryPolicies
.OUTPUTS
PSCustomObject with restore status
.NOTES
Restores ORIGINAL values including deletions if keys didn't exist before
#>
function Restore-RegistryPolicies {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$BackupPath
)
$result = [PSCustomObject]@{
Success = $false
ItemsRestored = 0
Errors = @()
}
if (-not (Test-Path $BackupPath)) {
$result.Errors += "Backup file not found: $BackupPath"
return $result
}
try {
Write-Log -Level DEBUG -Message "Loading backup from: $BackupPath" -Module "SecurityBaseline"
$backup = Get-Content -Path $BackupPath -Raw | ConvertFrom-Json
# Restore Computer policies (HKLM)
if ($backup.Computer) {
Write-Log -Level DEBUG -Message "Restoring $($backup.Computer.Count) Computer registry values..." -Module "SecurityBaseline"
foreach ($item in $backup.Computer) {
try {
# Parse key path
$keyPath = $item.KeyName -replace '^\[', '' -replace '\]$', ''
if ($keyPath -match '^(SOFTWARE|SYSTEM)\\') {
$fullPath = "HKLM:\$keyPath"
}
else {
continue
}
# Handle restoration based on original state
# CRITICAL FIX: **del* values are GPO DELETE markers - they should NOT be restored!
# These markers instruct GPO to delete a value. If we restore them, verification fails
# because verification expects these values to be DELETED (not present).
if ($item.ValueName -like "**del*" -or $item.ValueName -like "**delvals*") {
# DELETE marker - ensure value is deleted (not restored)
if (Test-Path $fullPath) {
try {
Remove-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message "Removed DELETE marker: $fullPath\$($item.ValueName)" -Module "SecurityBaseline"
}
catch { $null = $null }
}
}
elseif ($item.Exists -eq $false) {
# Item didn't exist before - delete it
if (Test-Path $fullPath) {
try {
Remove-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction Stop
Write-Log -Level DEBUG -Message "Removed: $fullPath\$($item.ValueName)" -Module "SecurityBaseline"
}
catch {
# Value doesn't exist anymore - that's fine
$null = $null
}
}
}
else {
# Item existed - restore original value
if (-not (Test-Path $fullPath)) {
New-Item -Path $fullPath -Force | Out-Null
}
# Convert type
$regType = switch ($item.Type) {
"REG_DWORD" { "DWord" }
"REG_SZ" { "String" }
"REG_EXPAND_SZ" { "ExpandString" }
"REG_BINARY" { "Binary" }
"REG_MULTI_SZ" { "MultiString" }
default { "String" }
}
# Restore value (create or update with correct type)
$existingValue = Get-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction SilentlyContinue
if ($null -ne $existingValue) {
# Value exists - update it
Set-ItemProperty -Path $fullPath `
-Name $item.ValueName `
-Value $item.OriginalValue `
-Force `
-ErrorAction Stop
}
else {
# Value does not exist - create it with proper type
New-ItemProperty -Path $fullPath `
-Name $item.ValueName `
-Value $item.OriginalValue `
-PropertyType $regType `
-Force `
-ErrorAction Stop | Out-Null
}
Write-Log -Level DEBUG -Message "Restored: $fullPath\$($item.ValueName) = $($item.OriginalValue)" -Module "SecurityBaseline"
}
$result.ItemsRestored++
}
catch {
$result.Errors += "Failed to restore $($item.KeyName)\$($item.ValueName): $_"
}
}
}
# Restore User policies (HKCU)
if ($backup.User) {
Write-Log -Level DEBUG -Message "Restoring $($backup.User.Count) User registry values..." -Module "SecurityBaseline"
foreach ($item in $backup.User) {
try {
# Parse key path
$keyPath = $item.KeyName -replace '^\[', '' -replace '\]$', ''
if ($keyPath -match '^SOFTWARE\\') {
$fullPath = "HKCU:\$keyPath"
}
else {
continue
}
# Handle restoration based on original state
# CRITICAL FIX: **del* values are GPO DELETE markers - they should NOT be restored!
if ($item.ValueName -like "**del*" -or $item.ValueName -like "**delvals*") {
# DELETE marker - ensure value is deleted (not restored)
if (Test-Path $fullPath) {
try {
Remove-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message "Removed DELETE marker: $fullPath\$($item.ValueName)" -Module "SecurityBaseline"
}
catch { $null = $null }
}
}
elseif ($item.Exists -eq $false) {
# Item didn't exist before - delete it
if (Test-Path $fullPath) {
try {
Remove-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction Stop
Write-Log -Level DEBUG -Message "Removed: $fullPath\$($item.ValueName)" -Module "SecurityBaseline"
}
catch {
# Value doesn't exist anymore - that's fine
$null = $null
}
}
}
else {
# Item existed - restore original value
if (-not (Test-Path $fullPath)) {
New-Item -Path $fullPath -Force | Out-Null
}
# Convert type
$regType = switch ($item.Type) {
"REG_DWORD" { "DWord" }
"REG_SZ" { "String" }
"REG_EXPAND_SZ" { "ExpandString" }
"REG_BINARY" { "Binary" }
"REG_MULTI_SZ" { "MultiString" }
default { "String" }
}
# Restore value (create or update with correct type)
$existingValue = Get-ItemProperty -Path $fullPath -Name $item.ValueName -ErrorAction SilentlyContinue
if ($null -ne $existingValue) {
# Value exists - update it
Set-ItemProperty -Path $fullPath `
-Name $item.ValueName `
-Value $item.OriginalValue `
-Force `
-ErrorAction Stop
}
else {
# Value does not exist - create it with proper type
New-ItemProperty -Path $fullPath `
-Name $item.ValueName `
-Value $item.OriginalValue `
-PropertyType $regType `
-Force `
-ErrorAction Stop | Out-Null
}
Write-Log -Level DEBUG -Message "Restored: $fullPath\$($item.ValueName) = $($item.OriginalValue)" -Module "SecurityBaseline"
}
$result.ItemsRestored++
}
catch {
$result.Errors += "Failed to restore User $($item.KeyName)\$($item.ValueName): $_"
}
}
}
$result.Success = ($result.Errors.Count -eq 0)
Write-Log -Level DEBUG -Message "Registry restore complete: $($result.ItemsRestored) items restored" -Module "SecurityBaseline"
}
catch {
$result.Errors += "Registry restore failed: $_"
Write-Error "Registry restore failed: $_"
}
return $result
}