Wednesday, December 8, 2010

Changing the target of a CNAME RR in DNS with Powershell

In your everyday sysadmin life there are situations where you could need a cheap solution for your infrastructure.

Typically if you host a service that doesn't tolerate availability disruption... it doesn't mean that you have the budget to install two pricey Windows 2008 Enterprise or Datacenter editions to build a cluster. This is exactly what happened to me. My company told me to put in place an HA solution for our file sharing solution but not to use a Cluster for the hostname resource, but just to stick to the Windows 2008 Standard Edition.

I have then decided to just implement the fileshare on two replicated servers and define an alias in the DNS which can be quickly moved from one backend server to another. So, if I lose a fileserver I would just have to check replication status and then update the CNAME's target fully qualified domain name (FQDN) field.

Knowing that using WMI it is possible to modify any existing record hosted on a Microsoft DNS Server, I decided to develop a Powershell script which does the job on-the-fly without me connecting to DNS interface, modifying the record, checking the TTL and issuing a ping to verify that the modification has been correctly replicated to other DNS server and to clients.

Here's my solution. It consists of:

  • A first part where I define variables such as the primary DNS server, the CNAME RR to update, the list of possible targets, the TTL. Yes, the TTL. When it comes to using the CNAME as a failover or disaster recovery tool, a shorter TTL then the default one (1 hour) should be used. In my case I choosed to set the TTL to just 30 seconds but you can adapt this to your needs in terms of availability.
  • A second part where I check the actual CNAME target by issuing a nslookup via powershell.
  • A third part where I query the DNS server and I update via the "Put" method the RecordData field, which corresponds to the target behind the alias.
  • A fourth part where I update the TTL. This cannot be done with the "Put" method. You have to use the "Modify" method. The "Modify" method updates a Canonical Name (CNAME) Resource Record and the first parameter it takes is the TTL in seconds.
  • A fifth and last part where I wait for the RR to be deleted from the cache and I issue a new nslookup to check that the modification has been correctly applied to the alias.

Here's the full Powershell code:

  1. #This is your primary DNS server:  
  2. $DNS_SERVER=""   
  3. write-host "DNS Server: "$DNS_SERVER  
  4. #This is the alias you want to update:  
  5. $CNAME_ALIAS=""   
  6. write-host "Alias to modify: "$CNAME_ALIAS  
  7. #Setup the DNS query:  
  8. $nslookup_test1 = "nslookup " + $CNAME_ALIAS   
  9. #Check the target with nslookup before changing:  
  10. $nslookup_result1 = Invoke-Expression ($nslookup_test1)   
  11. #Retrieve the solved HOST record:  
  12. $solved_A_before1 = $nslookup_result1.SyncRoot[3]   
  13. write-host "It was resolved as: "$solved_A_before1.substring(9,$solved_A_before1.Length-9)  
  14. #This is the first of your backend redundant nodes:  
  15. $NODE_A=""   
  16. #This is the second of your backend redundant nodes:  
  17. $NODE_B=""   
  18. #This is the time, in seconds, that you want the RR to be  
  19. #cached before a new DNS request is sent out by your clients:  
  20. $CNAME_TTL="30"   
  21. #We use the MicrosoftDNS_CNAMEType which is the subclass for CNAMEs:  
  22. $Query_CNAME="Select * from MicrosoftDNS_CNAMEType"   
  23. $record_CNAME = Gwmi -Namespace "root\microsoftdns" -Query $Query_CNAME -ComputerName $DNS_SERVER | ?{$_.Ownername -match $CNAME_ALIAS}  
  24. write-host "It was resolved as (WMI version): "$record_CNAME.RecordData  
  25. write-host "TTL to set: "$CNAME_TTL " seconds"  
  26. if($record_CNAME.RecordData -match $NODE_A){$NEW_TARGET=$NODE_B}else{$NEW_TARGET=$NODE_A}  
  27. $record_CNAME.RecordData = $NEW_TARGET  
  28. write-host "New target to set: "$NEW_TARGET  
  29. #Set the new target for your alias:  
  30. $record_CNAME.put() | out-null   
  31. $record_CNAME = Gwmi -Namespace "root\microsoftdns" -Query $Query_CNAME -ComputerName $DNS_SERVER | ?{$_.Ownername -match $CNAME_ALIAS}  
  32. #Set the TTL for the alias if it's changed:  
  33. if($record_CNAME.TTL -eq $CNAME_TTL){}else{$record_CNAME.modify($CNAME_TTL)|out-null}   
  34. Write-Host "Sleeping one second more than TTL so the RR is deleted from the cache..."  
  35. #This is to convert the TTL from string to int32:  
  36. $TTLint = [System.Convert]::ToInt32($CNAME_TTL)   
  37. #Sleep 1 second more the TTL:  
  38. sleep (1 + $TTLint)   
  39. #Setup the DNS query:  
  40. $nslookup_test2 = "nslookup " + $CNAME_ALIAS   
  41. #Check the target after changing:  
  42. $nslookup_result2 = Invoke-Expression ($nslookup_test2)  
  43. #Retrieve the solved HOST record:  
  44. $solved_A_after2 = $nslookup_result2.SyncRoot[3]   
  45. write-host "It is now resolved as: "$solved_A_after2.substring(9,$solved_A_after2.Length-9)  
  46. Write-Host "New TTL set to:    " $CNAME_TTL " seconds"  
  47. write-host "Done!"  

I hope this helps. Do not hesitate to ask questions if something is unclear or if you want to suggest improvements to this script.

A few useful resources:


  1. Excellent well written article. I took little bits here and there and... Voila a working solution for my rig.
    Cheers for the insight.
    Just one thing I don't believe "modify" is now needed to set the TTL.

    1. Thanks for commenting and for the suggestions on TTL!! It would be nice if you posted the relevant parts of your script if you like.


Related Posts Plugin for WordPress, Blogger...