14 July, 2014

Check DNS server IPs on NICs remotely - OS

This will be a quick post. I've come across an issue - while you assume that configuration management on loads of hosts is important and usually managed well, you can never be sure. Let's say I have some DNS servers and I'm replacing them with new ones, hopefully all other servers - in one way or another - will pick up the new DNS IPs and drop the old ones on all of their network interfaces. I just had a bad feeling about this...felt tremor in The Force.

I wanted a quick report on a list of servers and see what DNS IPs they used. At the same time - if I was writing some PowerShell code already why not check if those DNS IPs were alive.

So here is what needed to be done:

Enumerate DNS IPs of all interfaces

This is the easy bit, just use WMI and take all data from all adapters which are IP enabled and have a default gateway defined.
[array]$InterfaceList = gwmi win32_networkadapterconfiguration -ComputerName $srv | ?{$_.ipenabled -ieq "true" -and $_.DefaultIPGateway}

Read DNS IPs from each interface

This will be in the DNSServerSearchOrder property and you can assume it will be an array with as many IPs as many DNS servers the interface has. In my case, I know I have 3 everywhere so I might just hard code it into a loop to look at 3 elements:
for ($i = 1; $i -le 3; $i++){
$script:obj.("DNS_" + $i) = $interface.DNSServerSearchOrder[$i]
}

Verify the given DNS IP to see if it's a live DNS server

This is the tricky bit. Not impossible though.. there are ugly solutions and some uglier ones. Why? The nice native method would be to perform a DNS lookup against the DNS server by using the System.Net.Dns class. The problem is that this class will always use the DNS server set on your local host and you can't specify one like with nslookup.

So an ugly solution would be to run nslookup against the given DNS server and parse its text output. Or you could also change your DNS server on the local host on the fly and use System.Net.Dns. Another one is to see if UDP port 53 is open, it's at least 30 lines of code because as UDP is not a handshaking protocol, you need to setup a System.Net.Sockets.Udpclient, set encoding, initiate connection, handle errors, false positives...etc.
I decided to go with an easier one. By default, all DNS servers have the TCP port 53 open as well for large queries and zone transfers.  That's much easier to test:
$Socket = New-Object System.Net.Sockets.TCPClient
$Connect = $Socket.BeginConnect($srv,53,$null,$null)
$Wait = $Connect.AsyncWaitHandle.WaitOne(3000,$false)

The below script takes a list of hosts from the pipe, enumerates 3 DNS IPs of each interface and checks if those DNS servers are alive and then lists the output in an object collection.

 param ( [string] $log = "",  
  [string] $hosts = "")  
   
   
   
 #### Function for checking if a TCP port is opened on a host  
 function TCPportCheck ([string]$fsrv, [int]$port) {  
  $Socket = New-Object System.Net.Sockets.TCPClient  
  $Connect = $Socket.BeginConnect($fsrv,$port,$null,$null)  
  Start-Sleep 1  
  if($Connect.IsCompleted){  
  $Wait = $Connect.AsyncWaitHandle.WaitOne(3000,$false)    
  if(!$Wait){  
   $Socket.Close()   
   return $false   
  }   
  else{  
   $Socket.EndConnect($Connect)  
   $Socket.Close()  
   return $true  
  }  
  }  
  else{  
  return $false  
  }  
 }  
   
   
 $script:objColl = @()  
   
 #### Collate the host list.  
 $hostlist = @($Input)  
 if ($hosts) {  
  if($hosts -imatch " "){  
  $hostsArr = @($hosts.split(" "))  
  $hostlist += $hostsArr  
  }  
  else{  
  $hostlist += $hosts  
  }  
 }  
   
 foreach ($srv in $hostlist) {  
    
  # read list of interfaces  
  [array]$InterfaceList = gwmi win32_networkadapterconfiguration -ComputerName $srv | ?{$_.ipenabled -ieq "true" -and $_.DefaultIPGateway}  
    
  # go through each interface and read DNS IPS  
  if($InterfaceList.Count -gt 0){  
  foreach ($interface in $interfaceList){  
     
   #then check the DNS accessibility and lookup the DNS server name.  
   $script:obj = "" | select ComputerName,Access,InterfaceName,DNS_1,DNS_1_check,DNS_2,DNS_2_check,DNS_3,DNS_3_check,Result  
   $script:obj.ComputerName = $srv  
   $script:obj.Access = "OK"  
   $script:obj.InterfaceName = $interface.Description  
   $tmpResult = $null  
     
   for ($i = 0; $i -le 2; $i++){  
   $tmpIP = $tmpName = $null  
   $tmpIP = $interface.DNSServerSearchOrder[$i]  
     
   $tmpName = [System.Net.Dns]::GetHostByAddress($tmpIP).HostName  
   $script:obj.("DNS_"+($i+1)) = $tmpIP + " ($tmpName)"  
     
   if((TCPportCheck $tmpName 53)){  
    $script:obj.("DNS_"+($i+1)+"_Check") = "DNS port open"  
   }  
   else{  
    $script:obj.("DNS_"+($i+1)+"_Check") = "DNS port closed"  
    $tmpResult = "One or more DNS servers are not accessible"  
   }  
   }  
   if($tmpResult) {$script:obj.Result = $tmpResult}  
   else  {$script:obj.Result = "OK"}  
   $script:objColl += $script:obj  
  }  
  }  
 }  
   
 $script:objColl  
   


t