powershellDNS

Synchronizace PTR záznamů v DNS

Občas je potřeba synchronizace PTR záznamů v DNS s aktuálními IP adresami stanic.

Jako vstup si vezme název zény dopředného vyhledávání a zkontroluje všechny záznamy, zda mají korespondující PTR záznam v reverzní zóně. Pokud ne, aktualizuje je nebo vytvoří nový.

Na konci běhu zobrazí přehledný souhrn: souhrn

<#
.SYNOPSIS
    Synchronizuje PTR záznamy z forward lookup zóny na DNS serveru (DC).

.DESCRIPTION
    Prochází A záznamy v zadané forward lookup zóně a pro každý záznam:
      - Zkontroluje, zda existuje odpovídající PTR záznam v reverse zóně.
      - Pokud existuje a hostname se shoduje → přeskočí.
      - Pokud existuje, ale hostname se liší → aktualizuje PTR záznam.
      - Pokud neexistuje → vytvoří nový PTR záznam.

.PARAMETER ForwardZone
    Název forward lookup zóny (např. "firma.local").

.PARAMETER DnsServer
    Hostname nebo IP adresa DNS serveru. Výchozí: localhost.

.PARAMETER WhatIf
    Pouze zobrazí, co by se stalo – nic nemění.

.EXAMPLE
    .\Sync-DnsPtrRecords.ps1 -ForwardZone "firma.local"

.EXAMPLE
    .\Sync-DnsPtrRecords.ps1 -ForwardZone "firma.local" -DnsServer "dc01.firma.local" -WhatIf
#>

[CmdletBinding(SupportsShouldProcess)]
param (
    [Parameter(Mandatory)]
    [string]$ForwardZone,

    [string]$DnsServer = $env:COMPUTERNAME
)

#region Pomocné funkce

function Get-ReverseZoneName {
    <#
    .SYNOPSIS
        Zjistí název reverse zóny pro zadanou IP adresu z dostupných zón na DNS serveru.
        Testuje postupně /24, /16, /8 varianty.
    #>
    param (
        [string]$IpAddress,
        [string[]]$AvailableReverseZones
    )

    $octets = $IpAddress.Split('.')

    # Kandidáti od nejpřesnějšího (/24) po nejméně přesný (/8)
    $candidates = @(
        "$($octets[2]).$($octets[1]).$($octets[0]).in-addr.arpa",   # /24
        "$($octets[1]).$($octets[0]).in-addr.arpa",                  # /16
        "$($octets[0]).in-addr.arpa"                                  # /8
    )

    foreach ($candidate in $candidates) {
        if ($AvailableReverseZones -contains $candidate) {
            return $candidate
        }
    }

    return $null
}

function Get-PtrRecordName {
    <#
    .SYNOPSIS
        Vrátí část PTR záznamu (hostovou část) pro danou IP a reverse zónu.
        Např. IP 192.168.1.100 v zóně "1.168.192.in-addr.arpa" → "100"
    #>
    param (
        [string]$IpAddress,
        [string]$ReverseZone
    )

    $octets = $IpAddress.Split('.')
    $zoneDepth = ($ReverseZone -replace '\.in-addr\.arpa$','').Split('.').Count

    # Reverzní pořadí oktetů: 10.20.18.81 → @('81','18','20','10')
    $reversedOctets = @($octets[3], $octets[2], $octets[1], $octets[0])

    # Hostová část = prvních (4 - zoneDepth) reverzních oktetů
    $hostPart = $reversedOctets[0..(3 - $zoneDepth)]
    return ($hostPart -join '.')
}

function Normalize-Fqdn {
    param ([string]$Name, [string]$Zone)
    if ($Name -notmatch '\.$') {
        # není absolutní FQDN, přidáme zónu
        if ($Name -match "\.$([regex]::Escape($Zone))$") {
            return "$Name."
        }
        return "$Name.$Zone."
    }
    return $Name
}

#endregion

#region Hlavní logika

# Ověření modulu
if (-not (Get-Module -ListAvailable -Name DnsServer)) {
    Write-Error "Modul DnsServer není dostupný. Spusťte skript na DNS serveru nebo nainstalujte RSAT: Install-WindowsFeature RSAT-DNS-Server"
    exit 1
}

Import-Module DnsServer -ErrorAction Stop

Write-Host "`n=== Sync-DnsPtrRecords ===" -ForegroundColor Cyan
Write-Host "DNS Server  : $DnsServer"
Write-Host "Forward Zone: $ForwardZone"
Write-Host ""

# Načtení všech reverse zón ze serveru
Write-Verbose "Načítám dostupné reverse zóny..."
try {
    $allReverseZones = Get-DnsServerZone -ComputerName $DnsServer |
        Where-Object { $_.ZoneName -like '*.in-addr.arpa' -and -not $_.IsAutoCreated } |
        Select-Object -ExpandProperty ZoneName
}
catch {
    Write-Error "Nelze načíst zóny z DNS serveru '$DnsServer': $_"
    exit 1
}

if (-not $allReverseZones) {
    Write-Warning "Na serveru '$DnsServer' nebyly nalezeny žádné reverse lookup zóny."
    exit 0
}

Write-Verbose "Dostupné reverse zóny: $($allReverseZones -join ', ')"

# Načtení A záznamů z forward zóny
Write-Verbose "Načítám A záznamy ze zóny '$ForwardZone'..."
try {
    $aRecords = Get-DnsServerResourceRecord -ComputerName $DnsServer -ZoneName $ForwardZone -RRType A -ErrorAction Stop
}
catch {
    Write-Error "Nelze načíst záznamy ze zóny '$ForwardZone': $_"
    exit 1
}

if (-not $aRecords) {
    Write-Warning "V zóně '$ForwardZone' nebyly nalezeny žádné A záznamy."
    exit 0
}

Write-Host "Nalezeno A záznamů: $($aRecords.Count)" -ForegroundColor Green
Write-Host ""

# Statistiky
$stats = @{ Skipped = 0; Created = 0; Updated = 0; NoZone = 0; Errors = 0 }

foreach ($record in $aRecords) {
    $hostname = $record.HostName
    $ip       = $record.RecordData.IPv4Address.ToString()
    $fqdn     = if ($hostname -eq '@') { "$ForwardZone." } else { "$hostname.$ForwardZone." }

    Write-Verbose "Zpracovávám: $fqdn$ip"

    # Najdi příslušnou reverse zónu
    $reverseZone = Get-ReverseZoneName -IpAddress $ip -AvailableReverseZones $allReverseZones

    if (-not $reverseZone) {
        Write-Warning "  [$ip] $fqdn — reverse zóna nenalezena, přeskočeno."
        $stats.NoZone++
        continue
    }

    # Hostová část PTR záznamu (např. "100" pro 192.168.1.100 v zóně 1.168.192.in-addr.arpa)
    $ptrName = Get-PtrRecordName -IpAddress $ip -ReverseZone $reverseZone

    # Načti existující PTR záznam (pokud existuje)
    try {
        $existingPtr = Get-DnsServerResourceRecord -ComputerName $DnsServer `
            -ZoneName $reverseZone -Name $ptrName -RRType Ptr -ErrorAction SilentlyContinue
    }
    catch {
        $existingPtr = $null
    }

    if ($existingPtr) {
        $existingFqdn = $existingPtr.RecordData.PtrDomainName

        if ($existingFqdn -eq $fqdn) {
            # Shoduje se – přeskoč
            Write-Host "  [OK]      $ip$fqdn" -ForegroundColor DarkGray
            $stats.Skipped++
        }
        else {
            # Neshoda – aktualizuj
            Write-Host "  [UPDATE]  $ip : '$existingFqdn' → '$fqdn'" -ForegroundColor Yellow

            if ($PSCmdlet.ShouldProcess("$ip v zóně $reverseZone", "Aktualizovat PTR '$existingFqdn' → '$fqdn'")) {
                try {
                    # DNS modul nemá přímý Set pro PTR – odstraníme a vytvoříme znovu
                    Write-host "DNS modul nemá přímý Set pro PTR – odstraníme a vytvoříme znovu" -ForegroundColor Yellow
                    # Remove-DnsServerResourceRecord -ComputerName $DnsServer `
                    #     -ZoneName $reverseZone -Name $ptrName -RRType Ptr -Force -ErrorAction Stop

                    Write-host "Vytvářím nový PTR záznam..." -ForegroundColor Yellow

                    # Add-DnsServerResourceRecordPtr -ComputerName $DnsServer `
                    #     -ZoneName $reverseZone -Name $ptrName -PtrDomainName $fqdn -ErrorAction Stop

                    Write-Host "             Aktualizováno." -ForegroundColor Green
                    $stats.Updated++
                }
                catch {
                    Write-Warning "  [CHYBA]   Aktualizace selhala pro $ip : $_"
                    $stats.Errors++
                }
            }
        }
    }
    else {
        # PTR neexistuje – vytvoř
        Write-Host "  [CREATE]  $ip$fqdn" -ForegroundColor Cyan

        # if ($PSCmdlet.ShouldProcess("$ip v zóně $reverseZone", "Vytvořit PTR '$fqdn'")) {
        #     try {
        #         Add-DnsServerResourceRecordPtr -ComputerName $DnsServer `
        #             -ZoneName $reverseZone -Name $ptrName -PtrDomainName $fqdn -ErrorAction Stop

        #         Write-Host "             Vytvořeno." -ForegroundColor Green
        #         $stats.Created++
        #     }
        #     catch {
        #         Write-Warning "  [CHYBA]   Vytvoření selhalo pro $ip : $_"
        #         $stats.Errors++
        #     }
        # }
    }
}

#endregion

#region Souhrn

Write-Host ""
Write-Host "=== Souhrn ===" -ForegroundColor Cyan
Write-Host "  Přeskočeno (shoda) : $($stats.Skipped)"
Write-Host "  Vytvořeno          : $($stats.Created)"
Write-Host "  Aktualizováno      : $($stats.Updated)"
Write-Host "  Bez reverse zóny   : $($stats.NoZone)"
Write-Host "  Chyby              : $($stats.Errors)"
Write-Host ""

#endregion