Public/healthcheck/Get-DnsServerHealth.ps1
|
#Requires -Version 5.1 function Get-DnsServerHealth { <# .SYNOPSIS Checks DNS Server role health on Windows servers .DESCRIPTION Retrieves comprehensive DNS Server health information including service status, zone inventory, forwarder configuration, root hints, and self-resolution capability. Returns a single typed object per server with an overall health assessment. .PARAMETER ComputerName One or more computer names to query. Defaults to the local machine. Accepts pipeline input by value and by property name. .PARAMETER Credential Optional PSCredential for authenticating to remote computers. Not used for local queries. .EXAMPLE Get-DnsServerHealth Checks DNS Server health on the local machine. .EXAMPLE Get-DnsServerHealth -ComputerName 'DNS01' Checks DNS Server health on a single remote server. .EXAMPLE 'DNS01', 'DNS02' | Get-DnsServerHealth -Credential (Get-Credential) Checks DNS Server health on multiple remote servers via pipeline. .OUTPUTS PSWinOps.DnsServerHealth Returns one object per server with DNS service status, zone counts, forwarder and root hints counts, self-resolution result, and overall health. .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-03-26 Requires: PowerShell 5.1+ / Windows only Requires: DNS Server role .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/powershell/module/dnsserver/ #> [CmdletBinding()] [OutputType('PSWinOps.DnsServerHealth')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) begin { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting" $localNames = @($env:COMPUTERNAME, 'localhost', '.') $scriptBlock = { $data = @{ ServiceStatus = 'NotFound' ModuleAvailable = $false TotalZones = 0 PrimaryZones = 0 SecondaryZones = 0 PausedZones = 0 ForwarderCount = 0 RootHintsCount = 0 SelfResolution = $false } # Check DNS service status $dnsSvc = Get-Service -Name 'DNS' -ErrorAction SilentlyContinue if ($dnsSvc) { $data.ServiceStatus = $dnsSvc.Status.ToString() } # Check DnsServer module availability $dnsModule = Get-Module -Name 'DnsServer' -ListAvailable -ErrorAction SilentlyContinue if ($dnsModule) { $data.ModuleAvailable = $true } if ($data.ModuleAvailable -and $data.ServiceStatus -eq 'Running') { # Zone inventory $zones = Get-DnsServerZone -ErrorAction SilentlyContinue if ($zones) { $data.TotalZones = @($zones).Count $data.PrimaryZones = @($zones | Where-Object -Property ZoneType -EQ -Value 'Primary').Count $data.SecondaryZones = @($zones | Where-Object -Property ZoneType -EQ -Value 'Secondary').Count $data.PausedZones = @($zones | Where-Object -Property IsPaused -EQ -Value $true).Count } # Forwarder configuration $forwarders = Get-DnsServerForwarder -ErrorAction SilentlyContinue if ($forwarders -and $forwarders.IPAddress) { $data.ForwarderCount = @($forwarders.IPAddress).Count } # Root hints $rootHints = Get-DnsServerRootHint -ErrorAction SilentlyContinue if ($rootHints) { $data.RootHintsCount = @($rootHints).Count } # Self-resolution test $resolveResult = Resolve-DnsName -Name $env:COMPUTERNAME -Server 'localhost' -DnsOnly -ErrorAction SilentlyContinue if ($resolveResult) { $data.SelfResolution = $true } } $data } } process { foreach ($machine in $ComputerName) { $displayName = $machine.ToUpper() Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying '${machine}'" try { $isLocal = $localNames -contains $machine if ($isLocal) { $result = & $scriptBlock } else { $invokeParams = @{ ComputerName = $machine ScriptBlock = $scriptBlock ErrorAction = 'Stop' } if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $invokeParams['Credential'] = $Credential } $result = Invoke-Command @invokeParams } # Compute OverallHealth outside the scriptblock if (-not $result.ModuleAvailable) { $healthStatus = 'RoleUnavailable' } elseif ($result.ServiceStatus -ne 'Running' -or -not $result.SelfResolution) { $healthStatus = 'Critical' } elseif ($result.PausedZones -gt 0 -or $result.ForwarderCount -eq 0) { $healthStatus = 'Degraded' } else { $healthStatus = 'Healthy' } [PSCustomObject]@{ PSTypeName = 'PSWinOps.DnsServerHealth' ComputerName = $displayName ServiceName = 'DNS' ServiceStatus = $result.ServiceStatus TotalZones = [int]$result.TotalZones PrimaryZones = [int]$result.PrimaryZones SecondaryZones = [int]$result.SecondaryZones PausedZones = [int]$result.PausedZones ForwarderCount = [int]$result.ForwarderCount RootHintsCount = [int]$result.RootHintsCount SelfResolution = [bool]$result.SelfResolution OverallHealth = $healthStatus Timestamp = Get-Date -Format 'o' } } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_" continue } } } end { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed" } } |