Skip to content

Instantly share code, notes, and snippets.

@xucian
Last active January 31, 2024 20:28
Show Gist options
  • Save xucian/ca479b12f62690c3552683f921ff6b15 to your computer and use it in GitHub Desktop.
Save xucian/ca479b12f62690c3552683f921ff6b15 to your computer and use it in GitHub Desktop.
Cloudflare DDNS for Windows
# 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