Windows ortamlarında sorun giderme yaparken en çok başvurduğum kaynak Event Log’lardır. Bir servis çöktüğünde, bir uygulama hata verdiğinde ya da güvenlik ihlali şüphesi olduğunda ilk açtığım yer Event Viewer olur. Ama Event Viewer’ı elle karıştırmak yerine PowerShell ile bu logları programatik olarak yönetmek, hem zamandan tasarruf sağlar hem de tekrarlanabilir süreçler oluşturmanıza imkan tanır. Bu yazıda sıfırdan özel event log kaynakları oluşturmaktan, log yönetimine, filtrelemeye ve gerçek dünya otomasyon senaryolarına kadar her şeyi ele alacağız.
Windows Event Log Sistemine Hızlı Bakış
Windows Event Log mimarisi üç temel bileşen üzerine kuruludur. Log kanalları (Application, System, Security gibi), bu kanallara yazan kaynaklar (event source) ve her kaydın benzersiz kimliği olan Event ID‘ler. Kendi uygulamanız veya scriptiniz için log tutmak istiyorsanız önce bir kaynak (source) oluşturmanız gerekiyor. Bu kaynağı oluşturduktan sonra o kaynak üzerinden istediğiniz kanala log yazabilirsiniz.
PowerShell’de Event Log yönetimi için iki farklı cmdlet seti mevcut:
- Get-EventLog / Write-EventLog / New-EventLog: Eski, klasik Windows event logları için kullanılır
- Get-WinEvent: Hem klasik hem de ETW (Event Tracing for Windows) tabanlı yeni log formatlarını okuyabilir, daha güçlüdür
İkisini de öğrenmek gerekiyor çünkü production ortamlarında her ikisiyle de karşılaşacaksınız.
Yeni Event Log Kaynağı Oluşturma
Kendi uygulamanız veya scriptiniz için log yazmadan önce bir kaynak oluşturmanız şart. Bu işlem için administrator yetkisi gerekiyor.
# Yeni bir event log kaynağı oluşturma - temel kullanım
New-EventLog -LogName "Application" -Source "MyBackupScript"
# Özel bir log kanalı oluşturma (tavsiye edilen yöntem)
New-EventLog -LogName "CompanyLogs" -Source "DeploymentTool"
# Kaynağın var olup olmadığını kontrol etme
[System.Diagnostics.EventLog]::SourceExists("MyBackupScript")
Kaynağı oluştururken dikkat etmeniz gereken bir nokta var: Aynı kaynak adı sistemde sadece bir log kanalına kayıt olabilir. Yani “MyBackupScript” kaynağını Application log’una bağladıysanız, aynı adı başka bir log için kullanamazsınız. Bu yüzden kaynak adlarını şirkete veya projeye özgü prefix’lerle oluşturmak iyi bir alışkanlık.
Özel bir log kanalı oluşturduğunuzda log dosyasının boyutunu ve davranışını da ayarlayabilirsiniz:
# Özel log kanalı oluşturma - gelişmiş ayarlar
New-EventLog -LogName "ITOps-ApplicationLogs" -Source "ServerMonitor", "DeployAgent", "BackupService"
# Log boyutunu ve overwrite davranışını ayarlama
Limit-EventLog -LogName "ITOps-ApplicationLogs" -MaximumSize 50MB -OverflowAction OverwriteOlder -RetentionDays 30
# Log ayarlarını doğrulama
Get-EventLog -List | Where-Object { $_.LogDisplayName -like "*ITOps*" }
-OverflowAction parametresi için üç seçenek var:
- OverwriteOlder: Belirlenen günden eski kayıtların üzerine yazar, en çok tercih edilen
- OverwriteAsNeeded: Yer bitince en eski kaydın üzerine yazar
- DoNotOverwrite: Dolu olduğunda yeni kayıt yazmaz, kritik loglar için düşünülebilir ama dikkatli kullanın
Event Log’a Kayıt Yazma
Kaynak oluşturulduktan sonra log yazmak çok basit. Write-EventLog cmdlet’i üç kritik parametre alıyor: EventID, EntryType ve Message.
# Temel log yazma
Write-EventLog -LogName "Application" -Source "MyBackupScript" -EventId 1001 -EntryType Information -Message "Yedekleme işlemi başarıyla tamamlandı. Toplam 450 dosya yedeklendi."
# Hata logu yazma
Write-EventLog -LogName "Application" -Source "MyBackupScript" -EventId 2001 -EntryType Error -Message "Yedekleme sırasında hata oluştu: Hedef disk dolu. Gerekli alan: 50GB, Mevcut alan: 2GB"
# Uyarı logu yazma
Write-EventLog -LogName "Application" -Source "MyBackupScript" -EventId 1501 -EntryType Warning -Message "Yedekleme tamamlandı ancak 3 dosya erişim hatası nedeniyle atlandı."
# Category ve RawData ile detaylı log
Write-EventLog -LogName "Application" -Source "MyBackupScript" -EventId 1001 -EntryType Information -Message "Backup completed" -Category 1 -RawData 1,2,3
EntryType için kullanabileceğiniz değerler:
- Information: Rutin işlem bilgileri için
- Warning: Dikkat edilmesi gereken durumlar için
- Error: Başarısız işlemler için
- SuccessAudit: Başarılı güvenlik denetimleri için
- FailureAudit: Başarısız güvenlik denetimleri için
EventId konusunda bir standart oturtmanızı öneririm. Örneğin 1000-1999 arası Information, 2000-2999 Error, 3000-3999 Warning gibi bir ayrım yapmak, log sorgularınızı kolaylaştırır.
Event Log Okuma ve Filtreleme
Log yazmak kadar önemli olan log okumak. Burada iki farklı yaklaşım göstereceğim.
Get-EventLog ile Klasik Yöntem
# Son 100 kaydı getirme
Get-EventLog -LogName Application -Newest 100
# Belirli bir kaynaktan gelen logları filtreleme
Get-EventLog -LogName Application -Source "MyBackupScript" -Newest 50
# Belirli bir zaman aralığındaki hataları listeleme
$startDate = (Get-Date).AddDays(-7)
Get-EventLog -LogName Application -EntryType Error -After $startDate | Select-Object TimeGenerated, Source, EventID, Message
# EventID'ye göre filtreleme
Get-EventLog -LogName System -InstanceId 7036 -Newest 20 | Select-Object TimeGenerated, Message
Get-WinEvent ile Modern Yöntem
Get-WinEvent çok daha esnek bir filtreleme mekanizması sunuyor. Özellikle büyük log dosyalarında performans farkı belirgin şekilde hissedilir.
# FilterHashtable ile hızlı filtreleme
Get-WinEvent -FilterHashtable @{
LogName = 'Application'
ProviderName = 'MyBackupScript'
Level = 2 # 1=Critical, 2=Error, 3=Warning, 4=Information
StartTime = (Get-Date).AddHours(-24)
} | Select-Object TimeCreated, Id, LevelDisplayName, Message
# Birden fazla EventID ile sorgulama
Get-WinEvent -FilterHashtable @{
LogName = 'System'
Id = 7034, 7035, 7036 # Service control manager event IDs
StartTime = (Get-Date).AddDays(-3)
} | Format-Table TimeCreated, Id, Message -AutoSize
# Belirli bir keyword içeren mesajları arama
Get-WinEvent -LogName Application | Where-Object { $_.Message -like "*yedekleme*" } | Select-Object -First 20
Level değerleri için referans:
- 1: Critical
- 2: Error
- 3: Warning
- 4: Information
- 5: Verbose
Gerçek Dünya Senaryosu: Backup Script’i için Log Sistemi
Şimdi gerçekçi bir örneğe bakalım. Kurumsal ortamda kullandığım bir backup script’inin log altyapısını birlikte oluşturalım.
# Backup-WithLogging.ps1
# Log altyapısını hazırlama fonksiyonu
function Initialize-BackupLogging {
param(
[string]$LogName = "ITOps-Backup",
[string]$Source = "DailyBackupService"
)
# Kaynak var mı kontrol et
if (-not [System.Diagnostics.EventLog]::SourceExists($Source)) {
Write-Host "Log kaynagi olusturuluyor: $Source" -ForegroundColor Yellow
New-EventLog -LogName $LogName -Source $Source -ErrorAction Stop
Limit-EventLog -LogName $LogName -MaximumSize 100MB -OverflowAction OverwriteOlder -RetentionDays 60
Write-Host "Log kanali olusturuldu: $LogName" -ForegroundColor Green
}
}
# Merkezi log yazma fonksiyonu
function Write-BackupLog {
param(
[Parameter(Mandatory)]
[string]$Message,
[ValidateSet("Information","Warning","Error")]
[string]$Level = "Information",
[int]$EventId = 1000
)
$source = "DailyBackupService"
$logName = "ITOps-Backup"
Write-EventLog -LogName $logName -Source $source -EventId $EventId -EntryType $Level -Message $Message
# Konsola da yaz (script manuel çalıştırıldığında görünsün)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$color = switch($Level) {
"Error" { "Red" }
"Warning" { "Yellow" }
default { "Green" }
}
Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color
}
# Backup işlemini simüle eden ana fonksiyon
function Start-BackupJob {
param([string]$SourcePath, [string]$DestinationPath)
Initialize-BackupLogging
Write-BackupLog -Message "Yedekleme basliyor. Kaynak: $SourcePath, Hedef: $DestinationPath" -EventId 1001
try {
# Disk alanı kontrolü
$destDrive = Split-Path -Qualifier $DestinationPath
$freeSpace = (Get-PSDrive -Name $destDrive.TrimEnd(':')).Free
if ($freeSpace -lt 5GB) {
Write-BackupLog -Message "Disk alani kritik seviyede dusuk: $(($freeSpace/1GB).ToString('F2')) GB" -Level Warning -EventId 1501
}
# Dosyaları kopyala
$files = Get-ChildItem -Path $SourcePath -Recurse -File
$copiedCount = 0
$errorCount = 0
foreach ($file in $files) {
try {
$destFile = $file.FullName.Replace($SourcePath, $DestinationPath)
$destDir = Split-Path $destFile -Parent
if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }
Copy-Item -Path $file.FullName -Destination $destFile -Force
$copiedCount++
} catch {
$errorCount++
Write-BackupLog -Message "Dosya kopyalanamadi: $($file.FullName). Hata: $($_.Exception.Message)" -Level Warning -EventId 1502
}
}
if ($errorCount -eq 0) {
Write-BackupLog -Message "Yedekleme basariyla tamamlandi. Kopyalanan dosya: $copiedCount" -EventId 1002
} else {
Write-BackupLog -Message "Yedekleme tamamlandi ancak $errorCount hata olustu. Basarili: $copiedCount" -Level Warning -EventId 1503
}
} catch {
Write-BackupLog -Message "KRITIK HATA: Yedekleme islemi basarisiz. $($_.Exception.Message)" -Level Error -EventId 2001
throw
}
}
Bu script’i çalıştırdıktan sonra logları sorgulamak için:
# Son 24 saatte oluşan tüm backup loglarını getir
Get-WinEvent -FilterHashtable @{
LogName = 'ITOps-Backup'
StartTime = (Get-Date).AddHours(-24)
} | Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-List
Log Temizleme ve Arşivleme
Production ortamlarında log yönetimi sadece yazmak ve okumaktan ibaret değil. Periyodik temizlik ve arşivleme de önemli.
# Log kanalını temizleme (dikkatli kullanın!)
Clear-EventLog -LogName "ITOps-Backup"
# Log'u XML formatında dışa aktarma (arşivleme)
$exportPath = "C:LogArchivebackup-logs-$(Get-Date -Format 'yyyyMMdd').evtx"
$logPath = (Get-WmiObject Win32_NTEventLogFile -Filter "LogFileName='ITOps-Backup'").Name
Copy-Item -Path $logPath -Destination $exportPath
# Arşivlenen log dosyasını okuma
Get-WinEvent -Path $exportPath | Select-Object TimeCreated, Id, Message
# Eski logları otomatik temizleme scripti
function Remove-OldEventLogs {
param(
[string]$LogName,
[int]$DaysToKeep = 30
)
$cutoffDate = (Get-Date).AddDays(-$DaysToKeep)
$oldEntries = Get-EventLog -LogName $LogName -Before $cutoffDate
if ($oldEntries.Count -gt 0) {
Write-Host "$($oldEntries.Count) eski kayit bulundu. Temizleniyor..." -ForegroundColor Yellow
# Not: Get-EventLog ile dogrudan silme yapılamaz, log'u temizleyip yeniden doldurmak gerekir
# Production'da genellikle Limit-EventLog ile otomatik yonetim tercih edilir
Write-Host "Öneri: Limit-EventLog ile OverwriteOlder kullanın" -ForegroundColor Cyan
}
}
Remote Makinelerde Event Log Yönetimi
Birden fazla sunucu yönetiyorsanız remote event log sorgulama hayat kurtarır.
# Uzak makinede log sorgulama
$servers = @("Server01", "Server02", "Server03")
$results = foreach ($server in $servers) {
try {
Get-WinEvent -ComputerName $server -FilterHashtable @{
LogName = 'System'
Level = 1, 2 # Critical ve Error
StartTime = (Get-Date).AddHours(-4)
} -ErrorAction Stop | Select-Object @{N='Server';E={$server}}, TimeCreated, Id, LevelDisplayName, Message
} catch {
Write-Warning "$server sunucusuna erisim saglanamadi: $($_.Exception.Message)"
}
}
# Sonuçları ekrana ve CSV'ye yaz
$results | Sort-Object TimeCreated -Descending | Format-Table -AutoSize
$results | Export-Csv -Path "C:ReportsMultiServer-EventLogs-$(Get-Date -Format 'yyyyMMdd-HHmm').csv" -NoTypeInformation -Encoding UTF8
# Uzak makinede log kaynağı oluşturma
Invoke-Command -ComputerName "Server01" -ScriptBlock {
if (-not [System.Diagnostics.EventLog]::SourceExists("RemoteMonitor")) {
New-EventLog -LogName "Application" -Source "RemoteMonitor"
}
}
Event Log Tabanlı Alert Sistemi
Son olarak, kritik event’leri izleyip e-posta gönderen basit bir alert sistemi kuralım. Scheduled Task ile saatlik çalıştırabilirsiniz.
# EventLog-AlertSystem.ps1
function Send-EventAlert {
param(
[string]$Subject,
[string]$Body,
[string]$SmtpServer = "mail.sirket.local",
[string]$To = "[email protected]",
[string]$From = "[email protected]"
)
try {
Send-MailMessage -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject -Body $Body -Encoding UTF8
Write-Host "Alert e-postasi gonderildi: $Subject" -ForegroundColor Green
} catch {
Write-Warning "E-posta gonderilemedi: $($_.Exception.Message)"
}
}
function Check-CriticalEvents {
param(
[string[]]$Servers = @("localhost"),
[int]$CheckIntervalMinutes = 60
)
$startTime = (Get-Date).AddMinutes(-$CheckIntervalMinutes)
$criticalEvents = @()
foreach ($server in $Servers) {
# Kritik sistem hataları
$systemErrors = Get-WinEvent -ComputerName $server -FilterHashtable @{
LogName = 'System'
Level = 1, 2
StartTime = $startTime
} -ErrorAction SilentlyContinue
# Uygulama hataları
$appErrors = Get-WinEvent -ComputerName $server -FilterHashtable @{
LogName = 'Application'
Level = 1, 2
StartTime = $startTime
} -ErrorAction SilentlyContinue
# Güvenlik başarısız oturum açma denemeleri (EventID 4625)
$failedLogins = Get-WinEvent -ComputerName $server -FilterHashtable @{
LogName = 'Security'
Id = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue
if ($systemErrors) { $criticalEvents += $systemErrors | Select-Object @{N='Server';E={$server}}, @{N='LogName';E={'System'}}, TimeCreated, Id, LevelDisplayName, Message }
if ($appErrors) { $criticalEvents += $appErrors | Select-Object @{N='Server';E={$server}}, @{N='LogName';E={'Application'}}, TimeCreated, Id, LevelDisplayName, Message }
# Brute force kontrolü: 5'ten fazla başarısız giriş
if ($failedLogins -and $failedLogins.Count -ge 5) {
$alertMsg = "GUVENLIK UYARISI: $server sunucusunda son $CheckIntervalMinutes dakikada $($failedLogins.Count) basarisiz oturum acma denemesi!"
Send-EventAlert -Subject "[ALERT] Brute Force Girişimi - $server" -Body $alertMsg
# Bu güvenlik uyarısını da log'a yaz
Write-EventLog -LogName Application -Source "EventAlertSystem" -EventId 9001 -EntryType Warning -Message $alertMsg
}
}
# Kritik event özeti e-postası
if ($criticalEvents.Count -gt 0) {
$body = "Son $CheckIntervalMinutes dakikada tespit edilen kritik olaylar:`n`n"
$body += $criticalEvents | ForEach-Object {
"[$($_.Server)] $($_.TimeCreated) - EventID: $($_.Id) - $($_.LevelDisplayName)`n$($_.Message)`n---`n"
} | Out-String
Send-EventAlert -Subject "[ALERT] $($criticalEvents.Count) Kritik Event Tespit Edildi" -Body $body
}
return $criticalEvents.Count
}
# Scripti çalıştır
$eventCount = Check-CriticalEvents -Servers @("Server01", "Server02") -CheckIntervalMinutes 60
Write-Host "Kontrol tamamlandi. Kritik event sayisi: $eventCount"
Bu scripti Scheduled Task olarak kaydetmek için:
# Scheduled Task olarak kaydetme
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NonInteractive -ExecutionPolicy Bypass -File C:ScriptsEventLog-AlertSystem.ps1"
$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 60) -Once -At (Get-Date)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "EventLog-AlertSystem" -Action $action -Trigger $trigger -Principal $principal -Description "Kritik event loglarini izler ve alert gonderir"
Sonuç
PowerShell ile Event Log yönetimi, Windows ortamlarında operasyonel görünürlüğü ciddi şekilde artırıyor. Bu yazıda ele aldığımız başlıkları özetlemek gerekirse:
- New-EventLog ve Limit-EventLog ile özel, iyi yapılandırılmış log kanalları oluşturabilirsiniz
- Write-EventLog ile scriptleriniz ve uygulamalarınız standart bir log altyapısına kayıt yazabilir
- Get-WinEvent ve FilterHashtable kombinasyonu, büyük log dosyalarında bile hızlı sorgulama yapmanızı sağlar
- Remote sorgulama ile tüm sunucu parkınızı tek noktadan izleyebilirsiniz
- Alert sistemi kurarak kritik sorunlardan anında haberdar olabilirsiniz
Pratikte öğrendiğim en önemli şey şu: EventID’lerinizi baştan standartlaştırın. Hangi sayı aralığının hangi kategoriye ait olduğunu belirleyin ve tüm scriptlerinizde bu standardı uygulayın. İleride log analizi yaptığınızda veya SIEM sisteminize log beslediğinizde bu düzeni kurmamış olmaktan dolayı pişman olursunuz. Bir saatlik planlama, sonraki ayların sorun giderme süresinden tasarruf sağlar.