# encoding: ascii
# api: powershell
# title: DNS functions
# description: copy, clean and dedup DNS zones, working code,
# version: 0.1
# type: script
# author: chriskenis
# license: CC0
# x-poshcode-id: 3659
# x-archived: 2014-06-16T03:04:31
# x-published: 2014-09-24T10:33:00
#
# needs extra code if you want additional DNS record types or zones
# 1 condition is to have at least one dns record type to compare with
#
Param (
[Parameter(Mandatory=$true, Position=1)][string] $SourceServer,
[Parameter(Mandatory=$true, Position=2)][string] $SourceZone,
[Parameter(Mandatory=$true, Position=3)][string] $DestinationServer,
[Parameter(Mandatory=$true, Position=4)][string] $DestinationZone,
[string[]] $RRtypes = @("MicrosoftDNS_AType"),
[switch] $Copy,
[switch] $Clean,
[switch] $Help
)
# script variables
$nl = [Environment]::NewLine
If ($Help){
Write-Host -ForegroundColor Green "
NAME
DNS_Functions
SYNOPSIS
Copies and/or cleans DNS records from one forward zone to another
SYNTAX
.\DNS_Functions SourceServer SourceZone DestinationServer DestinationZone Switches
EXAMPLES
Forward zone comparison report only:
.\DNS_Functions $SourceServer $SourceZone $DestinationServer $DestinationZone
Forward source zone clean up for CNAME records:
.\DNS_Functions.ps1 source_dnsserver source_zone.local destination_dnsserver destination_zone.local -RRtypes `"MicrosoftDNS_AType`",`"MicrosoftDNS_CNAMEType`" -Clean
DESCRIPTION
This script can both copy and clean DNS records by comparing any 2 DNS zones, either same or different FQDN
"
break
}
function GetDNSrecords($DNSserver, $DNSzone, $RRtype){
if ($RRtype -eq $null){$RRtype = "MicrosoftDNS_ResourceRecord"}
$DNSrecords = Get-WMIObject -Computer $DNSserver -Namespace "root\MicrosoftDNS" -Class $RRtype -Filter "ContainerName='$DNSzone'"
if ($DNSrecords){
Switch ($RRtype){
MicrosoftDNS_AType {
foreach ($rec in $DNSrecords){
# add simple name of dns record as extra property for comparison routine
Add-Member -InputObject $rec -MemberType NoteProperty -Name SimpleVal -Value $rec.OwnerName.Replace(".$DNSzone","")
}
}
MicrosoftDNS_CNAMEType {
foreach ($rec in $DNSrecords){
# add simple name of dns record as extra property for comparison routine
Add-Member -InputObject $rec -MemberType NoteProperty -Name SimpleVal -Value $rec.OwnerName.Replace(".$DNSzone","")
}
}
MicrosoftDNS_ResourceRecord {
# to be implemented
}
}
}
return $DNSrecords
}
function GetDNSrecord ($DNSserver, $DNSzone, $name, $RRtype){
$DnSrecord = Get-WMIObject -Computer $DNSserver -Namespace "root\MicrosoftDNS" -Class $RRtype -Filter "OwnerName='$name.$DNSzone'"
return $DNSrecord # returns the actual object for further processing
}
function GetDNSzones($DNSserver){
# server side filtering
$DNSzones = Get-WmiObject -ComputerName $DNSserver -Class MicrosoftDNS_Zone -Namespace root\microsoftDNS -Filter "ZoneType = 1 or ZoneType = 2"
# client side filtering
#$destDNSzone = Get-WmiObject -ComputerName $destServer -Class MicrosoftDNS_Zone -Namespace root\microsoftDNS | Where {$_.ZoneType -eq '1' -or $_.ZoneType -eq '2'}
return $DNSzones
}
function GetDNSzone ($DNSserver, $name){
$DnSzone = Get-WMIObject -Computer $DNSserver -Namespace "root\MicrosoftDNS" -Class MicrosoftDNS_Zone -Filter "Name='$name'"
return $DnSzone # returns the actual object for further processing
}
# just for debugging and reference
function DNSrecordInfo($DNSrecord){
$objOutput = New-Object PSObject -Property @{
$class = $DNSrecord.__CLASS # A, CNAME, PTR, etc.
$ownerName = $DNSrecord.OwnerName # Name column in DNS GUI, FQDN
$containerName = $DNSrecord.ContainerName # Zone FQDN
$domainName = $DNSrecord.DomainName # Zone FQDN
$ttl = $DNSrecord.TTL # TTL
$recordClass = $DNSrecord.RecordClass # Usually 1 (IN)
$recordData = $DNSrecord.RecordData # Data column in DNS GUI, value
$simpleval = $ownerName.Replace(".$domainName","")
}
return $objOutput
}
function CopyRecords($DNSrecords){
Write-Output "$($nl)$('Copying') $($DNSrecords.count) $('records to') $($DestinationServer)$($nl)"
$Succ = $Fail = 0
foreach ($DnSrecord in $DNSrecords){
Switch ($DNSrecord.__CLASS) {
MicrosoftDNS_AType {
$destRec = [WmiClass]"\\$DestinationServer\root\MicrosoftDNS:MicrosoftDNS_AType"
if ($Copy){
if ($DnSrecord.SimpleVal -eq $SourceZone){
# skip (same as parent) A records
Write-Output "$('copy of A:') $($DNSrecord) $('skipped')"
break
}
try{
$newrec = $DNSrecord.SimpleVal + "." + $DestinationZone
$destRec.CreateInstanceFromPropertyData($DestinationServer, $DestinationZone, $newrec, 1, $DNSrecord.TTL, $DNSrecord.RecordData) | out-null
$Succ ++
Write-Output "$('copy of A:') $($newrec) $('on') $($DestinationServer)"
}
catch{
Write-Output "$('copy of A:') $($newrec) $('failed')"
Write-Output "$($error[0])"
$Fail ++
}
}
}
MicrosoftDNS_CNAMEType {
$destRec = [WmiClass]"\\$DestinationServer\root\MicrosoftDNS:MicrosoftDNS_CNAMEType"
if ($Copy){
try{
$newrec = $DNSrecord.SimpleVal + "." + $DestinationZone
$crec = $DNSrecord.RecordData.Replace(".$SourceZone",".$DestinationZone")
$destRec.CreateInstanceFromPropertyData($DestinationServer, $DestinationZone, $newrec, 1, $DNSrecord.TTL, $crec ) | out-null
$Succ ++
Write-Output "$('copy of CNAME:') $($newrec) $('on') $($DestinationServer)"
}
catch{
Write-Output "$('copy of CNAME') $($newrec) $('failed')"
Write-Output "$($error[0])"
$Fail ++
}
}
}
default{
Write-Output "$('copy of:') $($DNSrecord.SimpleVal) $('failed') $('due to unknown record class')"
$Fail ++
}
}
}
Write-Output "$($nl)$($Succ) $('records copied')"
Write-Output "$($Fail) $('records failed')$($nl)"
}
function ScavengeRecords($DNSrecords){
$Deleted = $Dead = $Live = 0
foreach ($DnSrecord in $DNSrecords){
if (Test-Connection -ComputerName $DNSrecord.OwnerName -count 1 -ea silentlycontinue) {
write-output "$($DNSrecord.SimpleVal) $('is still alive')"
$Live ++
}
else{
write-output "$($DNSrecord.SimpleVal) $('cannot be reached')"
$Dead ++
if ($Clean){
try{
$DNSrecord.delete()
$Deleted ++
}
catch{}
}
}
}
Write-Output "$($nl)$($Deleted) $('records scavenged')"
Write-Output "$($Live) $('records were live')"
Write-Output "$($Dead) $('records were dead')$($nl)"
}
function DedupDNSzone ($srcRecords, $dstRecords){
# return only records from reference (= source zone) found in both DNS zones
$Records2Dedup = compare-object $srcRecords $dstRecords -IncludeEqual -ExcludeDifferent -Property SimpleVal -PassThru
Write-Output "$($nl)$('deleting duplicate records from') $($SourceZone)"
try{
$Records2Dedup | %{ $_.delete() }
Write-Output "$($Records2Dedup.count) $('duplicate records processed')$($nl)"
}
catch{
Write-Output "$('failure deleting record(s)')$($nl)"
}
}
function CleanDNSzone($srcRecords, $dstRecords){
# return unique records in source zone only
$Records2Clean = compare-object $srcRecords $dstRecords -Property SimpleVal -PassThru | Where-Object { $_.SideIndicator -eq '<=' }
ScavengeRecords $Records2Clean
}
function CopyDNSzone ($srcRecords, $dstRecords){
# return unique records in source zone only
$Records2Copy = compare-object $srcRecords $dstRecords -Property SimpleVal -PassThru | Where-Object { $_.SideIndicator -eq '<=' }
CopyRecords $Records2Copy
}
function CompareDNSservers($srcDNSzone ,$destDNSzone){
$dest = $src = $equ = 0
$zones = compare-object $srcDNSzone $destDNSzone -Property name -IncludeEqual
if ($zones -ne $null){
foreach ($zone in $zones){
switch ($zone.SideIndicator){
'=>' {
write-output "$($zone.Name) $('only exists on destination:') $($DestinationServer)"
$dest ++
}
'<=' {
write-output "$($zone.Name) $('only exists on source:') $($SourceServer)"
$src ++
}
'==' {
write-output "$($zone.Name) $('exists on both source and destination')"
$equ ++
}
}
}
write-output "$($nl)$($dest) $('zones on') $($DestinationServer) $('only')"
write-output "$($src) $('zones on') $($SourceServer) $('only')"
write-output "$($equ) $('zones on both source and destination')$($nl)"
}
}
function CompareDNSzone($srcRecords, $dstRecords){
$dest = $src = $equ = 0
$records = compare-object $srcRecords $dstRecords -IncludeEqual -Property SimpleVal
if ($records -ne $null){
foreach ($record in $records){
switch ($record.SideIndicator){
'=>' {
write-output "$($record.SimpleVal) $('exists only in:') $($DestinationZone)"
$dest ++
}
'<=' {
write-output "$($record.SimpleVal) $('exists only in:') $($SourceZone)"
$src ++
}
'==' {
write-output "$($record.SimpleVal) $('exists in both zones')"
$equ ++
}
}
}
write-output "$($nl)$($dest) $('records in') $($DestinationZone) $('on') $($DestinationServer) $('only')"
write-output "$($src) $('records in') $($SourceZone) $('on') $($SourceServer) $('only')"
write-output "$($equ) $('records in both source and destination')$($nl)"
}
}
function GetRecordType($Class){
# return human readable record type :-)
Switch ($Class) {
MicrosoftDNS_AAAAType {return "AAAA"}
MicrosoftDNS_AFSDBType {return "AFSDB"}
MicrosoftDNS_ATMAType {return "ATMA"}
MicrosoftDNS_AType {return "A"}
MicrosoftDNS_CNAMEType {return "Cname"}
MicrosoftDNS_HINFOType {return "H Info"}
MicrosoftDNS_ISDNType {return "ISDN"}
MicrosoftDNS_KEYType {return "Key"}
MicrosoftDNS_MBType {return "MB"}
MicrosoftDNS_MDType {return "MD"}
MicrosoftDNS_MFType {return "MF"}
MicrosoftDNS_MGType {return "MG"}
MicrosoftDNS_MINFOType {return "M Info"}
MicrosoftDNS_MRType {return "MR"}
MicrosoftDNS_MXType {return "MX"}
MicrosoftDNS_NSType {return "NS"}
MicrosoftDNS_NXTType {return "NXT"}
MicrosoftDNS_PTRType {return "PTR"}
MicrosoftDNS_RPType {return "RP"}
MicrosoftDNS_RTType {return "RT"}
MicrosoftDNS_SIGType {return "SIG"}
MicrosoftDNS_SOAType {return "SOA"}
MicrosoftDNS_SRVType {return "SRV"}
MicrosoftDNS_TXTType {return "Text"}
MicrosoftDNS_WINSRType {return "Wins R"}
MicrosoftDNS_WINSType {return "Wins"}
MicrosoftDNS_WKSType {return "WKS"}
MicrosoftDNS_X25Type {return "X25"}
}
}
# actual script execution
write-output "$('Comparison between') $($SourceServer) $('and') $($DestinationServer) $('for all regular DNS zones')$($nl)"
if ($SourceServer -ne $DestinationServer){
# arrays of DNS zones to compare
$srcZones = GetDNSzones $SourceServer
$destZones = GetDNSzones $DestinationServer
# output of DNS zones found on both servers
CompareDNSservers $srcZones $destZones
# run thru loop for all DNS record types in script parameter
}
else{write-output "$('Source and Destination server are the same')$($nl)"}
foreach ($RRtype in $RRtypes){
if (!(GetRecordType $RRtype)){write-warning "$RRtype is not a valid DNS resource type";continue}
write-output "$('Comparison between') $($SourceZone) $('on') $($SourceServer) $('and') $($DestinationZone) $('on') $($DestinationServer) $('for') $(GetRecordType $RRtype) $('records')$($nl)"
# arrays of DNS records to compare
$sourceRecords = (GetDNSrecords $SourceServer $SourceZone $RRtype)
$destinRecords = (GetDNSrecords $DestinationServer $DestinationZone $RRtype)
# check for empty array(s)
if ($sourceRecords -and $destinRecords){
# output of DNS records found in both zones
CompareDNSzone $sourceRecords $destinRecords
# clean duplicate records on the source server
DedupDNSzone $sourceRecords $destinRecords
# copy unique records from source to destination server
CopyDNSzone $sourceRecords $destinRecords
#CleanDNSzone $sourceRecords $destinRecords
}
else{write-output "$('no') $(GetRecordType $RRtype) $('records in either DNS zone')$($nl)"}
}
write-output "$('end script')$($nl)"