PowerShell ile Windows DNS Yönetimi ve Otomasyon

DNS yönetimi deyince aklımıza hep o klasik DNS Manager GUI’si gelir, fare tıklamaları, sağ tık menüleri… Ama yüzlerce kayıt eklemeniz gerektiğinde ya da her hafta aynı işlemi tekrar ettiğinizde GUI’nin ne kadar acı verici olduğunu anlarsınız. Bu yazıda PowerShell ile Windows DNS sunucusunu nasıl yöneteceğinizi, kayıtları nasıl otomatize edeceğinizi ve gerçek ortamlarda işinize yarayacak scriptler nasıl yazacağınızı anlatacağım.

Başlamadan Önce: Ortamı Hazırlamak

Windows Server 2012 R2 ve sonrasında DNS yönetimi için DnsServer modülü gelir. Ama bu modülün yüklü olup olmadığını kontrol etmek gerekiyor.

# DNS Server rolünü ve araçlarını kontrol et
Get-WindowsFeature -Name DNS, RSAT-DNS-Server

# Sadece RSAT araçlarını yüklemek istiyorsanız (uzaktan yönetim için)
Install-WindowsFeature -Name RSAT-DNS-Server

# Modülün yüklü olduğunu doğrula
Get-Module -ListAvailable -Name DnsServer

# Modülü import et
Import-Module DnsServer

Uzak bir DNS sunucusunu yönetecekseniz -ComputerName parametresini her cmdlet’te kullanabilirsiniz ya da PowerShell Remoting üzerinden bağlanabilirsiniz. Ben genellikle -ComputerName tercih ederim çünkü hangi sunucuyu yönettiğinizi her satırda görmek script okunabilirliğini artırıyor.

Temel DNS Zone Yönetimi

Önce zone kavramına bakalım. Her şeyin temelinde zone yönetimi yatıyor.

# Sunucudaki tüm zone'ları listele
Get-DnsServerZone -ComputerName "dns01.sirket.local"

# Sadece primary zone'ları filtrele
Get-DnsServerZone -ComputerName "dns01.sirket.local" | 
    Where-Object { $_.ZoneType -eq "Primary" -and $_.IsReverseLookupZone -eq $false }

# Yeni bir primary zone oluştur
Add-DnsServerPrimaryZone -Name "yenibrans.sirket.local" `
    -ZoneFile "yenibrans.sirket.local.dns" `
    -DynamicUpdate None `
    -ComputerName "dns01.sirket.local"

# Active Directory entegre zone oluştur (AD ortamlarında tercih edilen)
Add-DnsServerPrimaryZone -Name "yenibrans.sirket.local" `
    -ReplicationScope "Domain" `
    -DynamicUpdate Secure `
    -ComputerName "dns01.sirket.local"

# Reverse lookup zone oluştur (192.168.10.x ağı için)
Add-DnsServerPrimaryZone -NetworkId "192.168.10.0/24" `
    -ReplicationScope "Domain" `
    -ComputerName "dns01.sirket.local"

Zone oluştururken -ReplicationScope parametresine dikkat edin. Forest tüm AD ormanına, Domain sadece mevcut domain’e, Legacy eski BIND uyumlu alanlara replike eder.

DNS Kayıt Yönetimi

Bu kısım işin kalbi. Günlük hayatta en çok yapacağınız işlemler burada.

A ve PTR Kayıtları

# Tek bir A kaydı ekle
Add-DnsServerResourceRecordA -ZoneName "sirket.local" `
    -Name "webserver01" `
    -IPv4Address "192.168.10.50" `
    -TimeToLive 01:00:00 `
    -ComputerName "dns01.sirket.local"

# Aynı anda PTR kaydı da oluştur
Add-DnsServerResourceRecordA -ZoneName "sirket.local" `
    -Name "webserver01" `
    -IPv4Address "192.168.10.50" `
    -CreatePtr `
    -ComputerName "dns01.sirket.local"

# Mevcut kayıtları listele
Get-DnsServerResourceRecord -ZoneName "sirket.local" `
    -RRType "A" `
    -ComputerName "dns01.sirket.local"

# Belirli bir kaydı ara
Get-DnsServerResourceRecord -ZoneName "sirket.local" `
    -Name "webserver01" `
    -ComputerName "dns01.sirket.local"

CNAME, MX ve TXT Kayıtları

# CNAME kaydı oluştur
Add-DnsServerResourceRecordCName -ZoneName "sirket.local" `
    -Name "www" `
    -HostNameAlias "webserver01.sirket.local." `
    -ComputerName "dns01.sirket.local"

# MX kaydı ekle
Add-DnsServerResourceRecordMX -ZoneName "sirket.local" `
    -Name "@" `
    -MailExchange "mail.sirket.local" `
    -Preference 10 `
    -ComputerName "dns01.sirket.local"

# TXT kaydı (SPF için)
Add-DnsServerResourceRecord -ZoneName "sirket.local" `
    -Name "@" `
    -Txt `
    -DescriptiveText "v=spf1 mx a include:spf.koruma.net ~all" `
    -ComputerName "dns01.sirket.local"

Gerçek Dünya Senaryosu: Toplu Kayıt İçe Aktarma

Şimdi gerçekten işe yarar bir şey yapalım. Yeni bir ofis açıyorsunuz, IT ekibinden size Excel’den dönüştürülmüş 80 satırlık bir CSV geliyor ve “bunları DNS’e ekler misin” diyorlar. GUI ile yaparsanız saatlerinizi harcarsınız.

CSV dosyası şöyle görünüyor (dns_kayitlari.csv):

Ad,IPAdresi,Tur,Zone
printer01,192.168.20.10,A,sirket.local
fileserver01,192.168.20.11,A,sirket.local
www,webserver01.sirket.local.,CNAME,sirket.local
# CSV'den toplu DNS kaydı oluşturma scripti
param(
    [Parameter(Mandatory=$true)]
    [string]$CsvDosyasi,
    
    [Parameter(Mandatory=$true)]
    [string]$DnsSunucusu,
    
    [switch]$WhatIf
)

$kayitlar = Import-Csv -Path $CsvDosyasi -Encoding UTF8
$basarili = 0
$basarisiz = 0
$log = @()

foreach ($kayit in $kayitlar) {
    try {
        switch ($kayit.Tur) {
            "A" {
                $params = @{
                    ZoneName      = $kayit.Zone
                    Name          = $kayit.Ad
                    IPv4Address   = $kayit.IPAdresi
                    ComputerName  = $DnsSunucusu
                    ErrorAction   = "Stop"
                }
                if (-not $WhatIf) {
                    Add-DnsServerResourceRecordA @params
                }
                $mesaj = "BASARILI: A kaydı eklendi - $($kayit.Ad) -> $($kayit.IPAdresi)"
            }
            "CNAME" {
                $params = @{
                    ZoneName        = $kayit.Zone
                    Name            = $kayit.Ad
                    HostNameAlias   = $kayit.IPAdresi
                    ComputerName    = $DnsSunucusu
                    ErrorAction     = "Stop"
                }
                if (-not $WhatIf) {
                    Add-DnsServerResourceRecordCName @params
                }
                $mesaj = "BASARILI: CNAME kaydı eklendi - $($kayit.Ad) -> $($kayit.IPAdresi)"
            }
            default {
                $mesaj = "ATLANDI: Bilinmeyen kayıt türü - $($kayit.Tur)"
            }
        }
        $basarili++
    }
    catch {
        $mesaj = "HATA: $($kayit.Ad) eklenemedi - $($_.Exception.Message)"
        $basarisiz++
    }
    
    Write-Host $mesaj
    $log += [PSCustomObject]@{
        Zaman   = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Mesaj   = $mesaj
    }
}

# Log dosyasına yaz
$log | Export-Csv -Path "dns_import_log_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" `
    -NoTypeInformation -Encoding UTF8

Write-Host "`nTamamlandi: $basarili basarili, $basarisiz basarisiz"

Bu scripti .dns_import.ps1 -CsvDosyasi .dns_kayitlari.csv -DnsSunucusu dns01.sirket.local şeklinde çalıştırıyorsunuz. -WhatIf parametresiyle önce neyin ekleneceğini görebilirsiniz.

DNS Kayıtlarını Güncelleme ve Silme

Güncellemeler biraz farklı çalışıyor PowerShell’de. Doğrudan “update” cmdlet’i yok, eski kaydı alıp değiştirip geri yazıyorsunuz.

# A kaydının IP'sini güncelle
$eskiKayit = Get-DnsServerResourceRecord -ZoneName "sirket.local" `
    -Name "webserver01" `
    -RRType "A" `
    -ComputerName "dns01.sirket.local"

$yeniKayit = $eskiKayit.Clone()
$yeniKayit.RecordData.IPv4Address = [System.Net.IPAddress]::Parse("192.168.10.55")

Set-DnsServerResourceRecord -ZoneName "sirket.local" `
    -OldInputObject $eskiKayit `
    -NewInputObject $yeniKayit `
    -ComputerName "dns01.sirket.local"

# TTL güncelleme
$yeniKayit.TimeToLive = [System.TimeSpan]::FromMinutes(30)

# Kayıt silme
Remove-DnsServerResourceRecord -ZoneName "sirket.local" `
    -Name "eskisunucu" `
    -RRType "A" `
    -Force `
    -ComputerName "dns01.sirket.local"

DNS Sunucu Sağlık Kontrolü ve İzleme

Operasyonel açıdan en önemli script bu. Sabah işe geldiğinizde DNS sunucunuzun durumunu öğrenmek için elle kontrol yapmamalısınız.

# DNS Sunucu sağlık kontrolü - cronjob/task scheduler ile çalıştırın
param(
    [string[]]$DnsSunuculari = @("dns01.sirket.local", "dns02.sirket.local"),
    [string]$RaporDosyasi = "dns_saglik_raporu.txt",
    [string]$UyariEmail = "[email protected]"
)

$sorunlar = @()
$rapor = @()
$rapor += "DNS Saglik Raporu - $(Get-Date -Format 'dd.MM.yyyy HH:mm')"
$rapor += "=" * 60

foreach ($sunucu in $DnsSunuculari) {
    $rapor += "`nSunucu: $sunucu"
    
    # Sunucuya erişilebilirlik kontrolü
    if (-not (Test-Connection -ComputerName $sunucu -Count 2 -Quiet)) {
        $mesaj = "KRITIK: $sunucu erisilebilir degil!"
        $rapor += $mesaj
        $sorunlar += $mesaj
        continue
    }
    
    try {
        # Zone sayısı
        $zones = Get-DnsServerZone -ComputerName $sunucu -ErrorAction Stop
        $rapor += "  Toplam zone sayisi: $($zones.Count)"
        
        # Hata durumundaki zone'ları kontrol et
        $hataliZones = $zones | Where-Object { $_.ZoneType -eq "Secondary" -and $_.IsDsIntegrated -eq $false }
        foreach ($zone in $hataliZones) {
            $transferDurumu = Get-DnsServerZoneTransferPolicy -ZoneName $zone.ZoneName `
                -ComputerName $sunucu -ErrorAction SilentlyContinue
        }
        
        # DNS servis durumu
        $servis = Get-Service -Name "DNS" -ComputerName $sunucu -ErrorAction Stop
        if ($servis.Status -ne "Running") {
            $mesaj = "KRITIK: $sunucu DNS servisi calismiyor! Durum: $($servis.Status)"
            $rapor += "  $mesaj"
            $sorunlar += $mesaj
        } else {
            $rapor += "  DNS servisi: Calisiyor"
        }
        
        # Forwarder kontrolü
        $forwarders = Get-DnsServerForwarder -ComputerName $sunucu -ErrorAction Stop
        if ($forwarders.IPAddress.Count -eq 0) {
            $mesaj = "UYARI: $sunucu uzerinde forwarder tanimlanmamis"
            $rapor += "  $mesaj"
            $sorunlar += $mesaj
        } else {
            $rapor += "  Forwarderlar: $($forwarders.IPAddress -join ', ')"
        }
        
        # Diagnostik sorgu testi
        $testSonuc = Resolve-DnsName -Name "sirket.local" -Server $sunucu `
            -ErrorAction SilentlyContinue
        if ($null -eq $testSonuc) {
            $mesaj = "UYARI: $sunucu test sorgusuna yanit vermiyor"
            $rapor += "  $mesaj"
            $sorunlar += $mesaj
        } else {
            $rapor += "  Sorgu testi: Basarili"
        }
    }
    catch {
        $mesaj = "HATA: $sunucu kontrol edilemedi - $($_.Exception.Message)"
        $rapor += "  $mesaj"
        $sorunlar += $mesaj
    }
}

# Raporu yaz
$rapor | Out-File -FilePath $RaporDosyasi -Encoding UTF8

# Sorun varsa email gönder
if ($sorunlar.Count -gt 0 -and $UyariEmail) {
    $emailGovde = $rapor -join "`n"
    Send-MailMessage -To $UyariEmail `
        -From "[email protected]" `
        -Subject "DNS Uyari: $($sorunlar.Count) sorun tespit edildi" `
        -Body $emailGovde `
        -SmtpServer "smtp.sirket.local"
    Write-Host "Uyari emaili gonderildi: $($sorunlar.Count) sorun"
}

Zone Transfer ve Replikasyon Yönetimi

Secondary zone’larınız varsa replikasyonun düzgün çalışıp çalışmadığını kontrol etmek kritik.

# Secondary zone'ların transfer durumunu kontrol et ve gerekirse zorla
$dnsServer = "dns02.sirket.local"
$secondaryZones = Get-DnsServerZone -ComputerName $dnsServer | 
    Where-Object { $_.ZoneType -eq "Secondary" }

foreach ($zone in $secondaryZones) {
    Write-Host "Zone transfer baslatiliyor: $($zone.ZoneName)"
    
    try {
        # Zone transferini tetikle
        Start-DnsServerZoneTransfer -Name $zone.ZoneName `
            -ComputerName $dnsServer `
            -FullTransfer `
            -ErrorAction Stop
        
        Write-Host "  Transfer baslatildi: $($zone.ZoneName)"
        
        # Kısa bekle ve durumu kontrol et
        Start-Sleep -Seconds 5
        $guncelZone = Get-DnsServerZone -Name $zone.ZoneName -ComputerName $dnsServer
        Write-Host "  Son degisiklik: $($guncelZone.LastZoneTransferAttempt)"
    }
    catch {
        Write-Warning "Transfer basarısız: $($zone.ZoneName) - $($_.Exception.Message)"
    }
}

# Forwarder güncelleme
Set-DnsServerForwarder -IPAddress "8.8.8.8", "8.8.4.4", "1.1.1.1" `
    -ComputerName "dns01.sirket.local" `
    -UseRootHint $false

Eski Kayıtları Temizleme: Scavenging Otomasyonu

DNS ortamlarında zamanla binlerce orphan kayıt birikir. Scavenging mekanizması bunu temizler ama varsayılan olarak kapalıdır.

# Scavenging ayarlarını yapılandır
# Önce sunucu seviyesinde scavenging'i etkinleştir
Set-DnsServerScavenging -ComputerName "dns01.sirket.local" `
    -ScavengingState $true `
    -ScavengingInterval 7.00:00:00 `
    -Verbose

# Zone seviyesinde aging'i etkinleştir
Set-DnsServerZoneAging -Name "sirket.local" `
    -ComputerName "dns01.sirket.local" `
    -Aging $true `
    -ScavengeServers "192.168.10.10" `
    -NoRefreshInterval 7.00:00:00 `
    -RefreshInterval 7.00:00:00

# Mevcut scavenging durumunu kontrol et
Get-DnsServerScavenging -ComputerName "dns01.sirket.local"

# Elle scavenging başlat (dikkatli kullanın, önce test ortamında deneyin)
Start-DnsServerScavenging -ComputerName "dns01.sirket.local" `
    -Force `
    -Verbose

Önemli: Scavenging’i production ortamında açmadan önce mutlaka test edin. Yanlış yapılandırılmış aging ayarları geçerli kayıtları da silebilir. NoRefresh ve Refresh interval toplamı 7 günden az olmasın.

DNS Backup ve Restore

Zone dosyalarınızı düzenli olarak yedeklemek, bir felaket anında saatler kazandırır.

# Tüm zone'ları dışa aktar ve yedekle
param(
    [string]$DnsSunucusu = "dns01.sirket.local",
    [string]$YedekDizin = "C:DNSYedekler"
)

$tarih = Get-Date -Format "yyyyMMdd_HHmmss"
$yedekKlasor = Join-Path $YedekDizin $tarih
New-Item -ItemType Directory -Path $yedekKlasor -Force | Out-Null

# Zone listesini al
$zones = Get-DnsServerZone -ComputerName $DnsSunucusu | 
    Where-Object { -not $_.IsReverseLookupZone -and $_.ZoneType -eq "Primary" }

foreach ($zone in $zones) {
    try {
        # Zone'u dosyaya aktar
        $dosyaAdi = "$($zone.ZoneName)_$tarih.xml"
        $dosyaYolu = Join-Path $yedekKlasor $dosyaAdi
        
        # Tüm kayıtları XML olarak kaydet
        Get-DnsServerResourceRecord -ZoneName $zone.ZoneName `
            -ComputerName $DnsSunucusu | 
            Export-Clixml -Path $dosyaYolu
        
        Write-Host "Yedeklendi: $($zone.ZoneName) -> $dosyaAdi"
    }
    catch {
        Write-Warning "Yedeklenemedi: $($zone.ZoneName) - $($_.Exception.Message)"
    }
}

# 30 günden eski yedekleri temizle
Get-ChildItem -Path $YedekDizin -Directory | 
    Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-30) } | 
    Remove-Item -Recurse -Force

Write-Host "`nYedekleme tamamlandi: $yedekKlasor"

Task Scheduler ile Otomasyon

Yazdığınız scriptleri elle çalıştırmak zorunda kalmak otomasyon değildir. Task Scheduler’a bağlayın.

# Sağlık kontrolü scriptini her sabah 07:00'da çalıştır
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
    -Argument "-NonInteractive -ExecutionPolicy Bypass -File C:Scriptsdns_saglik_kontrol.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At "07:00"

$settings = New-ScheduledTaskSettingsSet `
    -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -RestartCount 2 `
    -RestartInterval (New-TimeSpan -Minutes 5)

$principal = New-ScheduledTaskPrincipal `
    -UserId "SIRKETsvc_dns_monitor" `
    -LogonType ServiceAccount `
    -RunLevel Highest

Register-ScheduledTask -TaskName "DNS Saglik Kontrolu" `
    -TaskPath "SysAdmin" `
    -Action $action `
    -Trigger $trigger `
    -Settings $settings `
    -Principal $principal `
    -Description "DNS sunucularinin gunluk saglik kontrolu"

Write-Host "Zamanlanmis gorev olusturuldu"

Pratik İpuçları

Yıllar içinde öğrendiğim birkaç pratik noktayı paylaşmak istiyorum:

  • DNS Manager GUI’yi tamamen bırakmayın: Karmaşık DNSSEC yapılandırmaları veya ilk kurulum için GUI hala kullanışlı. PowerShell ve GUI’yi birlikte kullanmak en iyi yaklaşım.
  • Her scriptte loglama olsun: DNS değişiklikleri audit açısından kritik. Neyi ne zaman kimin değiştirdiğini kaydedin.
  • -WhatIf parametresini kullanın: Toplu işlemlerde önce WhatIf ile neyin yapılacağını görün.
  • Test-DnsServer cmdlet’i: Resmi bir Test-DnsServer cmdlet’i yoktur ama Resolve-DnsName -Server ile işlevsel testler yapabilirsiniz.
  • AD entegre zone’larda değişiklikler replike olur: Bir DC’de yapılan değişiklik diğerlerine AD replikasyonu ile gider, zone transferi beklemezsiniz.
  • TTL değerlerini planlayın: Değişiklik öncesinde TTL’yi düşürün, değişiklik sonrasında geri yükleyin.
  • Scavenging’i açmadan önce zone’daki kayıtların timestamp’lerini kontrol edin: Statik kayıtların static olarak işaretlendiğinden emin olun.

Sonuç

PowerShell ile DNS yönetimi, başta biraz fazla kod yazmak gibi görünse de uzun vadede size inanılmaz zaman kazandırır. 80 kayıtlık bir CSV’yi GUI ile işlemek 2-3 saat alırken script ile 30 saniye. Üstelik insan hatası sıfırlanıyor, her işlem loglanıyor ve tekrar edilebilir hale geliyor.

Bu yazıda anlattıklarımı bir temel olarak kullanın. Kendi ortamınıza göre scriptleri özelleştirin, hata yönetimini güçlendirin, organizasyonunuzun ihtiyaçlarına uyarlar. DNS yönetimi karmaşık görünebilir ama PowerShell ile sistematik bir yaklaşım kurduğunuzda, o “acaba zone’u doğru mı yapılandırdım” kaygısından kurtulursunuz. Her şey kod halinde, version control’de, tekrar edilebilir.

Bir sonraki adım olarak DNS değişikliklerini Git repository’de tutmayı ve değişiklik onay süreçlerini otomatize etmeyi düşünebilirsiniz. Ama bu başlı başına ayrı bir yazı konusu.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir