Last active
January 31, 2024 20:28
-
-
Save xucian/ca479b12f62690c3552683f921ff6b15 to your computer and use it in GitHub Desktop.
Cloudflare DDNS for Windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Create or Update a Cloudflare domain or subdomain with your current IP, and schedule a recurring run via Task Scheduler | |
# (Optional but recommended) Rename this to include the subdomain name, such as ddns-cf-win_x.example.com.ps1 or simply x.example.com.ps1 | |
# Place this somewhere you're comfortable running it from (C:/PortablePrograms/ddns-cf/) | |
# Right-click and Run in PowerShell | |
# This will run once, and schedule itself to run via Task Scheduler every x minutes (will ask for Admin priviledges for this) | |
# If the Task already exists for this record, it won't be duplicated | |
# Can run multiple of these for different records (task name includes the recordFullName) | |
# Stop the script on any errors | |
$ErrorActionPreference = "Stop" | |
# --- Cloudflare DNS Update Part --- | |
# Cloudflare settings | |
$apiKey = "???" # or token | |
$zoneID = "???" | |
$domain = "example.com" | |
$recordName = "subdomain" # or @ | |
$updateInterval = 1 # minutes | |
# --- Cloudflare DNS Update Part --- | |
# Stop the script on any errors | |
$ErrorActionPreference = "Stop" | |
function Get-PublicIP { | |
$urlList = @( | |
"http://ipinfo.io/json", | |
"https://api.ipify.org", | |
"https://ifconfig.me/ip", | |
"https://icanhazip.com", | |
"https://ipinfo.io/ip" | |
) | |
foreach ($url in $urlList) { | |
try { | |
if ($url -eq "http://ipinfo.io/json") { | |
$response = Invoke-RestMethod -Uri $url | |
return $response.ip | |
} else { | |
$response = Invoke-WebRequest -Uri $url | |
return $response.Content.Trim() | |
} | |
} catch { | |
Write-Host "Failed to get IP from $url. Trying next..." | |
} | |
} | |
throw "All IP address providers failed." | |
} | |
$recordFullName = if ($recordName -eq "@") { $domain } else { "${recordName}.${domain}" } | |
try { | |
$publicIp = Get-PublicIP | |
Write-Host "$recordFullName -> $publicIp" | |
} catch { | |
Write-Host "Failed to retrieve public IP address: $_" | |
exit | |
} | |
try { | |
$headers = @{ | |
"Authorization" = "Bearer $apiKey" | |
"Content-Type" = "application/json" | |
} | |
$recordUri = "https://api.cloudflare.com/client/v4/zones/$zoneID/dns_records?name=$recordFullName" | |
$recordID = $null | |
try { | |
#Write-Host "1 recordUri: $recordUri" | |
$response = Invoke-RestMethod -Uri "$recordUri" -Method Get -Headers $headers | |
#Write-Host "2 response: $response" | |
$recordID = ($response.result | Where-Object { $_.name -eq $recordFullName }).id | |
#Write-Host "3 recordID: $recordID" | |
} catch { | |
Write-Host "Error fetching record ID. Status Code: $($_.Exception.Response.StatusCode.Value__) Message: $($_.Exception.Message)" | |
$responseContent = $_.Exception.Response.GetResponseStream() | |
$reader = New-Object System.IO.StreamReader($responseContent) | |
$responseString = $reader.ReadToEnd() | |
Write-Host "Response Content: $responseString" | |
$recordID = $null | |
} | |
$updateUri = "https://api.cloudflare.com/client/v4/zones/$zoneID/dns_records/$recordID" | |
$body = @{ | |
type = "A" | |
name = $recordName | |
content = $publicIp | |
ttl = 1 | |
proxied = $false | |
} | ConvertTo-Json | |
if ($recordID) { | |
# Update | |
Invoke-RestMethod -Uri "$updateUri" -Method Put -Headers $headers -Body $body | |
Write-Host "DNS record updated to IP: $publicIp" | |
} else { | |
# Create | |
Invoke-RestMethod -Uri "$updateUri" -Method Post -Headers $headers -Body $body | |
Write-Host "OK" | |
} | |
} catch { | |
Write-Host "Error fetching record ID: $($_.Exception.Message)" | |
Write-Host "Status Code: $($_.Exception.Response.StatusCode)" | |
Write-Host "Response Content: $($_.Exception.Response.Content)" | |
exit | |
} | |
# --- Task Scheduler Part --- | |
$taskName = "ddns-cf-win-$recordFullName" | |
# Check if the task already exists | |
if (-not (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue)) { | |
# Function to check if running as Administrator | |
function Test-Admin { | |
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) | |
$currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
} | |
# Check if running as Administrator | |
if (-not (Test-Admin)) { | |
#Write-Error "Please run as Administrator if you want to schedule the recurring task" | |
#exit | |
# Relaunch as an administrator (Cloudflare API will be called a 2nd time, but that's fine) | |
if ($elevated) { | |
# tried to elevate, did not work, aborting | |
} else { | |
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition)) | |
} | |
exit | |
} | |
# Define script path | |
$scriptPath = $MyInvocation.MyCommand.Definition | |
# Task parameters | |
$action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-File `"$scriptPath`"" | |
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $updateInterval) -RepetitionDuration (New-TimeSpan -Days 10000) | |
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount | |
# Register the task | |
try { | |
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $taskName -Description "Update Cloudflare DNS for $recordFullName every $updateInterval minute(s)" -Principal $principal | |
Write-Host "Task Scheduler entry created for $scriptPath. Make sure to keep the script in the same place. To cancel this task, search for '$taskName' in Task Scheduler (usually under your username)" | |
} catch { | |
Write-Host "Failed to create Task Scheduler entry: $_" | |
exit | |
} | |
} else { | |
Write-Host "Task Scheduler entry already exists. All good" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment