PowerShell and DNS Reverse Lookup Zones

Welcome back! I know you've missed me

Today, I wanted to talk about PowerShell and DNS. I have a customer who is looking to clean up records which have grown out of control over the years. This customer has many reverse lookup zones, like any company which has various offices throughout the world. They were looking for a way to use PowerShell to pull all the old records, so they could verify them before going in and deleting the records.

Oh, and before we get too much further into this, there is some code being used in this project, so…

*DISCLAIMER*

This Sample Code is provided for illustration purposes only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys' fees, that arise or result from the use or distribution of the Sample Code.

Moving on!

This customer did not have scavenging turned on, which is not uncommon, but would have prevented the need to clean up mismatching and old records from happening. Scavenging is a setting on Windows DNS servers which will clean out records if the servers have not checked in after a specific length of time. Here's a blog on how to configure it through the GUI. That blog is a little old, so here's the setting on a Windows Server 2012 R2, being set at the zone level:

 

But that's not the point of this post! The point is, we needed to get a list of all the records, exported in a way that would be easy for us to understand and manipulate.

I found this article in the Microsoft scriptcenter, which was a great starting point, and did almost everything the customer needed. My first step was to get the data where we could read it. This script did that, and pretty much nothing else ;p

A few quick notes:

  • all the DNS PowerShell commands require an Administrator PowerShell prompt. Failure to do so will get you a Permission Denied error.
  • You must have the DnsServer module loaded to get these commands to work
  • I've been using PowerShell version 4 on Windows Server 2012 R2

 

#Get all the records

$records
=
Get-DnsServerResourceRecord
-ZoneName
contoso.com
-RRtype
A

 

#Filter out dynamic records

$records
=
$records
|
where
Timestamp
-ne
$null

 

#Filter out records with Timestamp newer than 1/1/2018

$records
=
$records
|
where
Timestamp
-le
1/1/2018
|
Out-GridView

 

#Get reverse zone

$reverse
=
Get-DnsServerResourceRecord
-ZoneName
10.168.192.in-addr.arpa
-RRtype
PTR

 

#Filter out dynamic records

$reverse
=
$reverse
|
where
Timestamp
-ne
$null

 

#Filter out records with Timestamp newer than 1/1/2018

$reverse= $reverse
|
where
Timestamp
-le
1/1/2018
|
Out-GridView

 

 

After taking a short break, we decided that what we really needed was to get all the reverse lookup zones, without having to call them one by one. That should be easy, we should just be able to pull all the zones into a variable and then a foreach loop and export it out. Easy peasy!

Should is such a great word.

First objective was to get just the reverse lookup zones. I knew I was going to need to use the Get-DnsServerZone cmdlet, but exactly how was I going to get just the reverse zones, and all of them?

A little poking around and a little trial and error bought me to pull this command, putting all the reverse lookup zones into a variable ($revZones):

$revZones
= (Get-DnsServerZone
|
Where-Object {$_.isReverseLookupZone -eq $true}).ZoneName

 

Test lab output:

That's exactly what I want to see. Now, I only need to throw this into a foreach loop and we'll be golden. Here's my first try at a loop:

foreach ($zone
in
$revZones){

$records
=
Get-DnsServerResourceRecord
-ZoneName
$zone

$list
+=
$records

 

}

That's full of a lot of stuff I'm not interested in, so a small adjustment of adding -RRType
Ptr

will get me just the records I want.

Much better output:

Now I'm going to just send it all to Out-GridView and we should be good. I chose Out-GridView because I wanted a quick view of my output in something that I could manipulate and export if I wanted to. Eventually we'll push the data to a .csv file.

 

foreach ($zone
in
$revZones){

$records
=
Get-DnsServerResourceRecord
-ZoneName
$zone
-RRType
Ptr

$list
+=
$records

}

$list
|
Out-GridView

 

Output:

Almost what I'm looking for. That HostName is not enough information though. You can see several of my servers have the same HostName, but are clearly not the same server. What's missing here is the rest of the address. As they're all in different reverse lookup zones, I need to be able to see that here, or the data is useless. In addition to not having all the information I need, it's got more than I care for as well. I filter on PTR records, so I don't need the record type, and for my purposes now I don't really need TimeToLive either.

So, I do a select
*
and pull up all the possible data that I can choose from.

It looks like I want DistinguishedName to pull the full IP address, but now I see something else weird.

Look at the two RecordData columns. What just happened?

That's definitely not what I'm looking for. I need my server names! Since they showed up there once, I know they're in there somewhere, I just need to find them. Let's take a look at all RecordData has to offer.

Sounds like PSComputerName might be what we're looking for. Let's grab that value, and just the few other things we want for our final output and see what we get.

 

Nope! RecordData is blank! Time to try again. Perhaps the PtrDomainName will give us the information we're looking for.

There we go! Just what I was looking for!

Last thing to be done is to export it to a .csv file and we're all set. For my testing I use -NoTypeInformation
and
-Force to remove the first line of type information from the file and to overwrite the file. I tend to run lots of tests before I'm finally happy with my results and changing the name each time or getting an error because the file already exists from my previous testing, and that annoys me. When I'm satisfied with the result, I won't want to overwrite the file, but for testing I prefer to blast through files as needed.

Here's the final script and a sample of the .csv file.

$list
=
$null

 

# retrieve all Reverse Lookup Zones

$revZones
= (Get-DnsServerZone
|
Where-Object {$_.isReverseLookupZone -eq $true}).ZoneName

 

# retrieve all PTR records from zones

foreach ($zone
in
$revZones){

$records
=
Get-DnsServerResourceRecord
-ZoneName
$zone
-RRType
Ptr
|
Select
DistinguishedName, Timestamp,@{name='RecordData';Expression={$_.RecordData.PtrDomainName}}

$list
+=
$records

 

}

 

# Export the compiled list

$list
|
Out-GridView

$list
|
Export-Csv
-Path
"C:\users\testadmin\Desktop\zones.csv"
-NoTypeInformation

 

 

Output:

 

That's it for now! I'll be working to refine this more, cleaning up the data a little, and doing a compare against forward lookup zones. That one will take a bit longer, but I'm it will be an adventure!