Google Domains Dynamic DNS Updates Using Powershell

I recently purchased a new domain to provide access to services running off my home network through Google Domains and needed a way to perform dynamic DNS updates so that I could access my home network reliably from wherever my travels take me. Luckily Google provides some basic DNS features for domains housed with them including dynamic updates.

I started reading through the Google Domains documentation to find out what it would take and found a great guide to setting up dynamic DNS for your domain. (https://support.google.com/domains/answer/6147083?hl=en&ref_topic=9018335)

This was the first step towards getting things set up.  Next I needed to find a client to perform the dynamic updates. I looked around at various clients out there and most didn't work with Google Domains and those that did were too expensive for my taste (I'm a cheap skate). So I started thinking about how I could create a roll-your-own solution to the problem.

At the bottom of the guide linked above there is a section titled "Using the API to update your Dynamic DNS record". I've recently been working on setting up an API at work and this got me thinking that I could create a Powershell script to check what my server's IP address is, then make the API call to update the DNS record for my domain. So without further delay, here is how I solve this problem.

Getting the Public IP Address

The first step is to get the current public IP address of the computer running the script.  I accomplish this using another public API from ipinfo.io.  This returns a JSON object with not only the public IP address, but a bunch of other information related to the carrier and location as well.  You should definitely take a moment to check out their service.  For now we just care about the IP address.  This is how I get it.
# Get the public IP
$ip = Invoke-RestMethod http://ipinfo.io/json | Select-Object -ExpandProperty ip
 
Now that we have our public IP address, we can use the Google Domains API to do our update.  The Google API requires 3 pieces of information to perform an updates:
  1. Username and Password
  2. Hostname to be updated
  3. The new IP Address
The username and password ARE NOT your Google account credentials.  Instead, when you follow the guide linked above to set up dynamic DNS updates on your domain, you will receive a generated set of credentials for your specific hostname. I'm only updating one hostname, but if you have multiple hostnames it seems that you will have a different set of credentials for each one.

Building the Credentials

When we make our call to the API, the username and password need to be sent as part of the request header, while the hostname and IP address are sent as query parameters.  So first we'll create an object to contain the credentials in the header.
# Build the headers that contain the credentials
$credpair = "$($username):$($password)"
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($credpair))
$headers = @{ Authorization = "Basic $encodedCredentials" }

A couple things are going on here. If it's not already obvious, in this code block the $username and $password variables contain the username and password provided to use by Google.
First, we create a new variable to hold the username & password in a Key:Value style format.  Then we encode them in Base 64 using a .Net class (System.Convert.ToBase64String).
Finally we create another variable to represent the header we will send to the API.  This header specifies that it contains the credentials in Basic authentication format.

Making the Call

Now that we have our IP address and our credentials, we should be ready to roll everything together and make the call to the API to update our hostname.

# Send the update to the server
$uri = "https://domains.google.com/nic/update?hostname=$hostname&myip=$ip"
$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers

In these two lines we build the URI, and then send the request using the header we built in the step before.  You'll notice we store the response from the API in a variable.  The API returns normal HTTP status codes so we can check the value of the variable to see if our update was successful.

Wrapping it All Together

Below is the complete script.  You'll notice that I'm pulling many of the values from a separate Settings.xml file.  You could hard code these values into the script but I chose to do this so that I could re-use and share this script a little easier.  You'll also notice that after I get the public IP address of the computer, I check to see if it's changed since the last time.  In an effort to be nice and cut down on the traffic to the Google API I wrote this in so I only send updates when there is a change.  That way we don't get on Google's bad side. The script also incorporates some logging capability so I can check on how it's been doing every once in a while.

# Script to update Dynamic DNS on Google Domains.
# Copyright 2018 Jake Weaver
# jake@jjepen.com

# Get settings
[xml]$Config = Get-Content .\Settings.xml
$baseURI = $config.Settings.URI
$username = $Config.Settings.username
$password = $Config.Settings.password
$hostname = $Config.Settings.hostname
$oldIP = $Config.Settings.IPAddress

# Build the headers that contain the credentials
$credpair = "$($username):$($password)"
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($credpair))
$headers = @{ Authorization = "Basic $encodedCredentials" }

# Get the public IP
$ip = Invoke-RestMethod http://ipinfo.io/json | Select-Object -ExpandProperty ip

# If the public IP has changed (Doesn't match the old public IP) then attempt to update it.
if ([string]$ip -ne $oldIP) {
    # Update the settings file with the new IP
    $Config.Settings.IPAddress = [string]$ip
    $config.Save("Settings.xml")

    # Send the update to the server
    $uri = "$($baseURI)?hostname=$hostname&myip=$ip"
    $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers

    # log the response from the server
    $date = Get-Date
    $date = Get-Date
    "$($date) - $($response)" | Out-File .\DNSUpdates.txt -Append
    
}

And that's it! I set this script to run once every 3 hours on my server using task scheduler. This could be run on any Windows client computer as well.

If you would like to download the full script and an example configuration file feel free to visit the GitHub repo at https://github.com/jjepen/GoogleDomainsDynamicDNS

Thanks for reading!

Comments

Popular posts from this blog

Controlling TP-Link Kasa Smart Switches with PowerShell

An introduction