Skip to content

Instantly share code, notes, and snippets.

@davidlu1001
Last active March 20, 2025 20:05
Show Gist options
  • Select an option

  • Save davidlu1001/60f2cfd0eb954f9d1ec06d7d00bac0db to your computer and use it in GitHub Desktop.

Select an option

Save davidlu1001/60f2cfd0eb954f9d1ec06d7d00bac0db to your computer and use it in GitHub Desktop.
Manage DNS Failover
[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