noid-privacy/Modules/DNS/Public/Restore-DNSSettings.ps1

501 lines
28 KiB
PowerShell

function Restore-DNSSettings {
<#
.SYNOPSIS
Restore DNS settings from backup
.DESCRIPTION
Restores DNS configuration from backup file including:
- DNS server addresses (IPv4 and IPv6)
- DHCP configuration (if applicable)
- DoH configuration removal
CRITICAL: Properly handles DHCP vs static DNS restoration.
.PARAMETER BackupFilePath
Path to backup file created by Backup-DNSSettings
.PARAMETER DryRun
Show what would be restored without applying changes
.EXAMPLE
Restore-DNSSettings -BackupFilePath "C:\Rollback\DNS_20250116_030000.json"
.OUTPUTS
System.Boolean - $true if successful, $false otherwise
.NOTES
Uses Set-DnsClientServerAddress with -ResetServerAddresses for DHCP restore
Uses Remove-DnsClientDohServerAddress to clean up DoH configuration
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$BackupFilePath,
[Parameter()]
[switch]$DryRun
)
try {
if (-not (Test-Path $BackupFilePath)) {
Write-Log -Level ERROR -Message "Backup file not found: $BackupFilePath" -Module $script:ModuleName
return $false
}
Write-Log -Level INFO -Message "Restoring DNS settings from backup..." -Module $script:ModuleName
# Load backup data
$backupJson = Get-Content -Path $BackupFilePath -Raw -ErrorAction Stop
$backupData = $backupJson | ConvertFrom-Json
Write-Log -Level INFO -Message "Backup from: $($backupData.Timestamp)" -Module $script:ModuleName
Write-Log -Level DEBUG -Message "Restoring $($backupData.Adapters.Count) adapter(s)" -Module $script:ModuleName
# First: Clean all current netsh DoH entries
if (-not $DryRun) {
Write-Log -Level DEBUG -Message "Cleaning current netsh DoH entries..." -Module $script:ModuleName
try {
# Get all current DoH entries
$currentDohServers = Get-DnsClientDohServerAddress -ErrorAction SilentlyContinue
foreach ($dohServer in $currentDohServers) {
try {
# Remove via netsh (more reliable than PowerShell cmdlet)
netsh dnsclient delete encryption server=$($dohServer.ServerAddress) 2>&1 | Out-Null
Write-Log -Level DEBUG -Message " Removed netsh DoH entry: $($dohServer.ServerAddress)" -Module $script:ModuleName
}
catch {
Write-Log -Level DEBUG -Message " Could not remove netsh DoH entry: $($dohServer.ServerAddress)" -Module $script:ModuleName
}
}
}
catch {
Write-Log -Level DEBUG -Message "Could not clean netsh DoH entries: $_" -Module $script:ModuleName
}
# Clean all current DohFlags registry entries (both Doh and Doh6)
Write-Log -Level DEBUG -Message "Cleaning current DohFlags registry entries..." -Module $script:ModuleName
try {
$dohFlagsBasePath = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters"
if (Test-Path $dohFlagsBasePath) {
Get-ChildItem $dohFlagsBasePath -ErrorAction SilentlyContinue | ForEach-Object {
$adapterPath = $_.PSPath
# Remove entire DohInterfaceSettings (contains both Doh and Doh6 branches)
if (Test-Path "$adapterPath\DohInterfaceSettings") {
Remove-Item "$adapterPath\DohInterfaceSettings" -Recurse -Force -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Cleaned DohInterfaceSettings for adapter: $($_.PSChildName)" -Module $script:ModuleName
}
}
}
}
catch {
Write-Log -Level DEBUG -Message "Could not clean DohFlags entries: $_" -Module $script:ModuleName
}
}
# Restore DoH Policy Registry settings
if ($backupData.DohPolicySettings -and $backupData.DohPolicySettings.Count -gt 0) {
if ($DryRun) {
Write-Log -Level INFO -Message "[DRYRUN] Would restore $($backupData.DohPolicySettings.Count) DoH policy settings" -Module $script:ModuleName
}
else {
Write-Log -Level DEBUG -Message "Restoring DoH policy settings..." -Module $script:ModuleName
try {
# Restore DoHPolicy
if ($backupData.DohPolicySettings.ContainsKey('DoHPolicy')) {
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
if (-not (Test-Path $dnsClientPath)) {
New-Item -Path $dnsClientPath -Force | Out-Null
}
$existing = Get-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -ErrorAction SilentlyContinue
if ($null -ne $existing) {
Set-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -Value $backupData.DohPolicySettings['DoHPolicy'] -Force
} else {
New-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -Value $backupData.DohPolicySettings['DoHPolicy'] -PropertyType DWord -Force | Out-Null
}
Write-Log -Level DEBUG -Message " Restored DoHPolicy = $($backupData.DohPolicySettings['DoHPolicy'])" -Module $script:ModuleName
}
else {
# Remove DoHPolicy if it wasn't set before
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
if (Test-Path $dnsClientPath) {
Remove-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed DoHPolicy (was not set in backup)" -Module $script:ModuleName
# CLEANUP: Remove key if empty (created by us)
$keyContent = Get-ChildItem $dnsClientPath -ErrorAction SilentlyContinue
$keyProps = Get-ItemProperty $dnsClientPath -ErrorAction SilentlyContinue
# Count properties (exclude PS metadata like PSPath, etc.)
$propCount = ($keyProps.PSObject.Properties | Where-Object { $_.Name -notin @('PSPath','PSParentPath','PSChildName','PSDrive','PSProvider') }).Count
if (($null -eq $keyContent -or $keyContent.Count -eq 0) -and $propCount -eq 0) {
Remove-Item $dnsClientPath -Force -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed empty registry key: $dnsClientPath" -Module $script:ModuleName
}
}
}
# Restore EnableAutoDoh
if ($backupData.DohPolicySettings.ContainsKey('EnableAutoDoh')) {
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
$existing = Get-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue
if ($null -ne $existing) {
Set-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value $backupData.DohPolicySettings['EnableAutoDoh'] -Force
} else {
New-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value $backupData.DohPolicySettings['EnableAutoDoh'] -PropertyType DWord -Force | Out-Null
}
Write-Log -Level DEBUG -Message " Restored EnableAutoDoh = $($backupData.DohPolicySettings['EnableAutoDoh'])" -Module $script:ModuleName
}
else {
# Remove EnableAutoDoh if it wasn't set before
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
Remove-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed EnableAutoDoh (was not set in backup)" -Module $script:ModuleName
# CLEANUP: Remove key if empty (unlikely for Parameters, but safe to check)
if (Test-Path $dnsParamsPath) {
$keyContent = Get-ChildItem $dnsParamsPath -ErrorAction SilentlyContinue
$keyProps = Get-ItemProperty $dnsParamsPath -ErrorAction SilentlyContinue
$propCount = ($keyProps.PSObject.Properties | Where-Object { $_.Name -notin @('PSPath','PSParentPath','PSChildName','PSDrive','PSProvider') }).Count
if (($null -eq $keyContent -or $keyContent.Count -eq 0) -and $propCount -eq 0) {
Remove-Item $dnsParamsPath -Force -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed empty registry key: $dnsParamsPath" -Module $script:ModuleName
}
}
}
# Restore DohFlags (global)
if ($backupData.DohPolicySettings.ContainsKey('DohFlags')) {
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
$existing = Get-ItemProperty -Path $dnsParamsPath -Name "DohFlags" -ErrorAction SilentlyContinue
if ($null -ne $existing) {
Set-ItemProperty -Path $dnsParamsPath -Name "DohFlags" -Value $backupData.DohPolicySettings['DohFlags'] -Force
} else {
New-ItemProperty -Path $dnsParamsPath -Name "DohFlags" -Value $backupData.DohPolicySettings['DohFlags'] -PropertyType DWord -Force | Out-Null
}
Write-Log -Level DEBUG -Message " Restored DohFlags (global) = $($backupData.DohPolicySettings['DohFlags'])" -Module $script:ModuleName
}
else {
# Remove DohFlags if it wasn't set before
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
Remove-ItemProperty -Path $dnsParamsPath -Name "DohFlags" -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed DohFlags (global) (was not set in backup)" -Module $script:ModuleName
}
}
catch {
Write-Log -Level WARNING -Message "Could not restore DoH policy settings: $_" -Module $script:ModuleName
}
}
}
else {
# No DoH policy settings in backup - remove them
if (-not $DryRun) {
Write-Log -Level DEBUG -Message "No DoH policy settings in backup - removing current settings" -Module $script:ModuleName
try {
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
if (Test-Path $dnsClientPath) {
Remove-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -ErrorAction SilentlyContinue
}
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
Remove-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue
Remove-ItemProperty -Path $dnsParamsPath -Name "DohFlags" -ErrorAction SilentlyContinue
}
catch {
Write-Log -Level DEBUG -Message "Could not remove DoH policy settings: $_" -Module $script:ModuleName
}
}
}
# Restore netsh global DoH state
if ($backupData.NetshGlobalDoh) {
if ($DryRun) {
Write-Log -Level INFO -Message "[DRYRUN] Would restore netsh global DoH: $($backupData.NetshGlobalDoh)" -Module $script:ModuleName
}
else {
Write-Log -Level DEBUG -Message "Restoring netsh global DoH state: $($backupData.NetshGlobalDoh)" -Module $script:ModuleName
try {
netsh dnsclient set global doh=$($backupData.NetshGlobalDoh) 2>&1 | Out-Null
Write-Log -Level DEBUG -Message " netsh global DoH restored" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message "Could not restore netsh global DoH: $_" -Module $script:ModuleName
}
}
}
else {
# If no global DoH state in backup, it means it wasn't configured or we couldn't read it.
# Since we enable it in Apply, we should disable it here to be safe.
if (-not $DryRun) {
Write-Log -Level DEBUG -Message "No global DoH state in backup - resetting to default (doh=no)" -Module $script:ModuleName
try {
netsh dnsclient set global doh=no 2>&1 | Out-Null
Write-Log -Level DEBUG -Message " netsh global DoH reset to 'no'" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message "Could not reset netsh global DoH: $_" -Module $script:ModuleName
}
}
}
# Restore netsh DoH entries
if ($backupData.DohEntries -and $backupData.DohEntries.Count -gt 0) {
if ($DryRun) {
Write-Log -Level INFO -Message "[DRYRUN] Would restore $($backupData.DohEntries.Count) DoH entries from snapshot" -Module $script:ModuleName
}
else {
Write-Log -Level DEBUG -Message "Restoring $($backupData.DohEntries.Count) DoH entries from snapshot..." -Module $script:ModuleName
foreach ($entry in $backupData.DohEntries) {
$server = $entry.ServerAddress
$template = $entry.DohTemplate
if (-not $server -or -not $template) {
continue
}
try {
try {
Add-DnsClientDohServerAddress -ServerAddress $server `
-DohTemplate $template `
-AllowFallbackToUdp ([bool]$entry.AllowFallbackToUdp) `
-AutoUpgrade ([bool]$entry.AutoUpgrade) `
-ErrorAction Stop
}
catch {
Write-Log -Level DEBUG -Message " Add-DnsClientDohServerAddress failed for ${server}: $_" -Module $script:ModuleName
}
$udpFallback = if ($entry.AllowFallbackToUdp) { 'yes' } else { 'no' }
$autoupgrade = if ($entry.AutoUpgrade) { 'yes' } else { 'no' }
netsh dnsclient add encryption `
server=$server `
dohtemplate=$template `
autoupgrade=$autoupgrade `
udpfallback=$udpFallback 2>&1 | Out-Null
Write-Log -Level DEBUG -Message " Restored DoH entry: $server" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message "Could not restore DoH entry for ${server}: $_" -Module $script:ModuleName
}
}
}
}
elseif ($backupData.NetshDohEntries -and $backupData.NetshDohEntries.Count -gt 0) {
if ($DryRun) {
Write-Log -Level INFO -Message "[DRYRUN] Would restore $($backupData.NetshDohEntries.Count) netsh DoH entries" -Module $script:ModuleName
}
else {
Write-Log -Level DEBUG -Message "Restoring $($backupData.NetshDohEntries.Count) netsh DoH entries..." -Module $script:ModuleName
foreach ($entry in $backupData.NetshDohEntries) {
try {
$autoupgrade = if ($entry.AutoUpgrade -eq 'yes') { 'yes' } else { 'no' }
$udpfallback = if ($entry.UdpFallback -eq 'yes') { 'yes' } else { 'no' }
netsh dnsclient add encryption `
server=$($entry.Server) `
dohtemplate=$($entry.Template) `
autoupgrade=$autoupgrade `
udpfallback=$udpfallback 2>&1 | Out-Null
Write-Log -Level DEBUG -Message " Restored netsh DoH: $($entry.Server)" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message "Could not restore netsh DoH for $($entry.Server): $_" -Module $script:ModuleName
}
}
}
}
$success = $true
foreach ($adapterBackup in $backupData.Adapters) {
$adapterName = $adapterBackup.InterfaceAlias
$interfaceIndex = $adapterBackup.InterfaceIndex
Write-Log -Level INFO -Message "Restoring adapter: $adapterName" -Module $script:ModuleName
# Verify adapter still exists
$adapter = Get-NetAdapter -InterfaceIndex $interfaceIndex -ErrorAction SilentlyContinue
if (-not $adapter) {
Write-Log -Level WARNING -Message " Adapter no longer exists - skipping" -Module $script:ModuleName
continue
}
if ($DryRun) {
if ($adapterBackup.IsDHCP) {
Write-Log -Level INFO -Message "[DRYRUN] Would reset $adapterName to DHCP" -Module $script:ModuleName
}
else {
Write-Log -Level INFO -Message "[DRYRUN] Would restore static DNS on $adapterName" -Module $script:ModuleName
Write-Log -Level DEBUG -Message "[DRYRUN] IPv4: $($adapterBackup.IPv4Addresses -join ', ')" -Module $script:ModuleName
Write-Log -Level DEBUG -Message "[DRYRUN] IPv6: $($adapterBackup.IPv6Addresses -join ', ')" -Module $script:ModuleName
if ($adapterBackup.DohFlags -and $adapterBackup.DohFlags.Count -gt 0) {
Write-Log -Level DEBUG -Message "[DRYRUN] DohFlags: $($adapterBackup.DohFlags.Count) registry entries" -Module $script:ModuleName
}
if ($null -ne $adapterBackup.DhcpOverrideDisabled) {
$overrideStatus = if ($adapterBackup.DhcpOverrideDisabled) { "disabled" } else { "enabled" }
Write-Log -Level DEBUG -Message "[DRYRUN] DHCP Override: $overrideStatus" -Module $script:ModuleName
}
}
continue
}
# Remove DoH configuration first (if any)
if ($adapterBackup.DoHConfiguration.Count -gt 0) {
Write-Log -Level DEBUG -Message " Removing DoH configuration..." -Module $script:ModuleName
foreach ($dohConfig in $adapterBackup.DoHConfiguration) {
try {
Remove-DnsClientDohServerAddress -ServerAddress $dohConfig.ServerAddress -ErrorAction SilentlyContinue
Write-Log -Level DEBUG -Message " Removed DoH for $($dohConfig.ServerAddress)" -Module $script:ModuleName
}
catch {
Write-Log -Level DEBUG -Message " Could not remove DoH for $($dohConfig.ServerAddress): $_" -Module $script:ModuleName
}
}
}
# Restore DNS configuration
if ($adapterBackup.IsDHCP) {
# Reset to DHCP
Write-Log -Level INFO -Message " Resetting to DHCP..." -Module $script:ModuleName
try {
Set-DnsClientServerAddress -InterfaceIndex $interfaceIndex -ResetServerAddresses -ErrorAction Stop
Write-Log -Level SUCCESS -Message " DNS reset to DHCP successfully" -Module $script:ModuleName
}
catch {
Write-Log -Level ERROR -Message " Failed to reset to DHCP: $_" -Module $script:ModuleName
$success = $false
}
}
else {
# Restore static DNS
Write-Log -Level INFO -Message " Restoring static DNS..." -Module $script:ModuleName
# Restore IPv4
if ($adapterBackup.IPv4Addresses.Count -gt 0) {
try {
Set-DnsClientServerAddress -InterfaceIndex $interfaceIndex `
-ServerAddresses $adapterBackup.IPv4Addresses `
-ErrorAction Stop
Write-Log -Level SUCCESS -Message " IPv4 DNS restored: $($adapterBackup.IPv4Addresses -join ', ')" -Module $script:ModuleName
}
catch {
Write-Log -Level ERROR -Message " Failed to restore IPv4 DNS: $_" -Module $script:ModuleName
$success = $false
}
}
# Restore IPv6 (if was configured)
if ($adapterBackup.IPv6Addresses.Count -gt 0) {
try {
# Use netsh for IPv6 (PowerShell cmdlet limitation)
$primaryIPv6 = $adapterBackup.IPv6Addresses[0]
& netsh interface ipv6 set dnsservers name="$adapterName" source=static address=$primaryIPv6 validate=no 2>&1 | Out-Null
if ($adapterBackup.IPv6Addresses.Count -gt 1) {
$secondaryIPv6 = $adapterBackup.IPv6Addresses[1]
& netsh interface ipv6 add dnsservers name="$adapterName" address=$secondaryIPv6 index=2 validate=no 2>&1 | Out-Null
}
Write-Log -Level SUCCESS -Message " IPv6 DNS restored: $($adapterBackup.IPv6Addresses -join ', ')" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message " Could not restore IPv6 DNS (non-fatal): $_" -Module $script:ModuleName
}
}
# Restore DoH configuration (if was configured)
if ($adapterBackup.DoHConfiguration.Count -gt 0) {
Write-Log -Level DEBUG -Message " Restoring DoH configuration..." -Module $script:ModuleName
foreach ($dohConfig in $adapterBackup.DoHConfiguration) {
try {
Add-DnsClientDohServerAddress -ServerAddress $dohConfig.ServerAddress `
-DohTemplate $dohConfig.DohTemplate `
-AllowFallbackToUdp $dohConfig.AllowFallbackToUdp `
-AutoUpgrade $dohConfig.AutoUpgrade `
-ErrorAction Stop
Write-Log -Level DEBUG -Message " Restored DoH for $($dohConfig.ServerAddress)" -Module $script:ModuleName
}
catch {
Write-Log -Level DEBUG -Message " Could not restore DoH for $($dohConfig.ServerAddress): $_" -Module $script:ModuleName
}
}
}
# Restore DohFlags registry settings (if was configured)
if ($adapterBackup.DohFlags -and $adapterBackup.DohFlags.Count -gt 0) {
Write-Log -Level DEBUG -Message " Restoring DohFlags registry settings..." -Module $script:ModuleName
$interfaceGuid = $adapterBackup.InterfaceGuid
if (-not $interfaceGuid) {
# Fallback: get current GUID
$currentAdapter = Get-NetAdapter -InterfaceIndex $interfaceIndex -ErrorAction SilentlyContinue
$interfaceGuid = $currentAdapter.InterfaceGuid
}
if ($interfaceGuid) {
foreach ($dnsIP in $adapterBackup.DohFlags.Keys) {
try {
$flagValue = $adapterBackup.DohFlags[$dnsIP]
$dohFlagsPath = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$interfaceGuid\DohInterfaceSettings\Doh\$dnsIP"
# Create path if needed
if (-not (Test-Path $dohFlagsPath)) {
New-Item -Path $dohFlagsPath -Force -ErrorAction Stop | Out-Null
}
# Restore DohFlags value
New-ItemProperty -Path $dohFlagsPath -Name "DohFlags" -Value $flagValue -PropertyType QWORD -Force -ErrorAction Stop | Out-Null
Write-Log -Level DEBUG -Message " Restored DohFlags for $dnsIP = $flagValue" -Module $script:ModuleName
}
catch {
Write-Log -Level DEBUG -Message " Could not restore DohFlags for $dnsIP : $_" -Module $script:ModuleName
}
}
}
else {
Write-Log -Level WARNING -Message " Could not restore DohFlags: Interface GUID not available" -Module $script:ModuleName
}
}
# Restore DHCP DNS Override setting
if ($null -ne $adapterBackup.DhcpOverrideDisabled) {
Write-Log -Level DEBUG -Message " Restoring DHCP DNS override setting..." -Module $script:ModuleName
try {
# DhcpOverrideDisabled=true means RegisterThisConnectionsAddress=false
$registerThisConnection = -not $adapterBackup.DhcpOverrideDisabled
Set-DnsClient -InterfaceIndex $interfaceIndex `
-RegisterThisConnectionsAddress $registerThisConnection `
-ErrorAction Stop
$statusText = if ($adapterBackup.DhcpOverrideDisabled) { "disabled" } else { "enabled" }
Write-Log -Level DEBUG -Message " DHCP DNS override: $statusText" -Module $script:ModuleName
}
catch {
Write-Log -Level WARNING -Message " Could not restore DHCP override setting: $_" -Module $script:ModuleName
}
}
}
}
if ($success) {
Write-Log -Level SUCCESS -Message "DNS settings restored successfully" -Module $script:ModuleName
}
else {
Write-Log -Level WARNING -Message "DNS settings restored with some errors" -Module $script:ModuleName
}
return $success
}
catch {
Write-ErrorLog -Message "Failed to restore DNS settings from backup" -Module $script:ModuleName -ErrorRecord $_
return $false
}
}