Dargslan.WinDnsHealth.psm1

<#
.SYNOPSIS
    Audit Windows DNS Server health: zones, scavenging, recursion, root hints, forwarders. JSON / HTML report.

.DESCRIPTION
    Part of the Dargslan Windows Admin Tools collection.
    Free Cheat Sheet: https://dargslan.com/cheat-sheets/windows-dns-server-health-2026
    Full Guide: https://dargslan.com/blog/windows-dns-server-health-powershell-2026
    More tools: https://dargslan.com

.LINK
    https://dargslan.com

.LINK
    https://github.com/Dargslan/powershell-admin-scripts
#>


$script:Banner = @"
+----------------------------------------------------------+
| Dargslan Windows DNS Health Audit
| https://dargslan.com - Free cheat sheets & eBooks |
+----------------------------------------------------------+
"@


function Get-DargslanDnsZones {
    <#
    .SYNOPSIS
        List all DNS zones with type, dynamic-update setting and scavenging.
    #>

    [CmdletBinding()]
    param([string]$ComputerName = $env:COMPUTERNAME)
    Get-DnsServerZone -ComputerName $ComputerName |
        Select ZoneName, ZoneType, IsAutoCreated, IsDsIntegrated, DynamicUpdate, IsReverseLookupZone
}

function Get-DargslanDnsServerSettings {
    <#
    .SYNOPSIS
        Return recursion, root hints and forwarder configuration.
    #>

    [CmdletBinding()]
    param([string]$ComputerName = $env:COMPUTERNAME)
    $set = Get-DnsServerSetting -ComputerName $ComputerName -All
    $rec = Get-DnsServerRecursion -ComputerName $ComputerName
    $fw  = Get-DnsServerForwarder -ComputerName $ComputerName
    [pscustomobject]@{
        ComputerName  = $ComputerName
        Recursion     = $rec.Enable
        SecureResp    = $set.EnableDnsSec
        ForwardersIp  = ($fw.IPAddress -join ',')
        ForwardingTimeout = $fw.Timeout
        ListenAddresses = ($set.ListeningIPAddress -join ',')
        Version       = $set.MajorVersion
    }
}

function Get-DargslanDnsScavengingStatus {
    <#
    .SYNOPSIS
        Server-level scavenging settings + per-zone aging.
    #>

    [CmdletBinding()]
    param([string]$ComputerName = $env:COMPUTERNAME)
    $srv = Get-DnsServerScavenging -ComputerName $ComputerName
    $zones = Get-DnsServerZone -ComputerName $ComputerName |
        Where-Object ZoneType -eq 'Primary' |
        ForEach-Object {
            $age = Get-DnsServerZoneAging -Name $_.ZoneName -ComputerName $ComputerName -ErrorAction SilentlyContinue
            [pscustomobject]@{
                Zone          = $_.ZoneName
                AgingEnabled  = $age.AgingEnabled
                NoRefresh     = $age.NoRefreshInterval
                Refresh       = $age.RefreshInterval
            }
        }
    [pscustomobject]@{
        Server = [pscustomobject]@{
            ScavengingState  = $srv.ScavengingState
            ScavengingInterval = $srv.ScavengingInterval
        }
        Zones = $zones
    }
}

function Get-DargslanDnsHealthReport {
    <#
    .SYNOPSIS
        Combined DNS audit with PASS / WARN / FAIL verdict.
    #>

    [CmdletBinding()]
    param([string]$ComputerName = $env:COMPUTERNAME)
    $zones = @(Get-DargslanDnsZones -ComputerName $ComputerName)
    $set   = Get-DargslanDnsServerSettings -ComputerName $ComputerName
    $scav  = Get-DargslanDnsScavengingStatus -ComputerName $ComputerName
    $primary = ($zones | Where-Object ZoneType -eq 'Primary').Count
    $secureZones = ($zones | Where-Object DynamicUpdate -eq 'Secure').Count
    $score = 0
    if ($primary -gt 0)                    { $score++ }
    if ($secureZones -ge 1)                { $score++ }
    if ($set.ForwardersIp)                  { $score++ }
    if ($scav.Server.ScavengingState)        { $score++ }
    $verdict = if ($score -ge 3) { 'PASS' } elseif ($score -ge 1) { 'WARN' } else { 'FAIL' }
    [pscustomobject]@{
        ComputerName  = $ComputerName
        ZoneCount     = $zones.Count
        PrimaryZones  = $primary
        SecureZones   = $secureZones
        Settings      = $set
        Scavenging    = $scav
        Score         = $score
        Verdict       = $verdict
        TimeStamp     = (Get-Date).ToString('s')
    }
}

function Export-DargslanDnsHealthReport {
    <#
    .SYNOPSIS
        Export the DNS audit to HTML and JSON.
    #>

    [CmdletBinding()]
    param([string]$ComputerName = $env:COMPUTERNAME, [string]$OutDir = (Join-Path $env:TEMP 'DargslanDnsAudit'))
    if (-not (Test-Path $OutDir)) { New-Item -Type Directory -Path $OutDir | Out-Null }
    $r = Get-DargslanDnsHealthReport -ComputerName $ComputerName
    $json = Join-Path $OutDir ('dns-' + $ComputerName + '.json')
    $html = Join-Path $OutDir ('dns-' + $ComputerName + '.html')
    $r | ConvertTo-Json -Depth 6 | Set-Content $json -Encoding UTF8
    $body  = "<h1>DNS Health - $($r.ComputerName)</h1>"
    $body += "<p>Verdict: <b>$($r.Verdict)</b> ($($r.Score)/4)</p>"
    $body += '<h2>Settings</h2>'  + ($r.Settings | ConvertTo-Html -Fragment)
    $body += '<h2>Scavenging</h2><pre>' + ($r.Scavenging | ConvertTo-Json -Depth 4) + '</pre>'
    ConvertTo-Html -Body $body -Title 'DNS Health' | Set-Content $html -Encoding UTF8
    [pscustomobject]@{ Json = $json; Html = $html; Verdict = $r.Verdict }
}