# Safe PowerShell Script to Update DNS Servers on Remote Machines # Author: DNS Management Script # Date: 2025-10-15 <# .SYNOPSIS Updates DNS server settings on remote servers with specified DNS IPs in order. .DESCRIPTION This script reads hostnames from dnsServer.txt and updates their DNS settings to match the specified DNS server list in the exact order provided. It includes safety checks, logging, and rollback capabilities. .NOTES - Requires administrative privileges on remote servers - Requires PowerShell Remoting to be enabled on target servers - Creates backup of current DNS settings before making changes #> # ============================================ # CONFIGURATION # ============================================ # Define the desired DNS servers in order (1st, 2nd, 3rd, etc.) $DesiredDNSServers = @( "1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4", "5.5.5.5", "6.6.6.6" ) # Input file containing server hostnames (one per line) $ServerListFile = "C:\Scripts\update dns\dnsServer.txt" # Log file path $LogFile = "C:\Scripts\update dns\logs\DNS_Update_Log_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt" # Backup file for rollback $BackupFile = "C:\Scripts\update dns\logs\DNS_Backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" # ============================================ # FUNCTIONS # ============================================ function Write-Log { param( [string]$Message, [ValidateSet('INFO','WARNING','ERROR','SUCCESS')] [string]$Level = 'INFO' ) $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $LogMessage = "[$Timestamp] [$Level] $Message" # Write to console with color switch ($Level) { 'ERROR' { Write-Host $LogMessage -ForegroundColor Red } 'WARNING' { Write-Host $LogMessage -ForegroundColor Yellow } 'SUCCESS' { Write-Host $LogMessage -ForegroundColor Green } default { Write-Host $LogMessage } } # Write to log file Add-Content -Path $LogFile -Value $LogMessage } function Test-ServerConnectivity { param([string]$ServerName) try { $PingResult = Test-Connection -ComputerName $ServerName -Count 2 -Quiet -ErrorAction Stop if ($PingResult) { # Test WinRM connectivity $WinRMTest = Test-WSMan -ComputerName $ServerName -ErrorAction Stop return $true } return $false } catch { return $false } } function Get-CurrentDNSSettings { param([string]$ServerName) try { $DNSSettings = Invoke-Command -ComputerName $ServerName -ScriptBlock { Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 } | Select-Object InterfaceAlias, InterfaceIndex, ServerAddresses } -ErrorAction Stop return $DNSSettings } catch { Write-Log "Failed to retrieve DNS settings from $ServerName : $($_.Exception.Message)" -Level ERROR return $null } } function Backup-DNSSettings { param( [string]$ServerName, [object]$DNSSettings ) try { foreach ($Interface in $DNSSettings) { $BackupEntry = [PSCustomObject]@{ ServerName = $ServerName InterfaceAlias = $Interface.InterfaceAlias InterfaceIndex = $Interface.InterfaceIndex DNSServers = ($Interface.ServerAddresses -join ';') BackupTime = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' } $BackupEntry | Export-Csv -Path $BackupFile -Append -NoTypeInformation } return $true } catch { Write-Log "Failed to backup DNS settings for $ServerName : $($_.Exception.Message)" -Level ERROR return $false } } function Merge-DNSSettings { param( [array]$CurrentDNS, [array]$DesiredDNS ) # Start with desired DNS servers in order $MergedDNS = @($DesiredDNS) # Add existing DNS servers that are NOT in the desired list to the end foreach ($DNS in $CurrentDNS) { if ($DNS -notin $DesiredDNS) { $MergedDNS += $DNS Write-Verbose "Preserving existing DNS: $DNS (moved to end)" } } return $MergedDNS } function Compare-DNSSettings { param( [array]$CurrentDNS, [array]$ExpectedDNS ) # Compare arrays if ($CurrentDNS.Count -ne $ExpectedDNS.Count) { return $false } for ($i = 0; $i -lt $CurrentDNS.Count; $i++) { if ($CurrentDNS[$i] -ne $ExpectedDNS[$i]) { return $false } } return $true } function Update-DNSServers { param( [string]$ServerName, [array]$DesiredDNSServers ) try { $Result = Invoke-Command -ComputerName $ServerName -ArgumentList (,$DesiredDNSServers) -ScriptBlock { param($DesiredDNS) $Results = @() # Get all network adapters with IP enabled (typically active adapters) $Adapters = Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 } foreach ($Adapter in $Adapters) { try { # Get current DNS to preserve $CurrentDNS = $Adapter.ServerAddresses # Merge: Desired DNS first, then existing DNS not in desired list $MergedDNS = @($DesiredDNS) $PreservedCount = 0 foreach ($DNS in $CurrentDNS) { if ($DNS -notin $DesiredDNS) { $MergedDNS += $DNS $PreservedCount++ } } # Set DNS servers in the merged order Set-DnsClientServerAddress -InterfaceIndex $Adapter.InterfaceIndex ` -ServerAddresses $MergedDNS ` -ErrorAction Stop $Results += [PSCustomObject]@{ InterfaceAlias = $Adapter.InterfaceAlias Success = $true Message = "DNS updated successfully (Preserved $PreservedCount existing DNS entries)" UpdatedDNS = $MergedDNS } } catch { $Results += [PSCustomObject]@{ InterfaceAlias = $Adapter.InterfaceAlias Success = $false Message = $_.Exception.Message UpdatedDNS = $null } } } return $Results } -ErrorAction Stop return $Result } catch { Write-Log "Failed to update DNS on $ServerName : $($_.Exception.Message)" -Level ERROR return $null } } # ============================================ # MAIN SCRIPT EXECUTION # ============================================ Write-Log "========================================" -Level INFO Write-Log "DNS Server Update Script Started" -Level INFO Write-Log "========================================" -Level INFO Write-Log "Desired DNS Servers (in order): $($DesiredDNSServers -join ', ')" -Level INFO Write-Log "NOTE: Existing DNS entries not in the above list will be preserved and moved to the end" -Level INFO # Check if server list file exists if (-not (Test-Path $ServerListFile)) { Write-Log "Server list file not found: $ServerListFile" -Level ERROR Write-Log "Please create '$ServerListFile' with one server hostname per line." -Level ERROR exit 1 } # Read server list $Servers = Get-Content $ServerListFile | Where-Object { $_.Trim() -ne "" } if ($Servers.Count -eq 0) { Write-Log "No servers found in $ServerListFile" -Level ERROR exit 1 } Write-Log "Found $($Servers.Count) server(s) to process" -Level INFO Write-Log "----------------------------------------" -Level INFO # Confirmation prompt Write-Host "`nThis script will update DNS settings on the following servers:" -ForegroundColor Cyan $Servers | ForEach-Object { Write-Host " - $_" -ForegroundColor Cyan } Write-Host "`nNew DNS servers will be added in this order (first positions):" -ForegroundColor Cyan $DesiredDNSServers | ForEach-Object { Write-Host " - $_" -ForegroundColor Cyan } Write-Host "`nIMPORTANT: Existing DNS entries will be PRESERVED and moved to the end of the list." -ForegroundColor Yellow $Confirmation = Read-Host "`nDo you want to proceed? (YES/NO)" if ($Confirmation -ne "YES") { Write-Log "Operation cancelled by user" -Level WARNING exit 0 } # Process each server $SuccessCount = 0 $FailureCount = 0 $SkippedCount = 0 foreach ($Server in $Servers) { Write-Log "----------------------------------------" -Level INFO Write-Log "Processing server: $Server" -Level INFO # Test connectivity Write-Log "Testing connectivity to $Server..." -Level INFO if (-not (Test-ServerConnectivity -ServerName $Server)) { Write-Log "Cannot connect to $Server - Skipping" -Level ERROR $SkippedCount++ continue } Write-Log "Connectivity test passed" -Level SUCCESS # Get current DNS settings Write-Log "Retrieving current DNS settings..." -Level INFO $CurrentDNS = Get-CurrentDNSSettings -ServerName $Server if ($null -eq $CurrentDNS) { Write-Log "Failed to retrieve DNS settings from $Server - Skipping" -Level ERROR $SkippedCount++ continue } # Display current DNS settings foreach ($Interface in $CurrentDNS) { Write-Log "Current DNS on '$($Interface.InterfaceAlias)': $($Interface.ServerAddresses -join ', ')" -Level INFO } # Calculate merged DNS settings (desired DNS first, then existing DNS not in desired list) Write-Log "Calculating merged DNS configuration..." -Level INFO $MergedDNSList = @{} foreach ($Interface in $CurrentDNS) { $MergedDNS = Merge-DNSSettings -CurrentDNS $Interface.ServerAddresses -DesiredDNS $DesiredDNSServers $MergedDNSList[$Interface.InterfaceAlias] = $MergedDNS Write-Log "Planned DNS for '$($Interface.InterfaceAlias)': $($MergedDNS -join ', ')" -Level INFO # Highlight preserved DNS entries $PreservedDNS = $Interface.ServerAddresses | Where-Object { $_ -notin $DesiredDNSServers } if ($PreservedDNS.Count -gt 0) { Write-Log " Preserving existing DNS (moved to end): $($PreservedDNS -join ', ')" -Level INFO } } # Backup current settings Write-Log "Backing up current DNS settings..." -Level INFO if (-not (Backup-DNSSettings -ServerName $Server -DNSSettings $CurrentDNS)) { Write-Log "Failed to backup settings for $Server - Skipping for safety" -Level ERROR $SkippedCount++ continue } Write-Log "Backup completed successfully" -Level SUCCESS # Check if DNS already matches desired configuration $NeedsUpdate = $false foreach ($Interface in $CurrentDNS) { $ExpectedDNS = $MergedDNSList[$Interface.InterfaceAlias] if (-not (Compare-DNSSettings -CurrentDNS $Interface.ServerAddresses -ExpectedDNS $ExpectedDNS)) { $NeedsUpdate = $true break } } if (-not $NeedsUpdate) { Write-Log "DNS settings on $Server already match desired configuration - No changes needed" -Level SUCCESS $SuccessCount++ continue } # Update DNS servers with merged list Write-Log "Updating DNS servers..." -Level INFO $UpdateResult = Invoke-Command -ComputerName $Server -ArgumentList (,$DesiredDNSServers) -ScriptBlock { param($DesiredDNS) $Results = @() # Get all network adapters with IP enabled (typically active adapters) $Adapters = Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 } foreach ($Adapter in $Adapters) { try { # Get current DNS to preserve $CurrentDNS = $Adapter.ServerAddresses # Merge: Desired DNS first, then existing DNS not in desired list $MergedDNS = @($DesiredDNS) foreach ($DNS in $CurrentDNS) { if ($DNS -notin $DesiredDNS) { $MergedDNS += $DNS } } # Set DNS servers in the merged order Set-DnsClientServerAddress -InterfaceIndex $Adapter.InterfaceIndex ` -ServerAddresses $MergedDNS ` -ErrorAction Stop $Results += [PSCustomObject]@{ InterfaceAlias = $Adapter.InterfaceAlias Success = $true Message = "DNS updated successfully (Preserved: $($CurrentDNS | Where-Object { $_ -notin $DesiredDNS } | Measure-Object | Select-Object -ExpandProperty Count) existing)" UpdatedDNS = $MergedDNS } } catch { $Results += [PSCustomObject]@{ InterfaceAlias = $Adapter.InterfaceAlias Success = $false Message = $_.Exception.Message UpdatedDNS = $null } } } return $Results } -ErrorAction Stop if ($null -eq $UpdateResult) { Write-Log "DNS update failed for $Server" -Level ERROR $FailureCount++ continue } # Process results $AllSuccess = $true foreach ($Result in $UpdateResult) { if ($Result.Success) { Write-Log "Interface '$($Result.InterfaceAlias)': $($Result.Message)" -Level SUCCESS } else { Write-Log "Interface '$($Result.InterfaceAlias)': $($Result.Message)" -Level ERROR $AllSuccess = $false } } # Verify the update Start-Sleep -Seconds 2 $VerifyDNS = Get-CurrentDNSSettings -ServerName $Server if ($null -ne $VerifyDNS) { Write-Log "Verification - New DNS settings:" -Level INFO foreach ($Interface in $VerifyDNS) { Write-Log " '$($Interface.InterfaceAlias)': $($Interface.ServerAddresses -join ', ')" -Level INFO } } if ($AllSuccess) { Write-Log "DNS update completed successfully for $Server" -Level SUCCESS $SuccessCount++ } else { Write-Log "DNS update completed with errors for $Server" -Level WARNING $FailureCount++ } } # Summary Write-Log "========================================" -Level INFO Write-Log "DNS Update Script Completed" -Level INFO Write-Log "========================================" -Level INFO Write-Log "Total Servers: $($Servers.Count)" -Level INFO Write-Log "Successful: $SuccessCount" -Level SUCCESS Write-Log "Failed: $FailureCount" -Level ERROR Write-Log "Skipped: $SkippedCount" -Level WARNING Write-Log "Log file: $LogFile" -Level INFO Write-Log "Backup file: $BackupFile" -Level INFO Write-Log "========================================" -Level INFO