Last active
March 20, 2025 20:05
-
-
Save davidlu1001/60f2cfd0eb954f9d1ec06d7d00bac0db to your computer and use it in GitHub Desktop.
Manage DNS Failover
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory=$false)] | |
| [ValidateSet("Dev", "Prod")] | |
| [string]$Env = "Dev", | |
| [Parameter(Mandatory=$false)] | |
| [string]$dnsServer, | |
| [Parameter(Mandatory=$false)] | |
| [string]$lookupZone, | |
| [Parameter(Mandatory=$false)] | |
| [ValidateSet("check", "failover")] | |
| [string]$Ops = "check", | |
| [Parameter(Mandatory=$false)] | |
| [string]$iisAppPoolPattern = "LoanAlterations*", | |
| [Parameter(Mandatory=$false)] | |
| [string]$logFilePath = "C:\temp\scripts\DNSFailover.log", | |
| [Parameter(Mandatory=$false)] | |
| [int]$iisRetryAttempts = 3, | |
| [Parameter(Mandatory=$false)] | |
| [int]$iisRetryWaitTime = 60, | |
| [Parameter(Mandatory=$false)] | |
| [switch]$Help | |
| ) | |
| # Function to show help information | |
| function Show-Help { | |
| $helpText = @" | |
| DNS Failover Script Help | |
| ======================= | |
| Description: | |
| This script manages DNS failover operations between environments with IIS application pool management. | |
| Syntax: | |
| .\dnsFailover.ps1 [-Env <String>] [-dnsServer <String>] [-lookupZone <String>] | |
| [-Ops <String>] [-iisAppPoolPattern <String>] [-logFilePath <String>] | |
| [-iisRetryAttempts <Int>] [-iisRetryWaitTime <Int>] [-Help] | |
| Parameters: | |
| -Env <String> | |
| Specifies the environment to operate in. | |
| Valid values: Dev, Prod | |
| Default: Dev | |
| Dev Environment Settings: | |
| - DNS Name: LendingWebServerDev | |
| - TTL: 1 minute | |
| - Initial Wait: 30 seconds | |
| - Check Interval: 10 seconds | |
| Prod Environment Settings: | |
| - DNS Name: LendingWebServer | |
| - TTL: 10 minutes | |
| - Initial Wait: 300 seconds | |
| - Check Interval: 60 seconds | |
| -dnsServer <String> | |
| Specifies the DNS server to use for operations. | |
| -lookupZone <String> | |
| Specifies the DNS lookup zone. | |
| -Ops <String> | |
| Specifies the operation to perform. | |
| Valid values: check, failover | |
| Default: check | |
| -iisAppPoolPattern <String> | |
| Pattern to match IIS application pools. | |
| Default: LoanAlterations* | |
| -logFilePath <String> | |
| Path to the log file. | |
| Default: C:\temp\scripts\DNSFailover.log | |
| -iisRetryAttempts <Int> | |
| Number of retry attempts for IIS operations. | |
| Default: 3 | |
| -iisRetryWaitTime <Int> | |
| Wait time between retry attempts in seconds. | |
| Default: 60 | |
| -Help [Switch] | |
| Shows this help message. | |
| Examples: | |
| # Show help | |
| .\dnsFailover.ps1 -Help | |
| # Check DNS in Dev environment | |
| .\dnsFailover.ps1 -Env Dev -Ops check | |
| # Perform failover in Prod environment | |
| .\dnsFailover.ps1 -Env Prod -Ops failover -dnsServer "dns1.company.com" -lookupZone "company.local" | |
| # Perform failover with custom IIS settings | |
| .\dnsFailover.ps1 -Env Prod -Ops failover -iisAppPoolPattern "WebApp*" -iisRetryAttempts 5 -iisRetryWaitTime 30 | |
| Notes: | |
| - The script requires the DnsServer PowerShell module | |
| - Appropriate permissions are required for DNS and IIS operations | |
| - All operations are logged to the specified log file | |
| "@ | |
| Write-Host $helpText | |
| exit 0 | |
| } | |
| # Show help if requested | |
| if ($Help) { | |
| Show-Help | |
| } | |
| # Environment-specific configurations using PowerShell 5.1 compatible syntax | |
| $envConfig = @{} | |
| # Dev environment configuration | |
| $devConfig = @{ | |
| dnsName = "LendingWebServerDev" | |
| TTL = [System.TimeSpan]::FromMinutes(1) | |
| waitTimeInit = 30 | |
| waitTime = 10 | |
| maxWaitTime = 300 # 5 minutes for Dev | |
| } | |
| $envConfig.Add("Dev", $devConfig) | |
| # Prod environment configuration | |
| $prodConfig = @{ | |
| dnsName = "LendingWebServer" | |
| TTL = [System.TimeSpan]::FromMinutes(10) | |
| waitTimeInit = 300 | |
| waitTime = 60 | |
| maxWaitTime = 1800 # 30 minutes for Prod | |
| } | |
| $envConfig.Add("Prod", $prodConfig) | |
| # Validate environment selection | |
| if (-not $envConfig.ContainsKey($Env)) { | |
| Write-Error "Invalid environment specified: $Env. Valid values are: Dev, Prod" | |
| exit 1 | |
| } | |
| # Apply environment-specific configurations | |
| $config = $envConfig[$Env] | |
| $dnsName = $config.dnsName | |
| $TTL = $config.TTL | |
| $waitTimeInit = $config.waitTimeInit | |
| $waitTime = $config.waitTime | |
| $maxWaitTime = $config.maxWaitTime | |
| # Enable strict mode for better error handling | |
| Set-StrictMode -Version Latest | |
| # Set error action preference to stop script execution on error | |
| $ErrorActionPreference = 'Stop' | |
| # Import required modules | |
| try { | |
| Import-Module DnsServer -ErrorAction Stop | |
| } catch { | |
| Write-Error "Failed to import DnsServer module. Please ensure it's installed. Error: $_" | |
| exit 1 | |
| } | |
| # Function to write log messages | |
| function Write-Log { | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$Message, | |
| [Parameter(Mandatory=$false)] | |
| [ValidateSet("INFO", "WARNING", "ERROR")] | |
| [string]$Level = "INFO" | |
| ) | |
| $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| $logMessage = "[$timestamp] [$Level] $Message" | |
| # Write to console with appropriate color | |
| switch ($Level) { | |
| "INFO" { Write-Host $logMessage -ForegroundColor Green } | |
| "WARNING" { Write-Host $logMessage -ForegroundColor Yellow } | |
| "ERROR" { Write-Host $logMessage -ForegroundColor Red } | |
| } | |
| # Append to log file | |
| try { | |
| Add-Content -Path $logFilePath -Value $logMessage -ErrorAction Stop | |
| } catch { | |
| Write-Warning "Failed to write to log file: $_" | |
| } | |
| } | |
| # Function to get the active host | |
| function Get-ActiveHost { | |
| Write-Log "Attempting to get active host for $dnsName" | |
| try { | |
| $ping = Test-Connection -ComputerName $dnsName -Count 1 -ErrorAction SilentlyContinue | |
| if ($ping) { | |
| $hostEntry = [System.Net.Dns]::GetHostEntry($ping.Address) | |
| $activeHost = $hostEntry.HostName | |
| Write-Log "Active host is reachable: $activeHost" | |
| } else { | |
| # Even if ping fails, we might still get the hostname | |
| $pingOutput = Test-Connection -ComputerName $dnsName -Count 1 -ErrorAction SilentlyContinue | Out-String | |
| if ($pingOutput -match "Pinging\s+(\S+)\s+") { | |
| $activeHost = $Matches[1] | |
| Write-Log "Host is unreachable, but hostname obtained: $activeHost" -Level "WARNING" | |
| } else { | |
| throw "Unable to obtain hostname for $dnsName" | |
| } | |
| } | |
| return $activeHost | |
| } catch { | |
| Write-Log "Failed to resolve active host for $dnsName. Error: $_" -Level "ERROR" | |
| return $null | |
| } | |
| } | |
| # Function to get current CNAME data | |
| function Get-CurrentCnameData { | |
| Write-Log "Retrieving current CNAME data for $dnsName" | |
| try { | |
| $dnsRecord = Get-DnsServerResourceRecord -Name $dnsName -RRType CName -ZoneName $lookupZone -ComputerName $dnsServer -ErrorAction Stop | |
| $currentCname = $dnsRecord.RecordData.HostNameAlias | |
| Write-Log "Current CNAME: $currentCname" | |
| return $currentCname | |
| } catch { | |
| Write-Log "Failed to get DNS record for $dnsName. Error: $_" -Level "ERROR" | |
| return $null | |
| } | |
| } | |
| # Function to update CNAME record | |
| function Update-Cname { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$currentData | |
| ) | |
| Write-Log "Updating CNAME record" | |
| $newData = switch -Regex ($currentData) { | |
| 'Prod' { $currentData -replace 'Prod', 'DR' } | |
| 'DR' { $currentData -replace 'DR', 'Prod' } | |
| default { | |
| Write-Log "No matching 'Prod' or 'DR' found in $currentData" -Level "WARNING" | |
| return $false | |
| } | |
| } | |
| if ($newData) { | |
| try { | |
| Remove-DnsServerResourceRecord -ZoneName $lookupZone -Name $dnsName -RRType CName -ComputerName $dnsServer -Force -ErrorAction Stop | |
| Add-DnsServerResourceRecordCName -Name $dnsName -HostNameAlias $newData -ZoneName $lookupZone -ComputerName $dnsServer -TimeToLive $TTL -ErrorAction Stop | |
| Write-Log "DNS CNAME updated to $newData" | |
| return $true | |
| } catch { | |
| Write-Log "Failed to update DNS CNAME. Error: $_" -Level "ERROR" | |
| return $false | |
| } | |
| } | |
| } | |
| # Function to verify failover | |
| function Verify-Failover { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$initialHost | |
| ) | |
| Write-Log "Starting failover verification" | |
| $elapsedTime = 0 | |
| Start-Sleep -Seconds $waitTimeInit | |
| do { | |
| Start-Sleep -Seconds $waitTime | |
| $elapsedTime += $waitTime | |
| $newHost = Get-ActiveHost | |
| if ($newHost -and ($newHost -ne $initialHost)) { | |
| Write-Log "DNS failover succeeded, new host is $newHost" | |
| return $newHost | |
| } | |
| Write-Log "Failover not complete, waiting... ($elapsedTime seconds elapsed)" | |
| } while ($elapsedTime -lt $maxWaitTime) | |
| Write-Log "DNS failover failed or is still pending propagation after $maxWaitTime seconds" -Level "WARNING" | |
| return $null | |
| } | |
| # Function to manage IIS application pools remotely | |
| function Invoke-RemoteIISManagement { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$computerName, | |
| [Parameter(Mandatory=$true)] | |
| [hashtable]$params | |
| ) | |
| $remoteScript = { | |
| param($p) | |
| # Ensure WebAdministration module is available and loaded | |
| if (-not (Get-Module -ListAvailable -Name WebAdministration)) { | |
| return @{ | |
| Success = $false | |
| ErrorMessage = "WebAdministration module is not available on the remote server. Please ensure IIS is installed." | |
| Results = $null | |
| } | |
| } | |
| Import-Module WebAdministration -ErrorAction Stop | |
| $appPools = Get-ChildItem IIS:\AppPools | Where-Object { $_.Name -like $p.pattern } | |
| if (-not $appPools) { | |
| return @{ | |
| Success = $false | |
| ErrorMessage = "No application pools matching the pattern '$($p.pattern)' were found." | |
| Results = $null | |
| } | |
| } | |
| $results = @() | |
| foreach ($appPool in $appPools) { | |
| $result = @{ | |
| AppPoolName = $appPool.Name | |
| InitialState = $appPool.State | |
| RestartStatus = "Not Attempted" | |
| RecycleStatus = "Not Attempted" | |
| FinalState = $appPool.State | |
| } | |
| if ($p.isRestart) { | |
| $restartSuccess = $false | |
| for ($attempt = 1; $attempt -le $p.retryAttempts; $attempt++) { | |
| try { | |
| Write-Output "Attempt $attempt: Restarting Application Pool: $($appPool.Name)" | |
| if ($appPool.State -eq "Started") { | |
| Stop-WebAppPool -Name $appPool.Name -ErrorAction Stop | |
| } | |
| Start-WebAppPool -Name $appPool.Name -ErrorAction Stop | |
| # Wait for the app pool to start | |
| $waited = 0 | |
| do { | |
| Start-Sleep -Seconds 1 | |
| $waited++ | |
| $appPool = Get-Item "IIS:\AppPools\$($appPool.Name)" | |
| } while (($appPool.State -ne "Started") -and ($waited -lt $p.maxWait)) | |
| if ($appPool.State -eq "Started") { | |
| $result.RestartStatus = "Success" | |
| $restartSuccess = $true | |
| Write-Output "Application Pool restarted successfully: $($appPool.Name)" | |
| break | |
| } else { | |
| throw "Application Pool failed to start within $($p.maxWait) seconds" | |
| } | |
| } catch { | |
| $result.RestartStatus = "Failed: $_" | |
| Write-Warning "Failed to restart Application Pool: $($appPool.Name). Error: $_" | |
| if ($attempt -lt $p.retryAttempts) { | |
| Write-Output "Waiting $($p.retryWaitTime) seconds before next attempt..." | |
| Start-Sleep -Seconds $p.retryWaitTime | |
| } | |
| } | |
| } | |
| if (-not $restartSuccess) { | |
| Write-Error "Failed to restart Application Pool after $($p.retryAttempts) attempts: $($appPool.Name)" | |
| # Skip recycling if restart failed | |
| $p.isRecycle = $false | |
| } | |
| } | |
| if ($p.isRecycle) { | |
| try { | |
| Write-Output "Recycling Application Pool: $($appPool.Name)" | |
| $appPool.Recycle() | |
| # Wait for recycle to complete | |
| Start-Sleep -Seconds 5 | |
| $appPool = Get-Item "IIS:\AppPools\$($appPool.Name)" | |
| $result.RecycleStatus = "Success" | |
| Write-Output "Application Pool recycled successfully: $($appPool.Name)" | |
| } catch { | |
| $result.RecycleStatus = "Failed: $_" | |
| Write-Error "Failed to recycle Application Pool: $($appPool.Name). Error: $_" | |
| } | |
| } | |
| $appPool = Get-Item "IIS:\AppPools\$($appPool.Name)" | |
| $result.FinalState = $appPool.State | |
| $results += [PSCustomObject]$result | |
| } | |
| return @{ | |
| Success = $true | |
| ErrorMessage = $null | |
| Results = $results | |
| } | |
| } | |
| try { | |
| $$remoteResult = Invoke-Command -ComputerName $computerName -ScriptBlock $remoteScript -ArgumentList $params | |
| return $remoteResult | |
| } catch { | |
| Write-Log "Failed to execute IIS management on remote server $computerName. Error: $_" -Level "ERROR" | |
| return @{ | |
| Success = $false | |
| ErrorMessage = "Failed to execute remote command: $_" | |
| Results = $null | |
| } | |
| } | |
| } | |
| # Main Execution Block | |
| try { | |
| Write-Log "Script started with operation: $Ops in $Env environment" | |
| Write-Log ("Using configuration: " + ` | |
| "DNS Name: $dnsName, " + ` | |
| "TTL: $($TTL.TotalMinutes) minutes, " + ` | |
| "Initial Wait: $waitTimeInit seconds, " + ` | |
| "Check Interval: $waitTime seconds") | |
| switch ($Ops) { | |
| "check" { | |
| $activeHost = Get-ActiveHost | |
| if ($activeHost) { | |
| Write-Log "Active host check completed: $activeHost" | |
| } else { | |
| Write-Log "No active host found" -Level "WARNING" | |
| } | |
| } | |
| "failover" { | |
| Write-Log "Starting failover process in $Env environment" | |
| $initialHost = Get-ActiveHost | |
| if ($initialHost) { | |
| $currentData = Get-CurrentCnameData | |
| if ($currentData) { | |
| if (Update-Cname $currentData) { | |
| $newHost = Verify-Failover $initialHost | |
| if ($newHost) { | |
| Write-Log "Failover successful. New active host: $newHost" | |
| Write-Log "Proceeding with IIS Application Pool management on $newHost" | |
| $iisParams = @{ | |
| pattern = $iisAppPoolPattern | |
| isRestart = $true | |
| isRecycle = $true | |
| maxWait = 120 | |
| retryAttempts = $iisRetryAttempts | |
| retryWaitTime = $iisRetryWaitTime | |
| } | |
| $iisResults = Invoke-RemoteIISManagement -computerName $newHost -params $iisParams | |
| if ($null -ne $iisResults -and $iisResults.Success -eq $true -and $null -ne $iisResults.Results) { | |
| $successCount = 0 | |
| $failCount = 0 | |
| foreach ($result in $iisResults.Results) { | |
| $restartStatus = $result.RestartStatus | |
| $recycleStatus = $result.RecycleStatus | |
| $appPoolName = $result.AppPoolName | |
| $initialState = $result.InitialState | |
| $finalState = $result.FinalState | |
| Write-Log "App Pool: $appPoolName`nInitial State: $initialState`nRestart: $restartStatus`nRecycle: $recycleStatus`nFinal State: $finalState" | |
| if ($restartStatus -eq "Success" -and $recycleStatus -eq "Success") { | |
| $successCount++ | |
| } else { | |
| $failCount++ | |
| } | |
| } | |
| Write-Log "IIS Application Pool management completed. Success: $successCount, Failed: $failCount" | |
| if ($failCount -eq 0 -and $successCount -gt 0) { | |
| Write-Log "All IIS Application Pool operations completed successfully." | |
| } elseif ($failCount -gt 0) { | |
| Write-Log "Some IIS Application Pool operations failed. Please check the logs for details." -Level "WARNING" | |
| } else { | |
| Write-Log "No successful IIS Application Pool operations were recorded." -Level "WARNING" | |
| } | |
| } else { | |
| Write-Log "No valid results returned from IIS Application Pool management." -Level "WARNING" | |
| if ($iisResults.ErrorMessage) { | |
| Write-Log "Error message: $($iisResults.ErrorMessage)" -Level "ERROR" | |
| } | |
| } | |
| } else { | |
| Write-Log "Failover process completed, but verification failed" -Level "ERROR" | |
| } | |
| } else { | |
| Write-Log "Failed to update CNAME, aborting failover" -Level "ERROR" | |
| } | |
| } else { | |
| Write-Log "Failed to get current CNAME data, aborting failover" -Level "ERROR" | |
| } | |
| } else { | |
| Write-Log "Failed to get initial active host, aborting failover" -Level "ERROR" | |
| } | |
| } | |
| default { | |
| Write-Log "Invalid operation specified: $Ops" -Level "ERROR" | |
| } | |
| } | |
| } catch { | |
| Write-Log "An unexpected error occurred: $_" -Level "ERROR" | |
| Write-Log "Use -Help parameter for usage information" -Level "WARNING" | |
| exit 1 | |
| } finally { | |
| Write-Log "Script execution completed for $Env environment." | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment