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.
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).
# 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
#
# .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