<# 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 💪