aerial photography of city during night time

Automating CloudFlare DNS updates with PowerShell

Posted on November 27, 2022

Using a home-based server to publish your website or other web-enabled services where a static IP address isn’t available requires manual updates of DNS if your IP address were to change, or using a Dynamic IP service that automatically handles that update for you. Depending on your DNS host, you have several options to make that process as hands-free and automated as possible.

Google DNS

Although the scenario we’re focused on today is around CloudFlare DNS, in the past, I’ve used Google DNS for domains where they were the registrar. Although Google DNS provided support for dynamic DNS records, I needed something to detect IP address changes and automatically push the record update to Google. For Google DNS users running Windows, you can automate DNS updates using this handy service written in C#. Update the configuration file with your hostnames and credentials, and the service takes care of the rest. I used this reliably for several years.

CloudFlare DNS

Using CloudFlare in a dynamic DNS scenario posed some initial challenges. Still, thankfully CloudFlare has a robust API to handle DNS updates, and a quick search put me onto a blog post by June Castillote on “How to Setup Cloudflare Dynamic DNS” using PowerShell. This served as a great starting point for getting the job done.

June’s thorough process coverage got me 99% of what I was looking for, including scheduling the process to run regularly and catching any updates. The only missing piece was the ability to pass multiple record names to the cmdlet for the same zone that should receive the same IP address updates.

Extending the cmdlet

The initial implementation was designed to take the email address of the CloudFlare account, the CloudFlare token that provides API access, the zone name, and the record to update. This means that if you had five records for that zone that all pointed to the same IP address, you would have to run it five separate times, which also meant five scheduled tasks. That’s workable, but I wanted to have a cmdlet that could serve either a single record or multiple records, provided they were from the same zone, and they were all getting the same IP address.

I wanted a cmdlet that could accept a comma-delimited string for the -record parameter:

First, I started by modifying the params, so the $Record param was now a string array:

[cmdletbinding()]
param (
[parameter(Mandatory)]
$Email,
[parameter(Mandatory)]
$Token,
[parameter(Mandatory)]
$Domain,
[parameter(Mandatory)]
[string]$Record
)

Next, I modified the existing record update code to loop through the string array and perform the same actions on each specified record name:

foreach($rec in $RecordArray) {
	## Retrieve the existing DNS record details from Cloudflare.
	$uri = "https://api.cloudflare.com/client/v4/zones/$($zone_id)/dns_records?name=$($rec)"
	$DnsRecord = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers -SkipHttpErrorCheck
	
	if (-not($DnsRecord.result)) {
		Write-Output "Search for the DNS record [$($rec)] return zero results. Skipping record."
		# Skip
		continue
	}
	
	## Store the existing IP address in the DNS record
	$old_ip = $DnsRecord.result.content
	
	## Store the DNS record type value
	$record_type = $DnsRecord.result.type
	
	## Store the DNS record id value
	$record_id = $DnsRecord.result.id
	
	## Store the DNS record ttl value
	$record_ttl = $DnsRecord.result.ttl
	
	## Store the DNS record proxied value
	$record_proxied = $DnsRecord.result.proxied
	
	Write-Output "DNS record [$($rec)]: Type=$($record_type), IP=$($old_ip)"
	
	#EndRegion
	
	#Region update Dynamic DNS Record
	## Compare current IP address with the DNS record
	## If the current IP address does not match the DNS record IP address, update the DNS record.
	
	if ($new_ip -ne $old_ip) {
		Write-Output "The current IP address does not match the DNS record IP address. Attempt to update."
		
		## Update the DNS record with the new IP address
		$uri = "https://api.cloudflare.com/client/v4/zones/$($zone_id)/dns_records/$($record_id)"
		$body = @{
		type = $record_type
		name = $rec
		content = $new_ip
		ttl = $record_ttl
		proxied = $record_proxied
	} | ConvertTo-Json
	
	$Update = Invoke-RestMethod -Method PUT -Uri $uri -Headers $headers -SkipHttpErrorCheck -Body $body
	
	if (($Update.errors)) {
		Write-Output "DNS record update failed. Error: $($Update[0].errors.message)"
		## Exit script
		continue
	}
	
	Write-Output $Update.result
	Write-Output "DNS record update successful!"
	write-host "`n"
	}
	
	else {
		Write-Output "The current IP address and DNS record IP address are the same. There's no need to update."
	}
	
	#EndRegion
}

Now when I run the cmdlet and specify multiple host names in a comma-separated string, I’ll see the output for each successful record update attempt:

Resources

You can find the original script source as part of June’s blog here.

The modified script with support for multiple records can be found on my GitHub.

You might find these interesting...

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments