Preparing To Decommission Windows DNS Servers

Today I want to write about a few of the tricks I’ve picked up for migrating DNS servers in a Windows AD environment with minimal pain and unexpected service interruptions due to forgotten devices that were pointing to the soon to be gone DNS server. The scenario is that you’ve provisioned a new DC/DNS server and now you need to demote and decommission the old one. Changing DHCP scopes is easy, but there will inevitably be statically configured devices and appliances which will need to be updated. Locating and changing all of those can be a challenge, and that is what we’re going to address.

We’re going to split this into two sections

  1. Changing DNS servers on statically configured Windows devices
  2. Detecting unknown devices that are still pointing to the DNS server

Updating DNS servers with PowerShell

We’re going to be using a PowerShell script to update DNS servers across our environment, but we there are a few considerations.

  • If the system has DNS set dynamically (via DHCP) we want to leave it alone - we don’t want to be setting these to static addresses.
  • We also need to decide whether we want to maintain the preferred and alternate order, or whether we wish to set our new DNS server as the preferred, regardless of whether it is currently preferred or not.

Finding systems with statically set DNS servers is a little tricker than one may imagine as it’s possible for the IP address, subnet mask, and default gateway to be allocated via DHCP, but for DNS servers to be set statically.

DHCP set IP with static DNS

We need to be a bit creative with detecting these systems as we can’t use the PrefixOrigin or SuffixOrigin properties of Get-NetIPAddress, nor the DHCPEnabled property of Get-CimInstance Win32_NetworkAdapterConfiguration. Luckily there are registry values we can use to determine how DNS servers are configured.

Here is the PowerShell function we’ll be using for detecting and updating statically configured DNS servers:

function Update-DNSServers {
    [Cmdletbinding()]
    Param(
        [Parameter(Mandatory,Position=0)]
        [ValidateScript({$_ -eq ([IPAddress]$_).IPAddressToString})]
        [string]$OldDNSServerIP,
        [Parameter(Mandatory,Position=1)]
        [ValidateScript({$_ -eq ([IPAddress]$_).IPAddressToString})]
        [string]$NewDNSServerIP,
        [Parameter(,Position=2)]
        [switch]$SetAsPreferred = $false
    )

    # Determine if any adapters are using the old server IP
    $OldIPAdapters = Get-DnsClientServerAddress -AddressFamily IPv4 | ? ServerAddresses -eq $OldDNSServerIP

    if ($null -ne $OldIPAdapters) {
        # Determine if DNS IP was statically or dynamically assigned

        $KeyPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\__DeviceID__"

        $OldIPAdapters | % {
            $DeviceID = Get-NetAdapter -ifIndex $_.InterfaceIndex | select -ExpandProperty DeviceID
            $FullKeyPath = $KeyPath.Replace('__DeviceID__', $DeviceID)

            $IntDNSSettings = Get-ItemProperty -Path $FullKeyPath -Name 'DhcpNameServer', 'NameServer'

            if ($IntDNSSettings.NameServer.Length -gt 0) {
                # DNS Server is statically set and should be updated
                if ($SetAsPreferred -eq $true) {
                    Get-DnsClientServerAddress | ? ServerAddresses -eq  $OldIP | % {$DNSServers = @($NewIP); $DNSServers += $_.ServerAddresses | ? {$_ -ne $OldIP}; $_ | Set-DnsClientServerAddress -ServerAddresses $DNSServers}
                    $NewDNSServers = @($NewDNSServerIP)
                    $NewDNSServers += $_.ServerAddresses | ? {$_ -ne $OldDNSServerIP}
                } else {
                    $NewDNSServers = $_.ServerAddresses -replace $OldDNSServerIP, $NewDNSServerIP
                }
                $_ | Set-DnsClientServerAddress -ServerAddresses $NewDNSServers
            } 
        }
    }
}

We can use this in combination with WinRM or other management tools to automate updating statically set DNS server throughout the environment. Below is a WinRM example.

Invoke-Command -ComputerName "srv-lab1","srv-lab2","srv-lab3" -Credential $AdminCred -ScriptBlock ${Function:Update-DNSServers} -ArgumentList "10.250.10.20","10.250.10.25"

While this example shows a list of computer names for simplicity, typically this would be pulled from AD or a CSV from an asset register and then piped into Invoke-Command.

Let’s see this is action. Here we’ve got a server with two statically configured DNS servers, 10.250.10.20 and 10.250.10.21. We want to replace 10.250.10.21 with 10.250.10.50 and set the new server as the preferred. To do this we’re going to append a third argument that ($true) to set the SetAsPreferred flag.

"srv-lab1" | % {Invoke-Command -ComputerName $_ -Credential $AdminCred -ScriptBlock ${Function:Update-DNSServers} ` -ArgumentList "10.250.10.21","10.250.10.50",$true}

Updating DNS servers with PowerShell

Detecting unknown devices that are using the old DNS server

Enabling DNS Logging via the GUI

Not everything on our network is going to be running Windows and we need a way to find these devices that are still pointing DNS to our soon to be decommissioned server.

We’re going to do this by enabling DNS Debug Logging and parsing the log file. Debug logging can be enabled via the GUI in the DNS Server properties using the following settings:

DNS Debug Logging

One thing to note that may not be immediately obvious is that I’ve removed two zeros from the default max size. The default size is 500MB, and our new size is 5MB.

Enabling DNS Logging with PowerShell

DNS logging can also be enabled with PowerShell

Set-DnsServerDiagnostics -Queries $true -QuestionTransactions $true -ReceivePackets $true -UdpPackets $true -TcpPackets $true -EnableLoggingToFile $true -MaxMBFileSize 5000000 -LogFilePath C:\Windows\Temp\dns.log

Notice the -MaxMBFileSize argument, the value we need to supply is actually in bytes (same as the GUI), not megabytes as the name may suggest.

Parsing the DNS log with PowerShell

The log format isn’t particularly friendly for parsing, but all we need to do is extract client IP addresses.

Windows DNS Log

We do the IP address extraction with a bit of PowerShell and regex.

(Get-Content C:\Windows\Temp\dns.log | Select-String '(\d{1,3})(\.\d{1,3}){3}' -AllMatches | Select-String -NotMatch "NOERROR\] SOA|NOERROR\] TKEY" | Select -ExpandProperty Matches).Value | Select -Unique

The output will be a list of unique IP addresses which are still querying the DNS server.

Parsing Windows DNS Log

I normally leave logging on for a day at a time, make changes to whatever devices are detected, then flush the log file and check it again in a day or so.

Disabling DNS logging

Should we need to turn off logging, we can uncheck all tick boxes in the GUI, or we can use PowerShell.

Set-DnsServerDiagnostics -All $false

That’s it for this one, I hope with these tips you’ll never again miss a device and need to deal with the inevitable customer call when things stop working.


If you enjoyed this post consider sharing it on , , , or , and .