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.
-WhatIfparametresini kullanın: Toplu işlemlerde önceWhatIfile neyin yapılacağını görün.- Test-DnsServer cmdlet’i: Resmi bir
Test-DnsServercmdlet’i yoktur amaResolve-DnsName -Serverile 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.
