Bir PowerShell scripti yazıyorsunuz, her şey güzel gidiyor, sonra bir noktada script sessizce başarısız oluyor. Ne hata mesajı var, ne de log. Sadece beklediğiniz sonuç yok. İşte bu an, hata ayıklama ve hata yönetiminin ne kadar kritik olduğunu anlatan andır. PowerShell’de try-catch mekanizmasını ve hata ayıklama araçlarını doğru kullanmak, sadece scriptlerinizi daha sağlam yapmaz; aynı zamanda saatler sürebilecek sorun giderme sürecini dakikalara indirir.
PowerShell’de Hata Türleri
PowerShell’de iki temel hata türü vardır ve bunları ayırt etmek, hata yönetiminin temelidir.
Terminating Error (Sonlandırıcı Hata): Script’in çalışmasını tamamen durduran hatalardır. Try-Catch bloklarıyla yakalanabilirler.
Non-Terminating Error (Sonlandırmayan Hata): Script çalışmaya devam eder ama hata konsola yazılır. Bu hataları varsayılan haliyle Try-Catch yakalayamaz.
İşte burada çoğu sysadmin’in düştüğü tuzak var. Bir dosyayı silmeye çalışıyorsunuz, dosya bulunamıyor, PowerShell hata yazıyor ama script çalışmaya devam ediyor. Try-Catch içine almışsınız ama yine de catch bloğuna girmiyor. Bunun sebebi o hatanın non-terminating olmasıdır.
Bunu aşmak için iki yöntem var:
# Yöntem 1: ErrorAction parametresi
Remove-Item "C:olmayan_dosya.txt" -ErrorAction Stop
# Yöntem 2: $ErrorActionPreference değişkeni (tüm script için)
$ErrorActionPreference = "Stop"
-ErrorAction Stop parametresi, non-terminating bir hatayı terminating hata haline getirir ve Try-Catch’in yakalamasını sağlar.
$Error Değişkeni ve Hata Geçmişi
PowerShell, son gerçekleşen hataları otomatik olarak $Error adlı bir dizide saklar. Bu dizi varsayılan olarak son 256 hatayı tutar.
# Son hatayı görüntüle
$Error[0]
# Son hatanın tam detayını görüntüle
$Error[0] | Format-List * -Force
# Hata mesajını al
$Error[0].Exception.Message
# Hangi satırda hata oluştu
$Error[0].InvocationInfo.ScriptLineNumber
# Hata geçmişini temizle
$Error.Clear()
# Kaç hata var
$Error.Count
Özellikle $Error[0].InvocationInfo.ScriptLineNumber bilgisi, uzun scriptlerde hatanın tam olarak nerede oluştuğunu bulmanızı sağlar. Bunu log’a yazdırmak, ilerleyen dönemlerde büyük kolaylık sağlar.
Temel Try-Catch-Finally Yapısı
Try-Catch bloğunun temel yapısı şu şekildedir:
try {
# Riskli kod buraya yazılır
$sonuc = Get-Content "C:configsettings.txt" -ErrorAction Stop
Write-Host "Dosya başarıyla okundu: $($sonuc.Count) satır"
}
catch {
# Hata oluşursa burası çalışır
Write-Host "Hata oluştu: $($_.Exception.Message)" -ForegroundColor Red
}
finally {
# Hata olsa da olmasa da burası çalışır
Write-Host "İşlem tamamlandı (finally bloğu)"
}
$_ ifadesi catch bloğu içinde mevcut hata nesnesini temsil eder. Bu nesne üzerinden hatanın pek çok detayına ulaşabilirsiniz:
- $_.Exception.Message: Hata mesajının okunabilir metni
- $_.Exception.GetType().FullName: Hatanın .NET tip adı
- $_.InvocationInfo.ScriptLineNumber: Hatanın oluştuğu satır numarası
- $_.InvocationInfo.Line: Hatanın oluştuğu satırın tam içeriği
- $_.ScriptStackTrace: Hata stack trace bilgisi
Finally bloğu, veritabanı bağlantısı kapatma, geçici dosya silme gibi temizleme işlemleri için idealdir. Hata olsa da olmasa da bu blok çalışacaktır.
Belirli Hata Türlerini Yakalamak
Gerçek dünyada farklı hata türlerine farklı tepkiler vermek gerekir. Birden fazla catch bloğu kullanarak bunu sağlayabilirsiniz:
try {
# Uzak sunucuya bağlanmayı dene
$session = New-PSSession -ComputerName "SUNUCU01" -ErrorAction Stop
Invoke-Command -Session $session -ScriptBlock {
Get-Service -Name "wuauserv" -ErrorAction Stop
}
}
catch [System.Management.Automation.Remoting.PSRemotingTransportException] {
Write-Host "Uzak bağlantı hatası: Sunucuya erişilemiyor" -ForegroundColor Red
Write-Host "Sunucu adını ve ağ bağlantısını kontrol edin" -ForegroundColor Yellow
}
catch [System.Management.Automation.RuntimeException] {
Write-Host "Çalışma zamanı hatası: $($_.Exception.Message)" -ForegroundColor Red
}
catch {
# Yukarıdakilerle eşleşmeyen tüm hatalar buraya düşer
Write-Host "Beklenmeyen hata: $($_.Exception.Message)" -ForegroundColor Red
}
finally {
if ($session) {
Remove-PSSession $session
Write-Host "Oturum kapatıldı"
}
}
Hata tipini nasıl öğrenirsiniz? Önce kodu çalıştırın, hata oluştuktan sonra $Error[0].Exception.GetType().FullName komutunu çalıştırın. Size tam tip adını verecektir.
Gerçek Dünya Senaryosu: Disk Alanı Kontrolü ve Uyarı Scripti
Şimdi pratik bir örnek yapalım. Birden fazla sunucuda disk alanını kontrol eden ve kritik durumları log’layan bir script:
function Get-DiskSpaceReport {
param(
[string[]]$Sunucular = @("SUNUCU01", "SUNUCU02", "SUNUCU03"),
[int]$KritikEsik = 10, # Yüzde
[string]$LogDosyasi = "C:Logsdisk_rapor_$(Get-Date -Format 'yyyyMMdd').log"
)
# Log dizini yoksa oluştur
$logDizin = Split-Path $LogDosyasi
if (-not (Test-Path $logDizin)) {
try {
New-Item -ItemType Directory -Path $logDizin -Force -ErrorAction Stop | Out-Null
Write-Host "Log dizini oluşturuldu: $logDizin"
}
catch {
Write-Host "Log dizini oluşturulamadı: $($_.Exception.Message)" -ForegroundColor Red
return
}
}
$rapor = @()
foreach ($sunucu in $Sunucular) {
try {
Write-Host "Kontrol ediliyor: $sunucu" -ForegroundColor Cyan
$diskler = Get-WmiObject -Class Win32_LogicalDisk `
-ComputerName $sunucu `
-Filter "DriveType=3" `
-ErrorAction Stop
foreach ($disk in $diskler) {
$bosYuzde = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
$durum = if ($bosYuzde -lt $KritikEsik) { "KRİTİK" } else { "Normal" }
$kayit = [PSCustomObject]@{
Sunucu = $sunucu
Sürücü = $disk.DeviceID
ToplamGB = [math]::Round($disk.Size / 1GB, 2)
BosGB = [math]::Round($disk.FreeSpace / 1GB, 2)
BosYuzde = $bosYuzde
Durum = $durum
Zaman = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
$rapor += $kayit
if ($durum -eq "KRİTİK") {
$mesaj = "[$($kayit.Zaman)] KRİTİK: $sunucu - $($disk.DeviceID) - Boş: %$bosYuzde"
Add-Content -Path $LogDosyasi -Value $mesaj
Write-Host $mesaj -ForegroundColor Red
}
}
}
catch [System.Runtime.InteropServices.COMException] {
$hataMsg = "[$( Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] HATA: $sunucu - WMI bağlantısı başarısız"
Add-Content -Path $LogDosyasi -Value $hataMsg
Write-Host $hataMsg -ForegroundColor Red
}
catch {
$hataMsg = "[$( Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] HATA: $sunucu - $($_.Exception.Message)"
Add-Content -Path $LogDosyasi -Value $hataMsg
Write-Host $hataMsg -ForegroundColor Red
}
}
return $rapor
}
# Scripti çalıştır
$sonuclar = Get-DiskSpaceReport
$sonuclar | Format-Table -AutoSize
Hata Ayıklama Araçları: Write-Debug ve Set-PSDebug
Write-Debug komutu, debug modunda ek bilgi göstermenizi sağlar. Normal çalışmada bu mesajlar görünmez, sadece debug modunda görünür:
function Invoke-FileProcess {
param([string]$DosyaYolu)
Write-Debug "Fonksiyon başladı. Parametre: $DosyaYolu"
try {
if (-not (Test-Path $DosyaYolu)) {
throw "Dosya bulunamadı: $DosyaYolu"
}
Write-Debug "Dosya mevcut, içerik okunuyor..."
$icerik = Get-Content $DosyaYolu -ErrorAction Stop
Write-Debug "Dosya okundu. Satır sayısı: $($icerik.Count)"
return $icerik
}
catch {
Write-Error "Dosya işleme hatası: $($_.Exception.Message)"
return $null
}
}
# Normal çalışma (debug mesajları görünmez)
$veri = Invoke-FileProcess -DosyaYolu "C:datainput.txt"
# Debug modunda çalıştır (debug mesajları görünür)
$DebugPreference = "Continue"
$veri = Invoke-FileProcess -DosyaYolu "C:datainput.txt"
$DebugPreference = "SilentlyContinue"
Set-PSDebug ise daha kapsamlı bir debug aracıdır:
# Her satır çalışmadan önce onay ister
Set-PSDebug -Step
# Her satırı çalıştırırken ekrana yazar
Set-PSDebug -Trace 1
# Değişken atamalarını da gösterir
Set-PSDebug -Trace 2
# Debug modunu kapat
Set-PSDebug -Off
Özel Hata Oluşturma: Throw ve Write-Error
Kendi hata mesajlarınızı oluşturmak için throw ve Write-Error kullanabilirsiniz:
function Test-ServiceStatus {
param(
[string]$ServisAdi,
[string]$Sunucu = $env:COMPUTERNAME
)
# Parametre doğrulama
if ([string]::IsNullOrEmpty($ServisAdi)) {
throw "ServisAdi parametresi boş olamaz"
}
try {
$servis = Get-Service -Name $ServisAdi -ComputerName $Sunucu -ErrorAction Stop
if ($servis.Status -ne "Running") {
# Non-terminating hata oluştur
Write-Error "Servis çalışmıyor: $ServisAdi - Durum: $($servis.Status)"
return $false
}
Write-Host "Servis çalışıyor: $ServisAdi" -ForegroundColor Green
return $true
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
throw "Servis bulunamadı: $ServisAdi - Sunucu: $Sunucu"
}
catch {
throw "Servis kontrolü başarısız: $($_.Exception.Message)"
}
}
# Kullanım
try {
$sonuc = Test-ServiceStatus -ServisAdi "wuauserv" -Sunucu "SUNUCU01"
if ($sonuc) {
Write-Host "Windows Update servisi aktif"
}
}
catch {
Write-Host "Kritik hata: $($_.Exception.Message)" -ForegroundColor Red
}
Gerçek Dünya Senaryosu: Merkezi Log Fonksiyonu
Profesyonel scriptlerde her zaman merkezi bir log fonksiyonu kullanmanızı tavsiye ederim. Bu yaklaşım hem kod tekrarını önler hem de log formatını tutarlı tutar:
function Write-Log {
param(
[string]$Mesaj,
[ValidateSet("INFO", "WARNING", "ERROR", "SUCCESS")]
[string]$Seviye = "INFO",
[string]$LogDosyasi = "C:Logsscript.log"
)
$zaman = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logSatiri = "[$zaman] [$Seviye] $Mesaj"
# Konsola renkli yaz
switch ($Seviye) {
"INFO" { Write-Host $logSatiri -ForegroundColor White }
"WARNING" { Write-Host $logSatiri -ForegroundColor Yellow }
"ERROR" { Write-Host $logSatiri -ForegroundColor Red }
"SUCCESS" { Write-Host $logSatiri -ForegroundColor Green }
}
# Dosyaya yaz
try {
$logDizin = Split-Path $LogDosyasi
if (-not (Test-Path $logDizin)) {
New-Item -ItemType Directory -Path $logDizin -Force | Out-Null
}
Add-Content -Path $LogDosyasi -Value $logSatiri -Encoding UTF8
}
catch {
Write-Host "LOG YAZMA HATASI: $($_.Exception.Message)" -ForegroundColor Magenta
}
}
# Kullanım örneği
function Invoke-BackupOperation {
param([string]$KaynakDizin, [string]$HedefDizin)
Write-Log "Yedekleme başlatıldı: $KaynakDizin -> $HedefDizin" -Seviye "INFO"
try {
if (-not (Test-Path $KaynakDizin)) {
throw "Kaynak dizin bulunamadı: $KaynakDizin"
}
Copy-Item -Path $KaynakDizin -Destination $HedefDizin -Recurse -Force -ErrorAction Stop
Write-Log "Yedekleme tamamlandı" -Seviye "SUCCESS"
}
catch {
Write-Log "Yedekleme başarısız: $($_.Exception.Message)" -Seviye "ERROR"
Write-Log "Hata satırı: $($_.InvocationInfo.ScriptLineNumber)" -Seviye "ERROR"
return $false
}
return $true
}
Trap Kullanımı
Trap ifadesi, eski bir hata yakalama yöntemidir ama bazı durumlarda hala işe yarar. Özellikle script genelinde beklenmedik hataları yakalamak için kullanılabilir:
# Script başına global trap koy
trap {
Write-Host "Beklenmedik hata yakalandı!" -ForegroundColor Red
Write-Host "Hata: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Satır: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red
Write-Log "KRİTİK HATA: $($_.Exception.Message) - Satır: $($_.InvocationInfo.ScriptLineNumber)" -Seviye "ERROR"
break # Script'i durdur
}
# Script kodları devam eder...
Ancak modern PowerShell scriptlerinde trap yerine Try-Catch kullanmak daha okunabilir ve yönetilebilir kod üretir.
Verbose ve Warning Mesajları
Hata yönetiminin yanı sıra, scriptlerinizde bilgi mesajları için de doğru araçları kullanmak önemlidir:
function Update-ConfigFile {
[CmdletBinding()]
param(
[string]$KonfigDosyasi,
[hashtable]$Ayarlar
)
Write-Verbose "Config dosyası güncelleniyor: $KonfigDosyasi"
try {
$icerik = Get-Content $KonfigDosyasi -Raw -ErrorAction Stop
Write-Verbose "Dosya okundu: $($icerik.Length) karakter"
foreach ($anahtar in $Ayarlar.Keys) {
$eskiDeger = if ($icerik -match "$anahtars*=s*(.+)") { $Matches[1] } else { "bulunamadı" }
if ($eskiDeger -eq "bulunamadı") {
Write-Warning "$anahtar anahtarı config dosyasında bulunamadı, atlanıyor"
continue
}
$icerik = $icerik -replace "$anahtars*=s*.+", "$anahtar = $($Ayarlar[$anahtar])"
Write-Verbose "$anahtar güncellendi: $eskiDeger -> $($Ayarlar[$anahtar])"
}
Set-Content -Path $KonfigDosyasi -Value $icerik -Encoding UTF8 -ErrorAction Stop
Write-Verbose "Dosya kaydedildi"
}
catch {
Write-Error "Config güncellenemedi: $($_.Exception.Message)"
}
}
# Normal çalıştırma
Update-ConfigFile -KonfigDosyasi "C:appconfig.ini" -Ayarlar @{ "Port" = "8080"; "Timeout" = "30" }
# Verbose mesajlarla çalıştırma
Update-ConfigFile -KonfigDosyasi "C:appconfig.ini" -Ayarlar @{ "Port" = "8080" } -Verbose
Hata Ayıklamada PowerShell ISE ve VS Code
Script geliştirirken IDE breakpoint desteği büyük kolaylık sağlar. VS Code’da PowerShell Extension ile:
- Satır numarasına tıklayarak breakpoint koyabilirsiniz
- F5 ile debug modunda çalıştırabilirsiniz
- Değişkenlerin anlık değerlerini izleyebilirsiniz
- Step Over (F10) ve Step Into (F11) ile adım adım ilerleyebilirsiniz
Breakpoint’e alternatif olarak kod içinde şu yöntemi de kullanabilirsiniz:
# Script'i belirli bir noktada durdur ve interaktif console aç
$Host.EnterNestedPrompt()
# Veya Wait-Debugger kullan (PowerShell 5+)
Wait-Debugger
Sonuç
PowerShell’de hata yönetimi, bir script’in “çalışan bir şey” ile “production’a alınabilecek güvenilir bir araç” arasındaki farkı belirler. Try-Catch blokları, $Error değişkeni, -ErrorAction Stop parametresi ve merkezi log fonksiyonları bir arada kullanıldığında, scriptleriniz hem hatalara karşı dayanıklı hale gelir hem de sorun çıktığında nedenini bulmak çok daha kolaylaşır.
Özellikle şu noktalara dikkat edin: Non-terminating hataları unutmayın ve gerektiğinde -ErrorAction Stop ile terminating hata haline getirin. Her catch bloğunda sadece Write-Host ile ekrana yazmak yerine mutlaka log dosyasına da yazın. Finally bloğunu kaynak temizliği için kullanın. Ve $_.InvocationInfo.ScriptLineNumber bilgisini her zaman log’a ekleyin; ilerleyen dönemde sizi kurtaracak olan bilgi budur.
Sağlam hata yönetimi yazmak başta zahmetli görünebilir. Ancak gece 2’de pagerduty alarmıyla uyandığınızda ve log dosyasında tam olarak nerede, ne hatası oluştuğunu gördüğünüzde, bu zahmete değdiğini anlarsınız.