Windows DNS Name Server (NS) Record Management: Analysis & Reverse Lookup Zone Updates

Analyzing and Updating Name Server (NS) Records on Windows DNS Servers

Managing Windows DNS is crucial for ensuring that network infrastructures remain up-to-date and function correctly. The accuracy and timeliness of Name Server (NS) records are essential elements that must be frequently monitored. In this article, we will discuss how a script operates to analyze and update Name Server records on Windows DNS servers.

How Does the Script Work?

The developed script runs on Windows DNS servers and performs the following key operations:

  1. Scanning All Name Server Records:
    • When the “Analyze” button is pressed, the script scans all Forward Lookup Zone and Reverse Lookup Zone areas and lists the current Name Server (NS) records.
  2. Forward Lookup Zone and Reverse Lookup Zone Scanning:
    • It collects the existing Name Server records for the Forward Lookup Zone and Reverse Lookup Zone but does not make any modifications.
  3. Updating Reverse Lookup Zone:
    • When the “Update NS” button is pressed, the NS records in the Reverse Lookup Zone are checked.
    • Missing NS records are added.
    • Old and unused NS records are removed.
  4. Listing Old Name Server Records:
    • No updates are made for the Forward Lookup Zone, but outdated and unused NS records are displayed for the system administrator’s review.

Name Server Checker & Reverse Lookup Zone Update

# DNS Active NS Record Manager GUI Application

Add-Type -AssemblyName System.Windows.Forms

Add-Type -AssemblyName System.Drawing

# Create the main form

$form = New-Object System.Windows.Forms.Form

$form.Text = 'DNS Active NS Record Manager'

$form.Size = New-Object System.Drawing.Size(900,700)

$form.StartPosition = 'CenterScreen'

$form.FormBorderStyle = 'FixedDialog'

$form.MaximizeBox = $false

$form.BackColor = [System.Drawing.Color]::FromArgb(240,240,240)

# Create banner panel

$bannerPanel = New-Object System.Windows.Forms.Panel

$bannerPanel.Size = New-Object System.Drawing.Size(900,60)

$bannerPanel.Location = New-Object System.Drawing.Point(0,0)

$bannerPanel.BackColor = [System.Drawing.Color]::FromArgb(0,120,215)

$form.Controls.Add($bannerPanel)

# Create banner title

$bannerTitle = New-Object System.Windows.Forms.Label

$bannerTitle.Text = "DNS Active NS Record Management"

$bannerTitle.ForeColor = [System.Drawing.Color]::White

$bannerTitle.Font = New-Object System.Drawing.Font("Segoe UI", 16, [System.Drawing.FontStyle]::Regular)

$bannerTitle.AutoSize = $true

$bannerTitle.Location = New-Object System.Drawing.Point(20,15)

$bannerPanel.Controls.Add($bannerTitle)

# Create main panel for controls

$mainPanel = New-Object System.Windows.Forms.Panel

$mainPanel.Size = New-Object System.Drawing.Size(860,580)

$mainPanel.Location = New-Object System.Drawing.Point(20,80)

$mainPanel.BackColor = [System.Drawing.Color]::White

$form.Controls.Add($mainPanel)

# Create DNS Server label and textbox

$lblDNSServer = New-Object System.Windows.Forms.Label

$lblDNSServer.Location = New-Object System.Drawing.Point(20,20)

$lblDNSServer.Size = New-Object System.Drawing.Size(100,20)

$lblDNSServer.Text = 'DNS Server:'

$lblDNSServer.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$mainPanel.Controls.Add($lblDNSServer)

$txtDNSServer = New-Object System.Windows.Forms.TextBox

$txtDNSServer.Location = New-Object System.Drawing.Point(120,20)

$txtDNSServer.Size = New-Object System.Drawing.Size(200,20)

$txtDNSServer.Text = 'localhost'

$txtDNSServer.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$mainPanel.Controls.Add($txtDNSServer)

# Create Active NS Servers ListView

$lblActiveNS = New-Object System.Windows.Forms.Label

$lblActiveNS.Location = New-Object System.Drawing.Point(20,60)

$lblActiveNS.Size = New-Object System.Drawing.Size(150,20)

$lblActiveNS.Text = 'Active NS Servers:'

$lblActiveNS.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$mainPanel.Controls.Add($lblActiveNS)

$listActiveNS = New-Object System.Windows.Forms.ListView

$listActiveNS.Location = New-Object System.Drawing.Point(20,85)

$listActiveNS.Size = New-Object System.Drawing.Size(400,150)

$listActiveNS.View = [System.Windows.Forms.View]::Details

$listActiveNS.FullRowSelect = $true

$listActiveNS.GridLines = $true

$listActiveNS.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$listActiveNS.Columns.Add("NS Server", 380)

$mainPanel.Controls.Add($listActiveNS)

# Create button panel

$buttonPanel = New-Object System.Windows.Forms.Panel

$buttonPanel.Size = New-Object System.Drawing.Size(420,30)

$buttonPanel.Location = New-Object System.Drawing.Point(440,85)

$buttonPanel.BackColor = [System.Drawing.Color]::White

$mainPanel.Controls.Add($buttonPanel)

# Create Refresh button

$btnRefresh = New-Object System.Windows.Forms.Button

$btnRefresh.Location = New-Object System.Drawing.Point(0,0)

$btnRefresh.Size = New-Object System.Drawing.Size(100,30)

$btnRefresh.Text = 'Refresh'

$btnRefresh.FlatStyle = 'Flat'

$btnRefresh.BackColor = [System.Drawing.Color]::FromArgb(0,120,215)

$btnRefresh.ForeColor = [System.Drawing.Color]::White

$btnRefresh.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$btnRefresh.Cursor = [System.Windows.Forms.Cursors]::Hand

$buttonPanel.Controls.Add($btnRefresh)

# Create Analyze button

$btnAnalyze = New-Object System.Windows.Forms.Button

$btnAnalyze.Location = New-Object System.Drawing.Point(105,0)

$btnAnalyze.Size = New-Object System.Drawing.Size(100,30)

$btnAnalyze.Text = 'Analyze'

$btnAnalyze.FlatStyle = 'Flat'

$btnAnalyze.BackColor = [System.Drawing.Color]::FromArgb(0,120,215)

$btnAnalyze.ForeColor = [System.Drawing.Color]::White

$btnAnalyze.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$btnAnalyze.Cursor = [System.Windows.Forms.Cursors]::Hand

$buttonPanel.Controls.Add($btnAnalyze)

# Create Clean button

$btnClean = New-Object System.Windows.Forms.Button

$btnClean.Location = New-Object System.Drawing.Point(210,0)

$btnClean.Size = New-Object System.Drawing.Size(100,30)

$btnClean.Text = 'Update NS'

$btnClean.FlatStyle = 'Flat'

$btnClean.BackColor = [System.Drawing.Color]::FromArgb(0,120,215)

$btnClean.ForeColor = [System.Drawing.Color]::White

$btnClean.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$btnClean.Enabled = $false

$btnClean.Cursor = [System.Windows.Forms.Cursors]::Hand

$buttonPanel.Controls.Add($btnClean)

# Create Exit button

$btnExit = New-Object System.Windows.Forms.Button

$btnExit.Location = New-Object System.Drawing.Point(315,0)

$btnExit.Size = New-Object System.Drawing.Size(100,30)

$btnExit.Text = 'Exit'

$btnExit.FlatStyle = 'Flat'

$btnExit.BackColor = [System.Drawing.Color]::FromArgb(232,17,35)

$btnExit.ForeColor = [System.Drawing.Color]::White

$btnExit.Font = New-Object System.Drawing.Font("Segoe UI", 9)

$btnExit.Cursor = [System.Windows.Forms.Cursors]::Hand

$buttonPanel.Controls.Add($btnExit)

# Create RichTextBox for output

$txtOutput = New-Object System.Windows.Forms.RichTextBox

$txtOutput.Location = New-Object System.Drawing.Point(20,250)

$txtOutput.Size = New-Object System.Drawing.Size(820,310)

$txtOutput.ReadOnly = $true

$txtOutput.BackColor = [System.Drawing.Color]::FromArgb(30,30,30)

$txtOutput.ForeColor = [System.Drawing.Color]::White

$txtOutput.Font = New-Object System.Drawing.Font("Consolas", 10)

$txtOutput.BorderStyle = 'None'

$mainPanel.Controls.Add($txtOutput)

# Function to write colored output

function Write-ColorOutput {

    param([string]$Message, [string]$Color = "White")

    $txtOutput.SelectionColor = [System.Drawing.Color]::$Color

    $txtOutput.AppendText("$Message`n")

    $txtOutput.ScrollToCaret()

}

# Function to write separator line

function Write-Separator {

    Write-ColorOutput ("-" * 80) "DarkGray"

}

# Function to test if a server is online with simple checks

function Test-ServerConnection {

    param([string]$ServerName)

    try {

        Write-ColorOutput "Testing NS server: $ServerName" "Yellow"

        

        # Try to get IP address

        try {

            $ipAddress = [System.Net.Dns]::GetHostAddresses($ServerName) |

                        Where-Object { $_.AddressFamily -eq 'InterNetwork' } |

                        Select-Object -First 1

            

            if ($ipAddress) {

                Write-ColorOutput "  - IP Address: $($ipAddress.IPAddressToString)" "Gray"

            } else {

                Write-ColorOutput "  - Could not resolve IP address" "Red"

                return $false

            }

        } catch {

            Write-ColorOutput "  - DNS resolution failed" "Red"

            return $false

        }

        

        # Try ping

        $ping = Test-Connection -ComputerName $ServerName -Count 2 -Quiet

        if ($ping) {

            Write-ColorOutput "  - Ping successful" "Green"

            

            # Try basic DNS port check

            try {

                $tcp = New-Object System.Net.Sockets.TcpClient

                $tcp.SendTimeout = 2000

                $tcp.ReceiveTimeout = 2000

                $result = $tcp.BeginConnect($ServerName, 53, $null, $null)

                $success = $result.AsyncWaitHandle.WaitOne(2000, $false)

                if ($success) {

                    Write-ColorOutput "  - DNS port is accessible" "Green"

                    $tcp.EndConnect($result)

                    return $true

                } else {

                    Write-ColorOutput "  - DNS port is not accessible" "Red"

                    return $false

                }

            } catch {

                Write-ColorOutput "  - DNS port check failed" "Red"

                return $false

            } finally {

                if ($tcp) { $tcp.Close() }

            }

        } else {

            Write-ColorOutput "  - Ping failed" "Red"

            return $false

        }

    } catch {

        Write-ColorOutput "  - Connection test failed: $_" "Red"

        return $false

    }

}

# Function to validate NS server with simple logic

function Test-NSServerValidity {

    param([string]$NSServer)

    return Test-ServerConnection -ServerName $NSServer

}

# Function to get active NS records

function Get-ActiveNSRecords {

    $listActiveNS.Items.Clear()

    Write-ColorOutput "Loading active NS records..." "Yellow"

    Write-Separator

    

    try {

        # Get all zones (both forward and reverse)

        $allZones = Get-DnsServerZone -ComputerName $txtDNSServer.Text |

                   Where-Object { $_.ZoneName -ne "TrustAnchors" }

        

        $script:activeNSServers = @()

        $script:inactiveNSServers = @{}  # Track inactive NS servers and their source zones

        $potentialNSServers = @{}

        

        foreach ($zone in $allZones) {

            Write-ColorOutput "Checking zone: $($zone.ZoneName)" "Cyan"

            

            # Get NS records for each zone

            $nsRecords = Get-DnsServerResourceRecord -ZoneName $zone.ZoneName -RRType "NS" -ComputerName $txtDNSServer.Text |

                        Where-Object { $_.RecordType -eq "NS" }

            

            foreach ($ns in $nsRecords) {

                $nsHost = $ns.RecordData.NameServer.Trim('.')

                if (-not $potentialNSServers.ContainsKey($nsHost)) {

                    $potentialNSServers[$nsHost] = $zone.ZoneName

                }

            }

        }

        

        Write-ColorOutput "`nValidating NS servers..." "Yellow"

        Write-Separator

        

        foreach ($nsServer in $potentialNSServers.Keys) {

            Write-ColorOutput "Processing NS server from zone '$($potentialNSServers[$nsServer])'" "Cyan"

            

            if (Test-NSServerValidity -NSServer $nsServer) {

                if (-not $script:activeNSServers.Contains($nsServer)) {

                    $script:activeNSServers += $nsServer

                    $listActiveNS.Items.Add($nsServer)

                    Write-ColorOutput "Confirmed active NS: $nsServer" "Green"

                }

            } else {

                Write-ColorOutput "NS server validation failed: $nsServer" "Red"

                if (-not $script:inactiveNSServers.ContainsKey($nsServer)) {

                    $script:inactiveNSServers[$nsServer] = $potentialNSServers[$nsServer]

                }

            }

            Write-Separator

        }

        

        Write-ColorOutput "`nTotal active NS servers found: $($script:activeNSServers.Count)" "Cyan"

        Write-ColorOutput "Active NS Servers:" "Yellow"

        foreach ($ns in $script:activeNSServers) {

            Write-ColorOutput "  $ns" "White"

        }

        Write-Separator

        

    } catch {

        Write-ColorOutput "Error getting active NS records: $_" "Red"

        Write-Separator

    }

}

# Function to analyze DNS zones

function Analyze-ReverseLookupZones {

    Write-ColorOutput "Starting DNS zone analysis..." "Yellow"

    Write-Separator

    

    try {

        # Get all zones for information

        $allZones = Get-DnsServerZone -ComputerName $txtDNSServer.Text |

                   Where-Object { $_.ZoneName -ne "TrustAnchors" }

        

        # Get only reverse lookup zones for updates

        $reverseLookupZones = $allZones | Where-Object { $_.ZoneName -like "*.in-addr.arpa" }

        

        $script:inactiveNSCount = 0

        $script:analyzedZones = @{}  # Will only contain reverse lookup zones

        $script:zonesToUpdate = @{}  # Will only contain reverse lookup zones

        

        # First, show inactive NS servers from all zones (informational)

        Write-ColorOutput "`nInactive NS Servers in All Zones (Informational):" "Yellow"

        Write-Separator

        foreach ($inactiveNS in $script:inactiveNSServers.Keys) {

            Write-ColorOutput "Found inactive NS server: $inactiveNS" "Red"

            Write-ColorOutput "Originally detected in zone: $($script:inactiveNSServers[$inactiveNS])" "Yellow"

            Write-Separator

        }

        

        # Process all zones for information

        Write-ColorOutput "`nAnalyzing All Zones (Informational):" "Yellow"

        Write-Separator

        foreach ($zone in $allZones) {

            if ($zone.ZoneName -notlike "*.in-addr.arpa") {

                Write-ColorOutput "Checking zone: $($zone.ZoneName)" "Cyan"

                

                $nsRecords = Get-DnsServerResourceRecord -ZoneName $zone.ZoneName -RRType "NS" -ComputerName $txtDNSServer.Text |

                            Where-Object { $_.RecordType -eq "NS" }

                

                foreach ($ns in $nsRecords) {

                    $nsHost = $ns.RecordData.NameServer.Trim('.')

                    if ($script:inactiveNSServers.ContainsKey($nsHost)) {

                        Write-ColorOutput "  - Inactive NS found: $nsHost" "Red"

                    } else {

                        Write-ColorOutput "  - Active NS found: $nsHost" "Green"

                    }

                }

                Write-Separator

            }

        }

        

        # Process reverse lookup zones for updates

        Write-ColorOutput "`nAnalyzing Reverse Lookup Zones (Will Be Updated):" "Yellow"

        Write-Separator

        foreach ($zone in $reverseLookupZones) {

            Write-ColorOutput "Analyzing zone: $($zone.ZoneName)" "Cyan"

            

            $nsRecords = Get-DnsServerResourceRecord -ZoneName $zone.ZoneName -RRType "NS" -ComputerName $txtDNSServer.Text |

                        Where-Object { $_.RecordType -eq "NS" }

            

            $inactiveNS = @()

            $existingNS = @()

            $missingNS = @()

            

            # Check for inactive NS records

            foreach ($ns in $nsRecords) {

                $nsHost = $ns.RecordData.NameServer.Trim('.')

                $existingNS += $nsHost

                

                if ($script:inactiveNSServers.ContainsKey($nsHost) -or -not $script:activeNSServers.Contains($nsHost)) {

                    $inactiveNS += $nsHost

                    $script:inactiveNSCount++

                    Write-ColorOutput "  - Inactive NS found: $nsHost" "Red"

                } else {

                    Write-ColorOutput "  - Active NS found: $nsHost" "Green"

                }

            }

            

            # Check for missing active NS records

            foreach ($activeNS in $script:activeNSServers) {

                if (-not $existingNS.Contains($activeNS)) {

                    $missingNS += $activeNS

                    Write-ColorOutput "  + Missing active NS: $activeNS" "Yellow"

                }

            }

            

            if ($inactiveNS.Count -gt 0) {

                $script:analyzedZones[$zone.ZoneName] = $inactiveNS

            }

            

            if ($missingNS.Count -gt 0) {

                $script:zonesToUpdate[$zone.ZoneName] = $missingNS

            }

            

            if ($inactiveNS.Count -eq 0 -and $missingNS.Count -eq 0) {

                Write-ColorOutput "  Zone is properly configured." "Green"

            }

            Write-Separator

        }

        

        Write-ColorOutput "`nAnalysis Summary:" "Cyan"

        Write-ColorOutput "Total zones analyzed: $($allZones.Count)" "White"

        Write-ColorOutput "Reverse lookup zones to update: $($reverseLookupZones.Count)" "White"

        Write-ColorOutput "Total inactive NS records in reverse zones: $script:inactiveNSCount" "Yellow"

        Write-ColorOutput "Total reverse zones needing NS records: $($script:zonesToUpdate.Count)" "Yellow"

        Write-Separator

        

        if ($script:inactiveNSCount -gt 0 -or $script:zonesToUpdate.Count -gt 0) {

            $btnClean.Enabled = $true

        } else {

            $btnClean.Enabled = $false

        }

        

    } catch {

        Write-ColorOutput "Error analyzing zones: $_" "Red"

        Write-Separator

    }

}

# Function to clean inactive NS records and add missing ones

function Clean-InactiveNSRecords {

    if ([System.Windows.Forms.MessageBox]::Show(

        "Are you sure you want to update NS records in reverse lookup zones?`n`nThis will:`n- Remove inactive NS records from reverse lookup zones`n- Add missing active NS records to reverse lookup zones`n`nOther zones will not be modified.",

        "Confirm Update",

        [System.Windows.Forms.MessageBoxButtons]::YesNo,

        [System.Windows.Forms.MessageBoxIcon]::Warning) -eq 'Yes')

    {

        Write-ColorOutput "Starting reverse lookup zone NS records update..." "Yellow"

        Write-Separator

        

        try {

            $removedCount = 0

            $addedCount = 0

            

            # Process only reverse lookup zones

            foreach ($zone in $script:analyzedZones.Keys) {

                Write-ColorOutput "Processing zone: $zone" "Cyan"

                

                foreach ($inactiveNS in $script:analyzedZones[$zone]) {

                    try {

                        Remove-DnsServerResourceRecord -ZoneName $zone -RRType "NS" -Name "@" `

                            -RecordData $inactiveNS -Force -ComputerName $txtDNSServer.Text

                        Write-ColorOutput "Removed NS record: $inactiveNS" "Green"

                        $removedCount++

                    } catch {

                        Write-ColorOutput "Error removing NS record $inactiveNS : $_" "Red"

                    }

                }

            }

            

            foreach ($zone in $script:zonesToUpdate.Keys) {

                Write-ColorOutput "`nAdding missing NS records to zone: $zone" "Cyan"

                

                foreach ($missingNS in $script:zonesToUpdate[$zone]) {

                    try {

                        Add-DnsServerResourceRecord -ZoneName $zone -NS -Name "@" `

                            -NameServer $missingNS -ComputerName $txtDNSServer.Text

                        Write-ColorOutput "Added NS record: $missingNS" "Green"

                        $addedCount++

                    } catch {

                        Write-ColorOutput "Error adding NS record $missingNS : $_" "Red"

                    }

                }

            }

            

            Write-ColorOutput "`nUpdate Summary:" "Cyan"

            Write-ColorOutput "Total NS records removed from reverse zones: $removedCount" "Yellow"

            Write-ColorOutput "Total NS records added to reverse zones: $addedCount" "Yellow"

            Write-Separator

            

            $btnClean.Enabled = $false

            $script:analyzedZones.Clear()

            $script:zonesToUpdate.Clear()

            $script:inactiveNSCount = 0

            

        } catch {

            Write-ColorOutput "Error during update: $_" "Red"

            Write-Separator

        }

    }

}

# Button click events

$btnRefresh.Add_Click({ Get-ActiveNSRecords })

$btnAnalyze.Add_Click({ Analyze-ReverseLookupZones })

$btnClean.Add_Click({ Clean-InactiveNSRecords })

$btnExit.Add_Click({ $form.Close() })

# Load active NS records when form loads

$form.Add_Shown({

    Get-ActiveNSRecords

})

# Show the form

$form.ShowDialog() 

 

What Is the Purpose of This Script?

This script was developed to improve DNS management efficiency. Over time, DNS servers may experience performance issues due to outdated records, making regular updates crucial. It is particularly useful in the following scenarios:

  • When making major changes to the network.
  • When updating existing Name Server records.
  • When identifying and removing old NS records.
  • When regularly optimizing Reverse Lookup Zone areas.

As a result, with this script

To ensure that Windows DNS servers function correctly, NS records must be continuously monitored and updated. This script provides a great convenience for DNS administrators by offering both analysis and update functions. Using automation like this to keep your DNS infrastructure healthy and up to date is highly beneficial.

If you found this trick helpful, feel free to share it with your team and check out my blog for more quick tips and insights!

How to Upgrade Failover Cluster Functional Level

When you add new nodes with a higher operating system to your Windows Failover cluster environment, you will receive warnings about "Functional Level" mismatch in your failover cluster logs.

The main reason for these warnings is that the operating systems and functional levels of the clusters connected to the nodes in your environment are different from each other.

You can follow the steps below to change the functional level compatibility;

  • To view the Failover Cluster Functional Level version
    Get-Cluster | select ClusterFunctionalLevel
  • To upgrade the Failover Cluster Functional Level version
    Update-ClusterFunctionalLevel
  • To view the upgrade process of the Failover Cluster Functional Level version
    Get-Cluster | select ClusterFunctionalLevel
  • In Windows Server 2019 the Clustering team introduced a new PowerShell cmdlet to check how many nodes of the cluster are running on which level
    "Get-ClusterNodeSupportedVersion" helps you to identify the Cluster Functional Level and the Cluster Upgrade Version.

The table below shows the values and each corresponding functional level:

 

For more detailed information;

Have a nice day!

How to In-Place Upgrade Windows Server 2016 to Windows Server 2019 ?

What is the in-place upgrade?
In the simplest terms, it is the process of upgrading your Windows operating system without losing your data.

If you are going to do this on a physical server, it is useful to check the hardware compatibility first.
Hardware requirements for Windows Server

You can also see the version transitions of the operating systems in the table below.

If your server is located on a virtual platform, it is useful to take a snapshot/checkpoint before doing the in-place upgrade.

After connecting the “Windows Server 2019” .iso file to your server with the “Windows Server 2016” operating system, "setup.exe" should be run.

On the next screen, we are asked whether to install the updates. The recommended action here is to make updates, and we continue by ticking this option.

Select the windows version.

Select Accept to accept the terms of your licensing agreement

Attention! Since our preference is an in-place upgrade, we continue with the "Keep personal files and apps" option.

Depending on the size of your data, the in-place upgrade process may take an average of 30 minutes to 1 hour.

 

If you have a server with the "Windows Server 2012 R2" operating system, you can’t upgrade directly to “Windows Server 2019”.
You will upgrade  "Windows Server 2016" first, then you can upgrade to "Windows Server 2019".

 

Ps: Disable UAC before starting the in-place upgrade, then enable it again.

Have a nice day!