Domain DNS Check Script

Domain DNS Check Script – vollneutral.ch
PowerShell Tools

Domain DNS Check
Script

Prüft MX, SPF, DMARC und DKIM-Einträge für beliebig viele Domains aus einer CSV-Datei – mit farbiger Konsolen-Ausgabe und CSV-Export.

⚠️
⚠ Nutzung auf eigene Gefahr Für die Richtigkeit und Vollständigkeit dieses Scripts wird keine Garantie übernommen. Scripts aus dem Internet sollten immer kritisch geprüft werden, bevor sie ausgeführt werden — auch dieser hier. Den Quellcode vor der Ausführung lesen und verstehen.

Dieses PowerShell-Skript liest eine Liste von Domains aus einer CSV-Datei ein und prüft pro Domain vier DNS-Sicherheitsmerkmale: MX (Mail Exchange), SPF (Sender Policy Framework), DMARC und DKIM. Es eignet sich für IT-Administratoren, die regelmässig E-Mail-Sicherheitskonfigurationen auditieren möchten.

Jede Prüfung gibt einen farbigen Status in der Konsole aus. Am Ende wird eine vollständige CSV-Datei mit allen Ergebnissen gespeichert.

📬

MX-Records

Prüft, ob Mailserver-Einträge vorhanden und erreichbar sind. Gibt Präferenz und Hostnamen aus.

🛡️

SPF

Sucht nach einem gültigen v=spf1-TXT-Eintrag im DNS der Domain.

📋

DMARC

Prüft _dmarc.<domain> auf einen gültigen DMARC-Eintrag.

🔑

DKIM

Sucht CNAME-Einträge für selector1 und selector2 (Microsoft 365-Style).

✓ OK – Eintrag vorhanden ⚠ MISSING – Kein Eintrag gefunden ✕ ERROR – DNS-Fehler / nicht auflösbar
Input
domains.csv  —  Spalte: domain
Output
domain-dns-check-results.csv
-InputCsv
Optionaler Pfad zur Eingabedatei (Default: .\domains.csv)
-OutputCsv
Optionaler Pfad für die Ausgabe (Default: .\domain-dns-check-results.csv)
PowerShell – Beispiele
# Standard – liest domains.csv im aktuellen Verzeichnis
.\domain-dns-check.ps1

# Eigene Pfade angeben
.\domain-dns-check.ps1 -InputCsv "C:\daten\meine-domains.csv" -OutputCsv "C:\daten\ergebnisse.csv"

# domains.csv – Beispielinhalt:
# domain
# example.com
# vollneutral.ch
# firma.net
domain-dns-check.ps1
#
# .SYNOPSIS
#     Check domains from CSV for MX, SPF, DMARC and DKIM.
#
# .DESCRIPTION
#     Reads domains from domains.csv (column: domain) and checks:
#     - MX records
#     - SPF TXT record
#     - DMARC TXT record
#     - DKIM CNAME records (selector1 / selector2 for Microsoft 365 style DKIM)
#
# .INPUT CSV
#     domains.csv
#     column name: domain
#
# .OUTPUT
#     - Console output with status per domain
#     - CSV export with all results
#
# .NOTES
#     DKIM check here is DNS-based only:
#     selector1._domainkey.<domain>
#     selector2._domainkey.<domain>
#
#     This does NOT query Exchange Online.
#

[CmdletBinding()]
param(
    [string]$InputCsv  = ".\domains.csv",
    [string]$OutputCsv = ".\domain-dns-check-results.csv"
)

# ------------------------------------------------------------
# Helper: write colored status
# ------------------------------------------------------------
function Write-StatusLine {
    param(
        [string]$Label,
        [string]$Status,
        [string]$Text = ""
    )

    $color = switch ($Status) {
        "OK"       { "Green" }
        "MISSING"  { "Yellow" }
        "ERROR"    { "Red" }
        default    { "Gray" }
    }

    Write-Host ("   {0,-6}: " -f $Label) -NoNewline
    Write-Host ("{0,-8}" -f $Status) -ForegroundColor $color -NoNewline

    if ($Text) {
        Write-Host " $Text"
    }
    else {
        Write-Host ""
    }
}

# ------------------------------------------------------------
# Helper: safe DNS lookup
# ------------------------------------------------------------
function Resolve-DnsSafe {
    param(
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][ValidateSet('MX','TXT','CNAME')][string]$Type
    )

    try {
        $records = Resolve-DnsName -Name $Name -Type $Type -ErrorAction Stop
        [PSCustomObject]@{
            Success = $true
            Records = @($records)
            Error   = $null
        }
    }
    catch {
        [PSCustomObject]@{
            Success = $false
            Records = @()
            Error   = $_.Exception.Message
        }
    }
}

# ------------------------------------------------------------
# MX
# ------------------------------------------------------------
function Get-MxInfo {
    param([Parameter(Mandatory)][string]$Domain)

    $lookup = Resolve-DnsSafe -Name $Domain -Type MX

    if (-not $lookup.Success) {
        return [PSCustomObject]@{
            Found       = $false
            Status      = "ERROR"
            Count       = 0
            Records     = $null
            Preference  = $null
            Error       = $lookup.Error
        }
    }

    $mx = $lookup.Records |
        Where-Object { $_.Type -eq 'MX' } |
        Sort-Object Preference, NameExchange

    if (-not $mx -or @($mx).Count -eq 0) {
        return [PSCustomObject]@{
            Found       = $false
            Status      = "MISSING"
            Count       = 0
            Records     = $null
            Preference  = $null
            Error       = $null
        }
    }

    return [PSCustomObject]@{
        Found       = $true
        Status      = "OK"
        Count       = @($mx).Count
        Records     = ($mx | ForEach-Object { $_.NameExchange.TrimEnd('.') }) -join '; '
        Preference  = ($mx | ForEach-Object { "$($_.Preference):$($_.NameExchange.TrimEnd('.'))" }) -join '; '
        Error       = $null
    }
}

# ------------------------------------------------------------
# SPF
# ------------------------------------------------------------
function Get-SpfInfo {
    param([Parameter(Mandatory)][string]$Domain)

    $lookup = Resolve-DnsSafe -Name $Domain -Type TXT

    if (-not $lookup.Success) {
        return [PSCustomObject]@{
            Found   = $false
            Status  = "ERROR"
            Record  = $null
            Error   = $lookup.Error
        }
    }

    $txtValues = foreach ($r in $lookup.Records) {
        if ($r.Type -eq 'TXT' -and $r.Strings) {
            ($r.Strings -join '')
        }
    }

    $spf = $txtValues | Where-Object { $_ -match '^v=spf1' }

    if ($spf) {
        return [PSCustomObject]@{
            Found   = $true
            Status  = "OK"
            Record  = ($spf -join ' | ')
            Error   = $null
        }
    }

    return [PSCustomObject]@{
        Found   = $false
        Status  = "MISSING"
        Record  = $null
        Error   = $null
    }
}

# ------------------------------------------------------------
# DMARC
# ------------------------------------------------------------
function Get-DmarcInfo {
    param([Parameter(Mandatory)][string]$Domain)

    $lookup = Resolve-DnsSafe -Name "_dmarc.$Domain" -Type TXT

    if (-not $lookup.Success) {
        return [PSCustomObject]@{
            Found   = $false
            Status  = "ERROR"
            Record  = $null
            Error   = $lookup.Error
        }
    }

    $txtValues = foreach ($r in $lookup.Records) {
        if ($r.Type -eq 'TXT' -and $r.Strings) {
            ($r.Strings -join '')
        }
    }

    $dmarc = $txtValues | Where-Object { $_ -match '^v=DMARC1' }

    if ($dmarc) {
        return [PSCustomObject]@{
            Found   = $true
            Status  = "OK"
            Record  = ($dmarc -join ' | ')
            Error   = $null
        }
    }

    return [PSCustomObject]@{
        Found   = $false
        Status  = "MISSING"
        Record  = $null
        Error   = $null
    }
}

# ------------------------------------------------------------
# DKIM (DNS only)
# ------------------------------------------------------------
function Get-DkimInfo {
    param([Parameter(Mandatory)][string]$Domain)

    $selector1Name = "selector1._domainkey.$Domain"
    $selector2Name = "selector2._domainkey.$Domain"

    $sel1 = Resolve-DnsSafe -Name $selector1Name -Type CNAME
    $sel2 = Resolve-DnsSafe -Name $selector2Name -Type CNAME

    $selector1Target = $null
    $selector2Target = $null
    $selector1Found  = $false
    $selector2Found  = $false

    if ($sel1.Success) {
        $record = $sel1.Records | Where-Object { $_.Type -eq 'CNAME' } | Select-Object -First 1
        if ($record) {
            $selector1Found  = $true
            $selector1Target = $record.NameHost.TrimEnd('.')
        }
    }

    if ($sel2.Success) {
        $record = $sel2.Records | Where-Object { $_.Type -eq 'CNAME' } | Select-Object -First 1
        if ($record) {
            $selector2Found  = $true
            $selector2Target = $record.NameHost.TrimEnd('.')
        }
    }

    $status = if ($selector1Found -or $selector2Found) { "OK" } else { "MISSING" }

    $errorText = @()
    if (-not $sel1.Success) { $errorText += "selector1: $($sel1.Error)" }
    if (-not $sel2.Success) { $errorText += "selector2: $($sel2.Error)" }

    return [PSCustomObject]@{
        Found            = ($selector1Found -or $selector2Found)
        Status           = $status
        Selector1Found   = $selector1Found
        Selector1Target  = $selector1Target
        Selector2Found   = $selector2Found
        Selector2Target  = $selector2Target
        Error            = ($errorText -join ' | ')
    }
}

# ------------------------------------------------------------
# Validate input
# ------------------------------------------------------------
if (-not (Test-Path -LiteralPath $InputCsv)) {
    Write-Error "Input CSV not found: $InputCsv"
    return
}

try {
    $domains = Import-Csv -Path $InputCsv -ErrorAction Stop
}
catch {
    Write-Error "Failed to read CSV: $($_.Exception.Message)"
    return
}

if (-not $domains) {
    Write-Error "CSV is empty."
    return
}

if (-not ($domains[0].PSObject.Properties.Name -contains 'domain')) {
    Write-Error "CSV must contain a column named 'domain'."
    return
}

$domains = $domains |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_.domain) } |
    ForEach-Object {
        [PSCustomObject]@{
            domain = $_.domain.Trim().ToLower()
        }
    } |
    Sort-Object domain -Unique

if (-not $domains -or $domains.Count -eq 0) {
    Write-Error "No valid domains found in CSV."
    return
}

# ------------------------------------------------------------
# Main Loop
# ------------------------------------------------------------
$results = New-Object System.Collections.Generic.List[object]
$total   = $domains.Count
$index   = 0

Write-Host ""
Write-Host "Domain DNS Check" -ForegroundColor Cyan
Write-Host "Input file : $InputCsv"
Write-Host "Domains    : $total"
Write-Host ""

foreach ($item in $domains) {
    $index++
    $domain = $item.domain

    Write-Progress -Activity "Checking domains" -Status "$domain ($index/$total)" `
        -PercentComplete (($index / $total) * 100)

    Write-Host "[$index/$total] $domain" -ForegroundColor White

    $mx    = Get-MxInfo    -Domain $domain
    $spf   = Get-SpfInfo   -Domain $domain
    $dmarc = Get-DmarcInfo -Domain $domain
    $dkim  = Get-DkimInfo  -Domain $domain

    if ($mx.Status -eq 'OK')       { Write-StatusLine -Label "MX"    -Status $mx.Status    -Text $mx.Records }
    elseif ($mx.Status -eq 'ERROR'){ Write-StatusLine -Label "MX"    -Status $mx.Status    -Text $mx.Error }
    else                           { Write-StatusLine -Label "MX"    -Status $mx.Status }

    if ($spf.Status -eq 'OK')      { Write-StatusLine -Label "SPF"   -Status $spf.Status   -Text $spf.Record }
    elseif ($spf.Status -eq 'ERROR'){ Write-StatusLine -Label "SPF"  -Status $spf.Status   -Text $spf.Error }
    else                           { Write-StatusLine -Label "SPF"   -Status $spf.Status }

    if ($dmarc.Status -eq 'OK')    { Write-StatusLine -Label "DMARC" -Status $dmarc.Status -Text $dmarc.Record }
    elseif ($dmarc.Status -eq 'ERROR'){ Write-StatusLine -Label "DMARC" -Status $dmarc.Status -Text $dmarc.Error }
    else                           { Write-StatusLine -Label "DMARC" -Status $dmarc.Status }

    if ($dkim.Status -eq 'OK') {
        $dkimText = @()
        if ($dkim.Selector1Found) { $dkimText += "selector1=$($dkim.Selector1Target)" }
        if ($dkim.Selector2Found) { $dkimText += "selector2=$($dkim.Selector2Target)" }
        Write-StatusLine -Label "DKIM" -Status $dkim.Status -Text ($dkimText -join '; ')
    }
    else {
        if ($dkim.Error) { Write-StatusLine -Label "DKIM" -Status $dkim.Status -Text $dkim.Error }
        else             { Write-StatusLine -Label "DKIM" -Status $dkim.Status }
    }

    Write-Host ""

    $results.Add([PSCustomObject]@{
        Domain              = $domain
        MXFound             = $mx.Found
        MXStatus            = $mx.Status
        MXCount             = $mx.Count
        MXRecords           = $mx.Records
        MXPreference        = $mx.Preference
        MXError             = $mx.Error
        SPFFound            = $spf.Found
        SPFStatus           = $spf.Status
        SPFRecord           = $spf.Record
        SPFError            = $spf.Error
        DMARCFound          = $dmarc.Found
        DMARCStatus         = $dmarc.Status
        DMARCRecord         = $dmarc.Record
        DMARCError          = $dmarc.Error
        DKIMFound           = $dkim.Found
        DKIMStatus          = $dkim.Status
        DKIMSelector1Found  = $dkim.Selector1Found
        DKIMSelector1Target = $dkim.Selector1Target
        DKIMSelector2Found  = $dkim.Selector2Found
        DKIMSelector2Target = $dkim.Selector2Target
        DKIMError           = $dkim.Error
    })
}

Write-Progress -Activity "Checking domains" -Completed

# ------------------------------------------------------------
# Export
# ------------------------------------------------------------
$results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8

# ------------------------------------------------------------
# Summary
# ------------------------------------------------------------
$mxOk    = @($results | Where-Object { $_.MXFound }).Count
$spfOk   = @($results | Where-Object { $_.SPFFound }).Count
$dmarcOk = @($results | Where-Object { $_.DMARCFound }).Count
$dkimOk  = @($results | Where-Object { $_.DKIMFound }).Count

Write-Host "Summary" -ForegroundColor Cyan
Write-Host "-------"
Write-Host ("Domains checked : {0}" -f $results.Count)
Write-Host ("MX found        : {0}" -f $mxOk)
Write-Host ("SPF found       : {0}" -f $spfOk)
Write-Host ("DMARC found     : {0}" -f $dmarcOk)
Write-Host ("DKIM found      : {0}" -f $dkimOk)
Write-Host ""
Write-Host "Exported to: $OutputCsv" -ForegroundColor Green
vollneutral.ch  ·  Lizenz: frei verwendbar  ·  PowerShell 5.1 / 7+