Lately I’ve been working on building out my home lab to play with things that I’d never even think of setting up and trying in our development environment at work. It’s been a blast; I’ve set up local domain controllers to mess with for testing scripts, built Linux servers for testing replication to and from SQL Servers and other relational database products, and even set up routing and remote access so that I can remote VPN into my home network for storing and retrieving code I’ve been working on, or even using it to show off proof of concept projects to other people. It’s insanely cool and I’ve learned a lot in the process.
When you start talking about doing things like home VPN servers (or any home server for that matter), sooner or later you run up against a thorny problem: how can I make sure I can always connect to my home network remotely? Good news: you already have an IP address!
Having (or knowing) what your home IP address is gives you a lot of flexibility though because if you know it, you can register domain names and have them point to it. For example, let’s say I wanted to expose a server (named “dwarf”) on my home network to the internet. Aside from the networking and firewall changes on my network’s end (opening the right ports for forwarding, enabling remote access, etc) I would also need a domain name and host entry so it knew how to resolve. Then I could register a domain name, like boatmurder.net, and have dwarf.boatmurder.net resolve to my home IP address. That’s a lot easier (and cooler) to remember than an IP address. But in order to do that, you have to know your home IP to set the “A” record for the domain name server.
Sounds neat right? The rub is that it isn’t likely you’re going to memorize your IP address, and even if you did most internet service providers (ISPs) who offer home broadband don’t provide you with a static IP address. That means if you ever need to reboot your modem you aren’t guaranteed to get the same address. That’s not to say you can’t have a static IP but they are usually part of a more expensive business offering that probably isn’t worth the cost to have in your home.
There’s a solution, though: Dynamic DNS. Dynamic DNS is defined as a service that lets you automatically update a name server without the need to change it manually, usually after an IP has changed (even without you knowing it has changed). The process usually works like this:
That might sound easy, but there’s a couple of gotchas: one, some services, like Dyn, aren’t free. CloudFlare has a free solution, but you need to use their name servers for your domains (which may or may not be a deal breaker for you). Secondly, almost all of the freely available tools that run on your network are Linux based. That in itself isn’t as bad as it sounds, even if you aren’t familiar with Linux you can get them up and running pretty quickly. The bigger problem is that if your home network is mostly Windows based and you don’t have any spare hardware laying around to build your own Linux server (or you don’t want to spin up VMs on your home machine), then you can’t use them either (update: it looks like you can get these working with Perl on your local machines too, but I haven’t tried that yet).
There is a third option, too: some newer home routers from vendors like Asus provider Dynamic DNS services bundled with the router OS software too. So if you have your own router this may be an option.
You might be wondering what this all has to do with PowerShell, right? Recently, I registered a couple new domain names with Google Domains, mostly because they give you a really nice web interface to use and since I use Google for almost everything else in my life, this just made sense (and I didn’t need or want yet another online account). When I was in there setting up some records to redirect them to my blog, I noticed something:
Under “synthetic records” I noticed a Dynamic DNS option. Clicking a little deeper, I made it to the help page for synthetic records, and then Dynamic DNS, which you can read here. Here’s the TL;DR: Google offers this service for free for domains registered on their service and they support the same dyndns2 protocol that other Dynamic DNS providers do, so the same (Linux) applications you would have installed for the other services apply here too.
But before I closed the window thinking I’d be building a new Linux VM in my home lab, I saw this:
And then I got really excited, because that meant I could roll my own solution to update their Dynamic DNS service with PowerShell! So that’s what I did.
Google’s Dynamic DNS API is really little less than a HTTPS POST to a specific URL with some parameters packed into the post. What makes it work, though, is for each dynamic DNS host name you provide, you get an auto-generated user name and password that needs to be part of the POST. PowerShell is really naturally suited for this type of automation, thanks to Invoke-WebRequest (MSDN Link). It’s built for this, and even supports HTTPS. Heck, it even supports the implementation of username:password in the request via a PSCredential object.
All I really needed to do was to wrap the code in a function and add some parameters that can be passed in, invoke the web request, and parse the results. Here’s a snippet of the larger function I mentioned above:
[CmdletBinding(SupportsShouldProcess = $true)] Param ( [parameter(Mandatory=$true)] [pscredential] $credential, [parameter(Mandatory = $true)] [string]$domainName, [parameter(Mandatory = $true)] [string]$subdomainName, [parameter(Mandatory = $false)] [string]$ip, [parameter(Mandatory = $false)] [switch]$offline, [parameter(Mandatory = $false)] [switch]$online ) begin { $webRequestURI = "https://domains.google.com/nic/update" $params = @{} } process { $splitDomain = $domainName.split(".") if ($splitDomain.Length -ne 2) { Throw "Please enter a valid top-level domain name (yourdomain.tld)" } $subAndDomain = $subDomainName + "." + $domainName $splitDomain = $subAndDomain.split(".") if ($splitDomain.Length -ne 3) { Throw "Please enter a valid host and domain name (subdomain.yourdomain.tld)" } $params.Add("hostname",$subAndDomain) if ($ip -and !$offline) { $ipValid = $true $splitIp = $ip.split(".") if ($splitIp.length -ne 4) { $ipValid = $false } ForEach ($i in $splitIp) { if ([int] $i -lt 0 -or [int] $i -gt 255) { $ipValid = $false } } if (!$ipValid) { Throw "Please enter a valid IP address" } $params.Add("myip",$ip) } elseif ($offline -and !$online) { $params.Add("offline","yes") } elseif ($online -and !$offline) { $params.Add("offline","no") } if ($PSCmdlet.ShouldProcess("$subAndDomain","Adding IP")) { $response = Invoke-WebRequest -uri $webRequestURI -Method Post -Body $params -Credential $credential $Result = $Response.Content $StatusCode = $Response.StatusCode if ($Result -like "good*") { $splitResult = $Result.split(" ") $newIp = $splitResult[1] Write-Verbose "IP successfully updated for $subAndDomain to $newIp." } if ($Result -like "nochg*") { $splitResult = $Result.split(" ") $newIp = $splitResult[1] Write-Verbose "No change to IP for $subAndDomain (already set to $newIp)." } if ($Result -eq "badauth") { Throw "The username/password you providede was not valid for the specified host." } if ($Result -eq "nohost") { Throw "The hostname you provided does not exist, or dynamic DNS is not enabled." } if ($Result -eq "notfqdn") { Throw "The supplied hostname is not a valid fully-qualified domain name." } if ($Result -eq "badagent") { Throw "You are making bad agent requests, or are making a request with IPV6 address (not supported)." } if ($Result -eq "abuse") { Throw "Dynamic DNS access for the hostname has been blocked due to failure to interperet previous responses correctly." } if ($Result -eq "911") { Throw "An error happened on Google's end; wait 5 minutes and try again." } } $response }
I’ve highlighted the lines that show you how the body of the HTTP post gets parameters and where the request is being made. The rest of the code is error checking to make sure people put in valid values for things Google will expect (and they also have error checking on their side, but if you keep throwing bad requests at them, they’ll flag your requests and reject them all. Just fair warning, so don’t spam them with bad requests).
With this code, you can set now use native PowerShell to keep your Google Domains Dynamic DNS up to date and without the need for separate machines or VMs to run utilities! I took the code above and created a module, called GoogleDynamicDNSTools. This module can be found on my GitHub, and I’m also proud to announce that I have made it available on the PowerShell Gallery! That means you can install the module one of two ways:
Once you have the module, you can create a scheduled task to run every hour and update your Dynamic DNS entry. For my set up, I wrote a .PSD1 script file and declared some variables, created a credential object from plain text, and then call the module’s function to send the request to Google. Here’s a sample:
Import-Module GoogleDynamicDNSTools $LogDate = Get-Date $LogFileName = "GoogleDNSUpdateLog_" + $LogDate.Month + $LogDate.Day + $LogDate.Year + "_" + $LogDate.Hour + $LogDate.Minute + $LogDate.Second + ".txt" $APIUserID = "myuniqueuserid" $APIPassword = "myuniquepassword" $SecurePassword = ConvertTo-SecureString -String $APIPassword -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential $APIUserID, $SecurePassword Start-Transcript -Path ("C:\DNSLogs\" + $LogFileName) Update-GoogleDynamicDNS -credential $Credential -domainName "boatmurder.net" -subdomainName "dwarf" -Verbose #Clean up old log files Get-ChildItem -Path "C:\DNSLogs" | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | Remove-Item -Verbose Stop-Transcript
In the script above, I’m doing a couple other things outside of the main function. First, I’m manually creating a PSCredential object with a clear text username and password. That’s probably not the best way to do it, but this is a quick example. For logging purposes, I am using Start-Transcript to capture all terminal output and saving a new transcript each time the task runs so I can review output for errors if I need to. Finally, to make sure we don’t fill up my hard drive with log files, the last part of the script looks in the directory where I’m storing the transcript files and purges anything over 30 days old.
Next, I created a Windows scheduled task to call powershell.exe with a parameter equal to the script name, and set it to run every hour. That way, if my IP changes because my cable modem or router goes offline at most I’ll have an hour until the script runs and updates my DNS entry.
Granted, if my power goes out and my computer is off, all bets are off (but that’s what UPS systems are for, right?)
I hope the code and examples here can help you set up something as useful as a light weight Dynamic DNS service updater on a machine you use every day. Part of the reason I made this a module was in an effort to add more Google DNS API features eventually, but I felt this was a pretty good start. Feel free to review my code and make changes. I’d love to get a pull request! And let me know what you think of the module if you grab it from the PowerShell gallery as well.
Pingback: Dynamic DNS And Powershell – Curated SQL