Files
linux-scripts/windows-dns-smoke-tests.ps1
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

428 lines
17 KiB
PowerShell

###############################################################################
# windows-dns-smoke-tests.ps1 - Verify Windows DNS Server health
#
# Checks DNS service status, zone loading, AD-integrated zone replication,
# conditional forwarders, forward and reverse resolution, scavenging
# configuration, root hints, forwarder connectivity, zone delegation,
# cache statistics, and event log errors.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# License: MIT
# Version 1.0
#
# Usage:
# .\windows-dns-smoke-tests.ps1
# .\windows-dns-smoke-tests.ps1 -DnsServer 10.0.0.53 -Domain corp.local
# .\windows-dns-smoke-tests.ps1 -OutputFormat tap
# .\windows-dns-smoke-tests.ps1 -Help
###############################################################################
[CmdletBinding()]
param(
[string]$DnsServer = "localhost",
[string]$Domain = $(if ($env:USERDNSDOMAIN) { $env:USERDNSDOMAIN } else { "" }),
[string]$TestDomain = "example.com",
[ValidateSet("text","tap")]
[string]$OutputFormat = "text",
[switch]$NoColor,
[switch]$Help
)
$ErrorActionPreference = "Continue"
$script:Pass = 0; $script:Fail = 0; $script:Skip = 0; $script:Total = 0
$script:Results = @(); $script:StartTime = $null
# ============================================================================
# COLORS
# ============================================================================
function Write-Color {
param([string]$Text, [string]$Color = "White")
if ($NoColor) { Write-Host $Text } else { Write-Host $Text -ForegroundColor $Color }
}
# ============================================================================
# TEST RESULT RECORDING
# ============================================================================
function Record-Pass {
param([string]$Name, [string]$Detail = "")
$script:Pass++; $script:Total++
$script:Results += [PSCustomObject]@{ Status="PASS"; Name=$Name; Detail=$Detail }
if ($OutputFormat -eq "tap") {
Write-Host "ok $($script:Total) - $Name"
} else {
$msg = " $(if($NoColor){'[PASS]'}else{[char]0x2713}) $Name"
if ($Detail) { $msg += " - $Detail" }
Write-Color $msg "Green"
}
}
function Record-Fail {
param([string]$Name, [string]$Detail = "")
$script:Fail++; $script:Total++
$script:Results += [PSCustomObject]@{ Status="FAIL"; Name=$Name; Detail=$Detail }
if ($OutputFormat -eq "tap") {
Write-Host "not ok $($script:Total) - $Name"
if ($Detail) { Write-Host " # $Detail" }
} else {
$msg = " $(if($NoColor){'[FAIL]'}else{[char]0x2717}) $Name"
if ($Detail) { $msg += " - $Detail" }
Write-Color $msg "Red"
}
}
function Record-Skip {
param([string]$Name, [string]$Reason = "")
$script:Skip++; $script:Total++
$script:Results += [PSCustomObject]@{ Status="SKIP"; Name=$Name; Detail=$Reason }
if ($OutputFormat -eq "tap") {
Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
} else {
$msg = " $(if($NoColor){'[SKIP]'}else{[char]0x2298}) $Name"
if ($Reason) { $msg += " - $Reason" }
Write-Color $msg "Yellow"
}
}
# ============================================================================
# HELPERS
# ============================================================================
function Get-DnsComputerParam {
if ($DnsServer -eq "localhost" -or $DnsServer -eq "127.0.0.1" -or $DnsServer -eq "::1") {
return @{}
}
return @{ ComputerName = $DnsServer }
}
function Write-Section {
param([string]$Title)
if ($OutputFormat -ne "tap") { Write-Host ""; Write-Color $Title "White" }
}
# ============================================================================
# TESTS
# ============================================================================
function Test-DnsService {
Write-Section "DNS Service"
try {
$params = @{ Name = "DNS"; ErrorAction = "Stop" }
if ($DnsServer -ne "localhost" -and $DnsServer -ne "127.0.0.1" -and $DnsServer -ne "::1") {
$params["ComputerName"] = $DnsServer
}
$svc = Get-Service @params
if ($svc.Status -eq "Running") { Record-Pass "DNS service running" }
else { Record-Fail "DNS service running" "status: $($svc.Status)" }
} catch { Record-Fail "DNS service running" $_.Exception.Message }
}
function Test-DnsResponds {
try {
$result = Resolve-DnsName -Name $TestDomain -Server $DnsServer -Type A -DnsOnly -ErrorAction Stop
if ($result) { Record-Pass "DNS server responds" "$TestDomain resolved" }
else { Record-Fail "DNS server responds" "no result for $TestDomain" }
} catch { Record-Fail "DNS server responds" $_.Exception.Message }
}
function Test-ZonesLoaded {
$cp = Get-DnsComputerParam
try {
$zones = @(Get-DnsServerZone @cp -ErrorAction Stop |
Where-Object { $_.ZoneType -ne "Forwarder" -and $_.ZoneName -ne "TrustAnchors" })
if ($zones.Count -gt 0) { Record-Pass "zones loaded" "$($zones.Count) zones" }
else { Record-Fail "zones loaded" "no zones found" }
} catch { Record-Fail "zones loaded" $_.Exception.Message }
}
function Test-ADIntegratedZones {
if (-not $Domain) { Record-Skip "AD-integrated zones" "Domain not set"; return }
$cp = Get-DnsComputerParam
try {
$zones = @(Get-DnsServerZone @cp -ErrorAction Stop |
Where-Object { $_.IsAutoCreated -eq $false -and $_.IsDsIntegrated -eq $true })
if ($zones.Count -eq 0) { Record-Skip "AD-integrated zones" "none found"; return }
foreach ($z in $zones) {
$scope = if ($z.DirectoryPartitionName) { $z.DirectoryPartitionName } else { "legacy" }
Record-Pass "AD-integrated zone ($($z.ZoneName))" "replication: $scope"
}
} catch { Record-Fail "AD-integrated zones" $_.Exception.Message }
}
function Test-ForwardLookup {
Write-Section "DNS Resolution"
if (-not $Domain) { Record-Skip "forward lookup" "Domain not set"; return }
try {
$result = Resolve-DnsName -Name $Domain -Server $DnsServer -Type A -DnsOnly -ErrorAction Stop
if ($result) {
$addrs = ($result | Where-Object { $_.QueryType -eq "A" } |
Select-Object -ExpandProperty IPAddress) -join ", "
if ($addrs) { Record-Pass "forward lookup ($Domain)" $addrs }
else { Record-Pass "forward lookup ($Domain)" "resolved (non-A records)" }
} else { Record-Fail "forward lookup ($Domain)" "no result" }
} catch { Record-Fail "forward lookup ($Domain)" $_.Exception.Message }
}
function Test-ReverseLookup {
$cp = Get-DnsComputerParam
try {
$rz = @(Get-DnsServerZone @cp -ErrorAction Stop |
Where-Object { $_.IsReverseLookupZone -eq $true -and $_.ZoneType -ne "Forwarder" })
if ($rz.Count -eq 0) { Record-Skip "reverse lookup" "no reverse zones found"; return }
$found = $false
foreach ($zone in $rz) {
try {
$ptrs = Get-DnsServerResourceRecord -ZoneName $zone.ZoneName -RRType PTR @cp -ErrorAction Stop
if ($ptrs) {
$first = $ptrs | Select-Object -First 1
Record-Pass "reverse lookup" "PTR $($first.HostName) -> $($first.RecordData.PtrDomainName)"
$found = $true; break
}
} catch { }
}
if (-not $found) { Record-Skip "reverse lookup" "no PTR records in reverse zones" }
} catch { Record-Fail "reverse lookup" $_.Exception.Message }
}
function Test-ConditionalForwarders {
Write-Section "Forwarders"
$cp = Get-DnsComputerParam
try {
$fzones = @(Get-DnsServerZone @cp -ErrorAction Stop |
Where-Object { $_.ZoneType -eq "Forwarder" })
if ($fzones.Count -eq 0) { Record-Skip "conditional forwarders" "none configured"; return }
foreach ($fz in $fzones) {
$targets = $fz.MasterServers
if (-not $targets -or @($targets).Count -eq 0) {
Record-Fail "conditional forwarder ($($fz.ZoneName))" "no targets"; continue
}
$ok = $false
foreach ($t in $targets) {
try {
$r = Resolve-DnsName -Name $fz.ZoneName -Server $t.IPAddressToString -Type SOA -DnsOnly -ErrorAction Stop
if ($r) { $ok = $true; break }
} catch { }
}
if ($ok) { Record-Pass "conditional forwarder ($($fz.ZoneName))" "target reachable" }
else { Record-Fail "conditional forwarder ($($fz.ZoneName))" "no targets responded" }
}
} catch { Record-Fail "conditional forwarders" $_.Exception.Message }
}
function Test-RootHints {
$cp = Get-DnsComputerParam
try {
$hints = @(Get-DnsServerRootHint @cp -ErrorAction Stop)
if ($hints.Count -eq 0) { Record-Skip "root hints" "none configured"; return }
Record-Pass "root hints configured" "$($hints.Count) root servers"
$tested = $false
foreach ($h in $hints) {
foreach ($ip in ($h.IPAddress | Where-Object { $_.RecordType -eq "A" })) {
try {
$addr = $ip.RecordData.IPv4Address.IPAddressToString
$r = Resolve-DnsName -Name "." -Server $addr -Type NS -DnsOnly -ErrorAction Stop
if ($r) { Record-Pass "root hint reachable" $addr; $tested = $true; break }
} catch { }
}
if ($tested) { break }
}
if (-not $tested) { Record-Fail "root hint reachable" "no root servers responded" }
} catch { Record-Fail "root hints" $_.Exception.Message }
}
function Test-Forwarders {
$cp = Get-DnsComputerParam
try {
$fwd = Get-DnsServerForwarder @cp -ErrorAction Stop
$ips = $fwd.IPAddress
if (-not $ips -or @($ips).Count -eq 0) {
Record-Skip "forwarder configuration" "no forwarders configured"; return
}
foreach ($ip in $ips) {
$addr = $ip.IPAddressToString
try {
$r = Resolve-DnsName -Name $TestDomain -Server $addr -Type A -DnsOnly -ErrorAction Stop
if ($r) { Record-Pass "forwarder responds ($addr)" }
else { Record-Fail "forwarder responds ($addr)" "no response" }
} catch { Record-Fail "forwarder responds ($addr)" $_.Exception.Message }
}
} catch { Record-Fail "forwarder configuration" $_.Exception.Message }
}
function Test-Scavenging {
Write-Section "Server Configuration"
$cp = Get-DnsComputerParam
try {
$scav = Get-DnsServerScavenging @cp -ErrorAction Stop
if ($scav.ScavengingState -eq $true) {
$detail = "interval: $($scav.ScavengingInterval)"
$last = $scav.LastScavengeTime
if ($last -and $last -ne [DateTime]::MinValue) {
$days = [math]::Floor(((Get-Date) - $last).TotalDays)
$detail += ", last run: $($last.ToString('yyyy-MM-dd')) (${days}d ago)"
if ($days -gt 14) { Record-Fail "scavenging" "$detail — over 14 days ago"; return }
} else { $detail += ", last run: never" }
Record-Pass "scavenging enabled" $detail
} else {
Record-Pass "scavenging disabled" "not enabled on this server"
}
} catch { Record-Fail "scavenging" $_.Exception.Message }
}
function Test-ZoneDelegation {
if (-not $Domain) { Record-Skip "zone delegation" "Domain not set"; return }
$cp = Get-DnsComputerParam
try {
$ns = @(Get-DnsServerResourceRecord -ZoneName $Domain -RRType NS @cp -ErrorAction Stop |
Where-Object { $_.HostName -ne "@" })
if ($ns.Count -eq 0) { Record-Skip "zone delegation" "no delegated subdomains"; return }
foreach ($r in $ns) {
Record-Pass "zone delegation ($($r.HostName))" "NS -> $($r.RecordData.NameServer)"
}
} catch {
if ($_.Exception.Message -match "not found") {
Record-Skip "zone delegation" "zone $Domain not hosted on this server"
} else { Record-Fail "zone delegation" $_.Exception.Message }
}
}
function Test-CacheStats {
Write-Section "Statistics"
$cp = Get-DnsComputerParam
try {
$stats = Get-DnsServerStatistics @cp -ErrorAction Stop
$cs = $stats.CacheStatistics
if ($cs) {
$hits = $cs.CacheHits; $misses = $cs.CacheMisses; $total = $hits + $misses
if ($total -gt 0) {
$ratio = [math]::Round(($hits / $total) * 100, 1)
Record-Pass "cache statistics" "hits: $hits, misses: $misses, hit ratio: ${ratio}%"
} else { Record-Pass "cache statistics" "no queries processed yet" }
} elseif ($stats.QueryStatistics) {
Record-Pass "cache statistics" "total queries: $($stats.QueryStatistics.TotalQueries)"
} else { Record-Skip "cache statistics" "not available" }
} catch { Record-Fail "cache statistics" $_.Exception.Message }
}
function Test-EventLog {
$cp = Get-DnsComputerParam
try {
$filter = @{ LogName = "DNS Server"; Level = 2; StartTime = (Get-Date).AddHours(-24) }
if ($cp.ContainsKey("ComputerName")) {
$events = @(Get-WinEvent -FilterHashtable $filter -ComputerName $cp.ComputerName -ErrorAction Stop)
} else {
$events = @(Get-WinEvent -FilterHashtable $filter -ErrorAction Stop)
}
if ($events.Count -gt 0) {
$latest = $events[0]
$msg = $latest.Message.Substring(0, [Math]::Min(80, $latest.Message.Length))
Record-Fail "event log" "$($events.Count) error(s) in last 24h — latest: $msg"
} else { Record-Pass "event log clean" "0 errors in last 24h" }
} catch {
if ($_.Exception.Message -match "No events were found") {
Record-Pass "event log clean" "0 errors in last 24h"
} elseif ($_.Exception.Message -match "not found|does not exist") {
Record-Skip "event log" "DNS Server event log not available"
} else { Record-Fail "event log" $_.Exception.Message }
}
}
# ============================================================================
# OUTPUT
# ============================================================================
function Write-Summary {
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
Write-Host ""
$sep = [string]::new([char]0x2500, 40)
Write-Color $sep "White"
Write-Color "Summary $DnsServer" "White"
Write-Host " $($script:Pass) passed $($script:Fail) failed $($script:Skip) skipped (${duration}s)"
Write-Color $sep "White"
if ($script:Fail -eq 0) { Write-Color "All tests passed." "Green" }
else { Write-Color "$($script:Fail) test(s) failed." "Red" }
}
function Write-TapHeader { Write-Host "TAP version 13" }
function Write-TapFooter {
Write-Host "1..$($script:Total)"
Write-Host "# pass $($script:Pass)"
Write-Host "# fail $($script:Fail)"
Write-Host "# skip $($script:Skip)"
}
# ============================================================================
# HELP
# ============================================================================
function Show-Usage {
@"
Usage: .\windows-dns-smoke-tests.ps1 [OPTIONS]
Smoke-test a Windows DNS Server. PowerShell 5.1+, DnsServer module only.
Parameters:
-DnsServer SERVER DNS server to test (default: localhost)
-Domain DOMAIN AD domain name (default: auto-detect from environment)
-TestDomain DOMAIN Domain for resolution tests (default: example.com)
-OutputFormat FMT Output format: text (default), tap
-NoColor Disable colored output
-Help Show this help message
Examples:
.\windows-dns-smoke-tests.ps1
.\windows-dns-smoke-tests.ps1 -DnsServer 10.0.0.53 -Domain corp.local
.\windows-dns-smoke-tests.ps1 -OutputFormat tap
.\windows-dns-smoke-tests.ps1 -NoColor | Out-File dns-results.txt
Requirements:
- PowerShell 5.1+ or PowerShell 7+
- DnsServer PowerShell module (DNS Server role or RSAT)
- Administrator privileges for statistics and event log access
"@
}
# ============================================================================
# MAIN
# ============================================================================
if ($Help) { Show-Usage; exit 0 }
$script:StartTime = Get-Date
if ($OutputFormat -eq "tap") {
Write-TapHeader
} else {
Write-Host ""
Write-Color "Windows DNS Smoke Tests" "White"
Write-Host "Server: $DnsServer"
if ($Domain) { Write-Host "Domain: $Domain" }
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
}
try { Import-Module DnsServer -ErrorAction Stop } catch {
Write-Color "[ERROR] DnsServer module not available. Install the DNS Server role or RSAT DNS tools." "Red"
exit 1
}
Test-DnsService
Test-DnsResponds
Test-ZonesLoaded
Test-ADIntegratedZones
Test-ForwardLookup
Test-ReverseLookup
Test-ConditionalForwarders
Test-RootHints
Test-Forwarders
Test-Scavenging
Test-ZoneDelegation
Test-CacheStats
Test-EventLog
if ($OutputFormat -eq "tap") { Write-TapFooter } else { Write-Summary }
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }