<#
Cloudflare DNS Update (Windows 11 / PowerShell)
- Updatet NUR genau einen Hostnamen: $RecordName (z.B. xxx.kaspat.xyz)
- Nutzt Cloudflare API Token (Bearer)
- Updatet A (IPv4) und optional AAAA (IPv6)
- Legt den Record optional an, falls er fehlt (CreateIfMissing)
Voraussetzungen:
- PowerShell 5.1+ (Windows 11 hat das)
- Cloudflare API Token mit: Zone -> DNS -> Edit, Zone Resource: Specific zone kaspat.xyz
#>
# =========================
# Konfiguration
# =========================
$ApiToken = "PASTE_YOUR_CLOUDFLARE_API_TOKEN_HERE"
$ZoneName = "kaspat.xyz" # Zone (Root-Domain in Cloudflare)
$RecordName = "xxx.kaspat.xyz" # GENAU dieser Host wird aktualisiert
$UpdateIPv4 = $true
$UpdateIPv6 = $false # auf $true setzen, wenn AAAA gewünscht
$TTL = 300 # 1..86400, oder 1 für "Auto" (je nach Plan)
$Proxied = $false # true = orange cloud (nur sinnvoll wenn du es willst)
$CreateIfMissing= $true # Record anlegen, wenn nicht vorhanden
# IP-Check Dienste (fallback enthalten)
$IPv4Services = @(
"https://api.ipify.org",
"https://ifconfig.me/ip",
"https://checkip.amazonaws.com"
)
$IPv6Services = @(
"https://api64.ipify.org",
"https://ifconfig.me/ip"
)
# Optional: lokales Log
$LogFile = "$env:ProgramData\CloudflareDNS\cf-dns-update.log"
# =========================
# Hilfsfunktionen
# =========================
function Write-Log {
param([string]$Message, [ValidateSet("INFO","WARN","ERROR")] [string]$Level="INFO")
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$ts] [$Level] $Message"
Write-Host $line
try {
$dir = Split-Path -Parent $LogFile
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
Add-Content -Path $LogFile -Value $line -Encoding UTF8
} catch { }
}
function Invoke-CfApi {
param(
[Parameter(Mandatory)][ValidateSet("GET","POST","PUT","DELETE")][string]$Method,
[Parameter(Mandatory)][string]$Path,
[object]$Body = $null
)
if ([string]::IsNullOrWhiteSpace($ApiToken) -or $ApiToken -like "PASTE_*") {
throw "ApiToken ist nicht gesetzt."
}
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json"
}
$uri = "https://api.cloudflare.com/client/v4$Path"
try {
if ($null -ne $Body) {
$json = ($Body | ConvertTo-Json -Depth 10 -Compress)
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body $json -TimeoutSec 20
} else {
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -TimeoutSec 20
}
} catch {
$msg = $_.Exception.Message
if ($_.ErrorDetails -and $_.ErrorDetails.Message) { $msg += " | " + $_.ErrorDetails.Message }
throw "Cloudflare API Fehler ($Method $Path): $msg"
}
}
function Get-PublicIP {
param(
[ValidateSet("IPv4","IPv6")][string]$Family
)
$services = if ($Family -eq "IPv6") { $IPv6Services } else { $IPv4Services }
foreach ($u in $services) {
try {
$ip = (Invoke-RestMethod -Uri $u -TimeoutSec 10).ToString().Trim()
if ($Family -eq "IPv4") {
if ($ip -match '^(?:\d{1,3}\.){3}\d{1,3}$') { return $ip }
} else {
# einfache IPv6-Validierung (reicht für Public-Check)
if ($ip -match '^[0-9a-fA-F:]+$' -and $ip.Contains(":")) { return $ip }
}
} catch {
continue
}
}
return $null
}
function Assert-RecordNameInZone {
# Sicherheitsgurt: verhindert, dass aus Versehen ein fremder Host aktualisiert wird.
if (-not $RecordName.ToLower().EndsWith($ZoneName.ToLower())) {
throw "RecordName '$RecordName' passt nicht zur Zone '$ZoneName'. Abbruch."
}
}
# =========================
# Hauptlogik
# =========================
try {
Assert-RecordNameInZone
Write-Log "Starte DNS-Update für $RecordName (Zone: $ZoneName) ..."
# Zone-ID holen
$zoneResp = Invoke-CfApi -Method GET -Path "/zones?name=$([uri]::EscapeDataString($ZoneName))&status=active"
if (-not $zoneResp.success -or -not $zoneResp.result -or $zoneResp.result.Count -lt 1) {
throw "Zone '$ZoneName' nicht gefunden oder nicht aktiv."
}
$zoneId = $zoneResp.result[0].id
Write-Log "ZoneId: $zoneId"
function Upsert-Record {
param(
[ValidateSet("A","AAAA")][string]$Type,
[Parameter(Mandatory)][string]$Content
)
# Record suchen (genau Name + Typ)
$qName = [uri]::EscapeDataString($RecordName)
$qType = [uri]::EscapeDataString($Type)
$recResp = Invoke-CfApi -Method GET -Path "/zones/$zoneId/dns_records?type=$qType&name=$qName"
$record = $null
if ($recResp.success -and $recResp.result -and $recResp.result.Count -ge 1) {
# Wenn mehrere, nimm den ersten (normalerweise ist es genau einer)
$record = $recResp.result[0]
}
if ($null -eq $record) {
if (-not $CreateIfMissing) {
Write-Log "$Type Record für $RecordName existiert nicht (CreateIfMissing=false). Überspringe." "WARN"
return
}
Write-Log "$Type Record existiert nicht, lege neu an ..."
$createBody = @{
type = $Type
name = $RecordName
content = $Content
ttl = $TTL
proxied = $Proxied
}
$createResp = Invoke-CfApi -Method POST -Path "/zones/$zoneId/dns_records" -Body $createBody
if (-not $createResp.success) { throw "Anlegen des $Type Records fehlgeschlagen." }
Write-Log "$Type Record angelegt: $RecordName -> $Content"
return
}
# Wenn Content gleich ist: nichts tun
if ($record.content -eq $Content) {
Write-Log "$Type unverändert ($Content). Kein Update nötig."
return
}
# Update (PUT auf Record-ID)
$recId = $record.id
Write-Log "$Type ändert sich: $($record.content) -> $Content (RecordId: $recId)"
$updateBody = @{
type = $Type
name = $RecordName
content = $Content
ttl = $TTL
proxied = $Proxied
}
$updResp = Invoke-CfApi -Method PUT -Path "/zones/$zoneId/dns_records/$recId" -Body $updateBody
if (-not $updResp.success) { throw "Update des $Type Records fehlgeschlagen." }
Write-Log "$Type Update OK: $RecordName -> $Content"
}
if ($UpdateIPv4) {
$ipv4 = Get-PublicIP -Family IPv4
if (-not $ipv4) { Write-Log "Konnte keine öffentliche IPv4 ermitteln." "ERROR" }
else { Upsert-Record -Type A -Content $ipv4 }
}
if ($UpdateIPv6) {
$ipv6 = Get-PublicIP -Family IPv6
if (-not $ipv6) { Write-Log "Konnte keine öffentliche IPv6 ermitteln." "ERROR" }
else { Upsert-Record -Type AAAA -Content $ipv6 }
}
Write-Log "Fertig."
exit 0
}
catch {
Write-Log $_.Exception.Message "ERROR"
exit 1
}
Wenn du das als geplante Aufgabe laufen lassen willst (z. B. alle 10 Minuten), sag kurz Bescheid – dann gebe ich dir den passenden schtasks-Befehl inkl. “run whether user is logged on or not” und sauberem Pfad (z. B. C:\Scripts\cf-dns-update.ps1).
ja, alle 5 minuten auf update checken unabhängig von der benutzeranmeldung, pfad wie vorgeschlagen
Perfekt 👍 Hier ist das saubere, praxiserprobte Setup, genau so wie du es willst: alle 5 Minuten, ohne Benutzeranmeldung, Windows 11 Pro, Pfad C:\Scripts.
1️⃣ Script ablegen
Speichere dein Script als:
C:\Scripts\cf-dns-update.ps1
⚠️ Achte darauf:
Datei nicht blockiert (Rechtsklick → Eigenschaften → ggf. „Zulassen“)
API-Token ist korrekt gesetzt
2️⃣ Ordner für Logs (optional, empfohlen)
Das Script nutzt:
C:\ProgramData\CloudflareDNS\cf-dns-update.log
Der Ordner wird automatisch erstellt – kein Handgriff nötig.
3️⃣ Geplante Aufgabe anlegen (SYSTEM, ohne Login) 🔹 PowerShell als Administrator öffnen
Dann exakt diesen Befehl ausführen:
schtasks /create ^ /tn „Cloudflare DNS Update xxx.kaspat.xyz“ ^ /tr „powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\cf-dns-update.ps1“ ^ /sc minute ^ /mo 5 ^ /ru SYSTEM ^ /rl HIGHEST ^ /f
✅ läuft alle 5 Minuten ✅ läuft auch ohne Benutzeranmeldung ✅ läuft mit SYSTEM-Rechten ✅ keine Probleme mit ExecutionPolicy
4️⃣ Aufgabe sofort testen schtasks /run /tn „Cloudflare DNS Update xxx.kaspat.xyz“
Dann Log prüfen:
type C:\ProgramData\CloudflareDNS\cf-dns-update.log
Beispiel:
[2026-02-04 15:12:01] [INFO] A unverändert (77.3.xxx.xxx). Kein Update nötig.
5️⃣ Typische Fehler & Fixes ❌ Script läuft manuell, aber nicht im Task
➡️ Pfad prüfen:
C:\Scripts\cf-dns-update.ps1
➡️ powershell.exe ohne Pfad ist korrekt (SYSTEM kennt ihn)
❌ Keine IPv6 gefunden
👉 normal bei vielen Anschlüssen ➡️ $UpdateIPv6 = $false ist absolut ok
❌ Keine Logs
➡️ Prüfen:
schtasks /query /tn „Cloudflare DNS Update xxx.kaspat.xyz“ /v /fo list
🔐 Sicherheits-Check (du bist hier vorbildlich)
✔ API Token: nur DNS Edit
✔ Zone: kaspat.xyz
✔ Script: hart auf xxx.kaspat.xyz verdrahtet
✔ Task: kein User-Kontext
✔ Optional: IP-Restriction im Token
➡️ Das ist State of the Art DynDNS auf Windows.
Wenn du willst, erweitere ich das Script noch um:
📩 Telegram / Mail nur bei echter Änderung
🧠 State-File (damit bei Neustart kein unnötiger PUT kommt)
🔕 Silent Mode (keine Logs ohne Änderung)
🌐 A + AAAA getrennt steuerbar per Switch
Sag einfach Bescheid 💪