Hey, Scripting Guy! Question

Hey, Scripting Guy! I know I can use the DNS WMI provider and query a DNS server. But what if I am not using a Microsoft DNS server? Or what if I do not have admin rights on the DNS server? What happens then? I really do not want to resort to parsing the output from NSLookup. Can you hook me up?

- BP

SpacerHey, Scripting Guy! Answer

Hi BP,

You know, "Hook Me Up" was the second album by that Australian band, The Veronicas. It is actually pretty good. They're not the Spice Girls, but then who is? Anyway, the problem with parsing NSLookup is that it returns string data and not an object. The problem with the DNS WMI provider, as you so adeptly put it, is that you need both rights and access, which is unlikely on the Internet.

This week we are talking about DNS. For more information about DNS, you can refer to the Windows Server 2008 Networking and Network Access Protection (NAP) book in the Windows Server 2008 Resource Kit. There is also a chapter on scripting network services in the Windows PowerShell Scripting Guide. A number of good VBScript examples are in the Script Center Script Repository. The WMI DNS provider is documented here on MSDN.

To allow us to query and to retrieve DNS information from the Internet or from our own local network for that matter, we can use the System.Net.DNS .NET Framework class. An example of using this class is seen in the Get-DnsEntry.ps1 script:

Function Get-DnsEntry($iphost)
{
 If($ipHost -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
  {
    [System.Net.Dns]::GetHostEntry($iphost).HostName
  }
 ElseIf( $ipHost -match "^.*\.\.*")
   {
    [System.Net.Dns]::GetHostEntry($iphost).AddressList[0].IPAddressToString
   } 
 ELSE { Throw "Specify either an IP V4 address or a hostname" }
} #end Get-DnsEntry

Get-DnsEntry "Vista.NwTraders.Com"

Our script consists of a single function. To create the function, we use the Function keyword and give it a name. A good name for a script that retrieves DNS entry information is Get-DnsEntry. The function accepts a single parameter, which we call $iphost. This is seen here:

Function Get-DnsEntry($iphost)
{

Rather than creating two separate functions, we decided to create a single function that would allow you to supply an IP address and receive a corresponding host name, or supply a hostname and retrieve an IP address. We further decided to use regular expressions to decide if you had supplied an IP address or a host name.

We have several “Hey, Scripting Guy!” articles in which we talk about regular expressions. We also have a pretty good introduction to using regular expressions.

We are interested in the result of a pattern match, but only if the match was found or not. In other words, we are looking for a Boolean return value: yes/no, true/false, that kind of thing. We do not care where the match was found, only if it was found. This makes our logic a bit easier. Our regular expression pattern uses several special characters. These are listed in Table 1.

That is it. All the special characters we use in our regular expression patterns in our Get-DnsEntry.ps1 script are seen there in Table 1. We repeat the pattern several times. This pattern will actually match anything between 0.0.0.0 and 999.999.999.999, so it does not validate good IP addresses or eliminate bad IP addresses; however, it will detect if something is an IP address.

When I am trying to create a new regular expression pattern, I start with a simple pattern and I work directly at the Windows PowerShell console. To match the number 1, I want to begin at the beginning of the line, and look for a digit. I will use the caret (^), followed by the \d. This is seen here:

PS C:\> 1 -match "^\d"
True

To match the number 1 followed by a period, I add the \. as seen here:

PS C:\> "1." -match "^\d\."
True

To find three numbers in the first position, we add the {1,3} operator:

PS C:\> 111 -match "^\d{1,3}"
True

But what happens if someone adds in four numbers: Will we get a false reading?

PS C:\> 1111 -match "^\d{1,3}"
True

As a matter of fact, we do not because the first three positions have numbers, and we said nothing about the end. After we add in our period, we now get a false match. When we test it with three numbers followed by a period and an additional number, we once again get a match.

PS C:\> "1111" -match "^\d{1,3}\."
False
PS C:\> "111.1" -match "^\d{1,3}\."
True

We continue in this manner, until we come to the end of the pattern, which we specify as ending in 1 to 3 numbers. The $ sign matches the end. Our completed regular expression pattern match is seen here:

If($ipHost -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")

If the value of $ipHost makes it past our regular expression pattern match, we call the static GetHostEntry method, and return only the HostName property from the returned object. To use the System.Net.Dns .NET Framework class, we put the name of the class in square brackets, use two colons (because this is a static method), call the method name, and supply it the value of the $iphost variable:

{
    [System.Net.Dns]::GetHostEntry($iphost).HostName
  }

If the value of $ipHost makes it past our regular expression pattern match, we call the static GetHostEntry method, and return only the HostName property from the returned object. To use the System.Net.Dns .NET Framework class, we put the name of the class in square brackets, use two colons (because this is a static method), call the method name, and supply it the value of the $iphost variable:

{    [System.Net.Dns]::GetHostEntry($iphost).HostName  }

If the $ipHost value does not match the IP address regular expression pattern, we see if it matches another pattern. This pattern looks for any character .* followed by a single period and then any other number of characters. This will match domain names with dashes, numbers, and letters in them. But it must have at least one period in the name. If a host name was passed in the $iphost variable, we obtain the AddressList property, and then retrieve the IPAddressToString property. Because the AddressList property is an array, we need to index into the array to retrieve a single value prior to converting it to a string. On many systems, this approach will be fine, but on Windows Vista and later the first IP address will be IPv6 and the second one IPv4. This is seen in this image:

Image of an IPv6 address

 

After we are done looking at the IP address that was submitted, we examine the host name to ensure it has a period in the name, has characters separated by a period, and has some more characters. This ensures that the dotted notation familiar to virtually all users of the internet is contained in the $IPHost variable. The command will kick out an attempt to resolve a simple netbios name:

ElseIf( $ipHost -match "^.*\.\.*")
   {
    [System.Net.Dns]::GetHostEntry($iphost).AddressList[0].IPAddressToString
   }

When the value of $iphost does not match our IP address pattern or our host name pattern, we throw an error and state that the user needs to supply the appropriate value to the function:

ELSE { Throw "Specify either an IP V4 address or a hostname" }
} #end Get-DnsEntry

The entry point to the script calls the Get-DnsEntry function and passes a hostname value to it. You can replace this line with one that takes command-line arguments or make whatever types of modifications to the function you wish. The entry point is seen here.

Get-DnsEntry "Vista.NwTraders.Com"

Well, BP, we have come to the end of the Get-DnsEntry.ps1 script. We finished a little early, so perhaps we have time to go and watch Spice World.(Okay, maybe not. I’ll probably wait another decade to watch it again.) This also concludes our DNS Week articles. See you tomorrow for Quick-Hits Friday. See you tomorrow.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys