Windows ortamlarında güncelleme yönetimi, sysadmin hayatının en can sıkıcı ama bir o kadar da kritik parçalarından biri. WSUS konsolunda tıkla bırak yapmak, onlarca sunucuyu tek tek RDP ile açıp “Windows Update” çalıştırmak… Bu döngüden çıkmanın en temiz yolu PowerShell. Bugün sıfırdan, production ortamında kullanabileceğin scriptlerle Windows güncelleme yönetimini ele alacağız.
Neden PowerShell ile Güncelleme Yönetimi?
Elle yapılan güncelleme süreçlerinde en büyük sorun tutarsızlık. Bir sunucuya bağlandın, güncelleme yaptın, kayıt tutmayı unuttun. Üç ay sonra hangi sunucunun hangi patch seviyesinde olduğunu bilmiyorsun. Audit geldiğinde panik başlıyor.
PowerShell burada iki temel sorunu çözüyor: otomasyon ve raporlama. Script bir kez yazılır, scheduled task olarak çalışır, log dosyasına yazar. Sabah işe geldiğinde mail kutunda rapor seni bekliyor.
Bunun yanı sıra büyük ortamlarda PSRemoting sayesinde 50 sunucuya aynı anda güncelleme uygulayabilir, sonuçları merkezi olarak toplayabilirsin. Bu yazıda hem tek sunucu hem de çoklu sunucu senaryolarını işleyeceğiz.
PSWindowsUpdate Modülü: Temel Silahın
Windows’un built-in cmdlet’leri güncelleme yönetimi için oldukça kısıtlı. Bu yüzden sektörün fiili standardı haline gelmiş PSWindowsUpdate modülünü kullanacağız. Modülü kurmak için:
# PowerShell Gallery'den kurulum
Install-Module -Name PSWindowsUpdate -Force -AllowClobber
# Kurulumu doğrula
Get-Module -Name PSWindowsUpdate -ListAvailable
# Modülü import et
Import-Module PSWindowsUpdate
Modül kurulduktan sonra mevcut cmdlet’leri görmek için:
Get-Command -Module PSWindowsUpdate
Çıktıda göreceğin önemli cmdlet’ler şunlar:
- Get-WindowsUpdate: Mevcut güncellemeleri listeler
- Install-WindowsUpdate: Güncellemeleri indirir ve yükler
- Remove-WindowsUpdate: Belirli bir güncellemeyi kaldırır
- Get-WUHistory: Güncelleme geçmişini getirir
- Get-WURebootStatus: Bekleyen yeniden başlatma durumunu kontrol eder
Mevcut Güncelleme Durumunu Kontrol Etmek
İlk adım her zaman mevcut durumu anlamak. Hangi güncellemeler bekliyor, sistem ne zaman son güncellendi, reboot bekliyor mu?
# Bekleyen tüm güncellemeleri listele
Get-WindowsUpdate -AcceptAll -IgnoreReboot
# Sadece kritik güncellemeleri filtrele
Get-WindowsUpdate -Category "Critical Updates" -AcceptAll
# Security patch'leri getir
Get-WindowsUpdate -Category "Security Updates" -AcceptAll
# Güncelleme geçmişinin son 20 kaydını göster
Get-WUHistory -Last 20 | Select-Object -Property Date, Title, Result | Format-Table -AutoSize
# Reboot bekliyor mu kontrol et
Get-WURebootStatus -Silent
Get-WURebootStatus özellikle önemli. Production sunucularda bazen güncelleme uygulanmış ama reboot yapılmamış olabiliyor. Bu durumu script içinde yakalayıp uyarı oluşturabilirsin.
Temel Güncelleme Script’i
Basit ama işlevsel bir güncelleme scripti yazalım. Bu scripti küçük ortamlar için doğrudan kullanabilirsin:
# update_server.ps1
# Tek sunucu üzerinde güncelleme çalıştıran temel script
param(
[switch]$AutoReboot,
[string]$LogPath = "C:LogsWindowsUpdate"
)
# Log dizinini oluştur
if (-not (Test-Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
$LogFile = "$LogPathupdate_$(Get-Date -Format 'yyyyMMdd_HHmm').log"
$ServerName = $env:COMPUTERNAME
$StartTime = Get-Date
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogEntry = "[$TimeStamp] [$Level] $ServerName - $Message"
Write-Host $LogEntry
Add-Content -Path $LogFile -Value $LogEntry
}
Write-Log "Güncelleme süreci başlatıldı"
# Modülü import et
Import-Module PSWindowsUpdate -ErrorAction Stop
# Bekleyen güncellemeleri kontrol et
$PendingUpdates = Get-WindowsUpdate -AcceptAll -IgnoreReboot
$UpdateCount = ($PendingUpdates | Measure-Object).Count
Write-Log "Toplam bekleyen güncelleme sayısı: $UpdateCount"
if ($UpdateCount -eq 0) {
Write-Log "Yüklenecek güncelleme bulunamadı. Script sonlandırılıyor."
exit 0
}
# Güncellemeleri listele
foreach ($Update in $PendingUpdates) {
Write-Log "Bekleyen: $($Update.Title) - $($Update.Size) KB"
}
# Güncellemeleri yükle
Write-Log "Güncelleme yükleme başlıyor..."
Install-WindowsUpdate -AcceptAll -IgnoreReboot -Verbose 2>&1 |
ForEach-Object { Write-Log $_.ToString() }
$EndTime = Get-Date
$Duration = ($EndTime - $StartTime).TotalMinutes
Write-Log "Güncelleme tamamlandı. Süre: $([math]::Round($Duration, 2)) dakika"
# Reboot kontrolü
$RebootStatus = Get-WURebootStatus -Silent
if ($RebootStatus) {
Write-Log "SİSTEM YENİDEN BAŞLATMA GEREKTİRİYOR" "WARNING"
if ($AutoReboot) {
Write-Log "Otomatik reboot başlatılıyor (60 saniye sonra)..."
shutdown /r /t 60 /c "Windows Update - Otomatik Reboot"
}
}
Bu scripti çalıştırırken dikkat etmen gereken: production saatlerinde reboot olmayacak şekilde scheduled task oluştur. -AutoReboot parametresini maintenance window dışında asla kullanma.
Çoklu Sunucu Yönetimi
Asıl güç çoklu sunucu senaryolarında ortaya çıkıyor. PSRemoting aktif olan bir ortamda 50 sunucuya aynı anda güncelleme uygulayabilirsin:
# multi_server_update.ps1
# Birden fazla sunucuda paralel güncelleme
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerList,
[int]$ThrottleLimit = 10,
[string]$ReportPath = "C:ReportsWUReport_$(Get-Date -Format 'yyyyMMdd').csv"
)
$Results = [System.Collections.Concurrent.ConcurrentBag[PSObject]]::new()
$ScriptBlock = {
param($Computer)
$Result = [PSCustomObject]@{
ComputerName = $Computer
Status = "Bilinmiyor"
UpdateCount = 0
InstalledCount = 0
FailedCount = 0
RebootRequired = $false
ErrorMessage = ""
Timestamp = Get-Date
}
try {
$Session = New-PSSession -ComputerName $Computer -ErrorAction Stop
$UpdateResult = Invoke-Command -Session $Session -ScriptBlock {
Import-Module PSWindowsUpdate -ErrorAction Stop
$Updates = Get-WindowsUpdate -AcceptAll -IgnoreReboot
$Count = ($Updates | Measure-Object).Count
if ($Count -gt 0) {
$Installed = Install-WindowsUpdate -AcceptAll -IgnoreReboot -PassThru
$Success = ($Installed | Where-Object { $_.Result -eq "Installed" } | Measure-Object).Count
$Failed = ($Installed | Where-Object { $_.Result -ne "Installed" } | Measure-Object).Count
return @{
Total = $Count
Installed = $Success
Failed = $Failed
Reboot = (Get-WURebootStatus -Silent)
}
}
return @{ Total = 0; Installed = 0; Failed = 0; Reboot = $false }
}
$Result.Status = "Başarılı"
$Result.UpdateCount = $UpdateResult.Total
$Result.InstalledCount = $UpdateResult.Installed
$Result.FailedCount = $UpdateResult.Failed
$Result.RebootRequired = $UpdateResult.Reboot
Remove-PSSession $Session
}
catch {
$Result.Status = "Hata"
$Result.ErrorMessage = $_.Exception.Message
}
return $Result
}
# Paralel çalıştır
$ComputerList | ForEach-Object -Parallel {
$res = & $using:ScriptBlock -Computer $_
$using:Results.Add($res)
} -ThrottleLimit $ThrottleLimit
# Sonuçları CSV'ye yaz
$Results | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8
Write-Host "`nÖzet Rapor:"
Write-Host "Toplam Sunucu: $($Results.Count)"
Write-Host "Başarılı: $(($Results | Where-Object { $_.Status -eq 'Başarılı' }).Count)"
Write-Host "Hatalı: $(($Results | Where-Object { $_.Status -eq 'Hata' }).Count)"
Write-Host "Reboot Gereken: $(($Results | Where-Object { $_.RebootRequired }).Count)"
Write-Host "Rapor: $ReportPath"
ForEach-Object -Parallel özelliği PowerShell 7 ile geldi. Eğer hala Windows PowerShell 5.1 kullanıyorsan Invoke-Command -ComputerName $ComputerList -AsJob yapısını tercih et.
WSUS Entegrasyonu
Kurumsal ortamlarda güncellemeler doğrudan Microsoft’tan değil, iç WSUS sunucusundan gelir. PSWindowsUpdate bu durumu da destekliyor:
# WSUS sunucusundan güncelleme al
# Önce sunucuyu WSUS'a yönlendir
Add-WUServiceManager -ServiceID "7971f918-a847-4430-9279-4a52d1efe18d" -AddServiceFlag 7
# Veya registry üzerinden WSUS ayarını kontrol et
$WUSettings = Get-ItemProperty -Path "HKLM:SOFTWAREPoliciesMicrosoftWindowsWindowsUpdateAU"
Write-Host "WSUS Sunucusu: $((Get-ItemProperty 'HKLM:SOFTWAREPoliciesMicrosoftWindowsWindowsUpdate').WUServer)"
# WSUS üzerinden güncelleme al (MicrosoftUpdate yerine WindowsUpdate servisi)
Get-WindowsUpdate -ServiceID "7971f918-a847-4430-9279-4a52d1efe18d" -AcceptAll
# Belirli bir KB numarasını WSUS'tan zorla yükle
Install-WindowsUpdate -KBArticleID "KB5025228" -AcceptAll -IgnoreReboot
Büyük ortamlarda WSUS güncelleme onay süreçleri kritik. Scriptin WSUS onaylı güncellemeleri mi yoksa tüm güncellemeleri mi alacağını net olarak belirlemek gerekiyor. Bunu yanlış yapılandırırsan WSUS politikalarını bypass ederek Microsoft’tan direkt güncelleme çekmiş olursun, bu da hem bant genişliği hem uyumluluk açısından sorun yaratır.
Güncelleme Raporlama ve Compliance Kontrolü
Sadece güncelleme yapmak yetmez, neyin güncellendiğini belgemen gerekiyor. Özellikle PCI DSS veya ISO 27001 kapsamındaki sistemlerde yama yönetimi kayıtları zorunlu:
# patch_compliance_report.ps1
# Sistemin güncel olup olmadığını ve son 30 günlük yama geçmişini raporlar
param(
[string[]]$Servers,
[int]$DaysBack = 30,
[string]$OutputPath = "C:Reports"
)
if (-not (Test-Path $OutputPath)) {
New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
}
$ReportDate = Get-Date -Format "yyyyMMdd_HHmm"
$ReportFile = "$OutputPathPatchCompliance_$ReportDate.html"
$AllResults = @()
foreach ($Server in $Servers) {
try {
$Data = Invoke-Command -ComputerName $Server -ScriptBlock {
param($Days)
Import-Module PSWindowsUpdate
# Son N gündeki yüklenen güncellemeler
$CutoffDate = (Get-Date).AddDays(-$Days)
$History = Get-WUHistory | Where-Object {
$_.Date -ge $CutoffDate -and $_.Result -eq "Installed"
}
# Bekleyen güncellemeler
$Pending = Get-WindowsUpdate -AcceptAll -IgnoreReboot
# Son başarılı güncelleme tarihi
$LastUpdate = (Get-WUHistory |
Where-Object { $_.Result -eq "Installed" } |
Sort-Object Date -Descending |
Select-Object -First 1).Date
return @{
InstalledCount = ($History | Measure-Object).Count
PendingCount = ($Pending | Measure-Object).Count
LastUpdateDate = $LastUpdate
RebootRequired = (Get-WURebootStatus -Silent)
OSVersion = (Get-WmiObject Win32_OperatingSystem).Caption
InstalledKBs = $History | Select-Object Title, Date, KB
}
} -ArgumentList $DaysBack
$ComplianceStatus = if ($Data.PendingCount -eq 0) { "Uyumlu" }
elseif ($Data.PendingCount -le 5) { "Kısmi" }
else { "Uyumsuz" }
$AllResults += [PSCustomObject]@{
Sunucu = $Server
OSVersiyon = $Data.OSVersion
SonGuncelleme = $Data.LastUpdateDate
YukluSayisi = $Data.InstalledCount
BekleyenSayisi = $Data.PendingCount
RebootGerekli = $Data.RebootRequired
Uyumluluk = $ComplianceStatus
}
}
catch {
$AllResults += [PSCustomObject]@{
Sunucu = $Server
Uyumluluk = "BAĞLANTI HATASI"
BekleyenSayisi = -1
}
}
}
# Sonuçları ekrana yazdır
$AllResults | Format-List
# CSV olarak kaydet
$AllResults | Export-Csv -Path "$OutputPathCompliance_$ReportDate.csv" -NoTypeInformation -Encoding UTF8
Write-Host "Rapor kaydedildi: $OutputPathCompliance_$ReportDate.csv"
Scheduled Task ile Otomatik Güncelleme
Script hazır, şimdi bunu otomatik çalışacak şekilde ayarlayalım. Maintenance window genellikle Cuma gecesi 02:00 ile Cumartesi 06:00 arası belirlenir:
# Scheduled Task oluştur
$TaskName = "AutoWindowsUpdate"
$ScriptPath = "C:Scriptsupdate_server.ps1"
$LogPath = "C:LogsWindowsUpdate"
$TaskUser = "DOMAINsvc_wsupdate" # Dedicated service account kullan
# Action tanımla
$Action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File `"$ScriptPath`" -LogPath `"$LogPath`""
# Trigger: Her Cuma 02:30
$Trigger = New-ScheduledTaskTrigger `
-Weekly `
-DaysOfWeek Friday `
-At "02:30AM"
# Ayarlar: Güç kaynağı fark etmeksizin çalış, maksimum 4 saat
$Settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 4) `
-RunOnlyIfNetworkAvailable `
-WakeToRun `
-StartWhenAvailable
# Principal: SYSTEM yerine dedicated account kullan
$Principal = New-ScheduledTaskPrincipal `
-UserId $TaskUser `
-LogonType Password `
-RunLevel Highest
# Task'ı kaydet
Register-ScheduledTask `
-TaskName $TaskName `
-TaskPath "CustomTasks" `
-Action $Action `
-Trigger $Trigger `
-Settings $Settings `
-Principal $Principal `
-Description "Haftalık otomatik Windows güncelleme - Maintenance Window"
Write-Host "Scheduled Task oluşturuldu: $TaskName"
# Task durumunu kontrol et
Get-ScheduledTask -TaskName $TaskName | Select-Object TaskName, State, LastRunTime, NextRunTime
Servis hesabı kullanımı burada kritik. SYSTEM hesabıyla da çalışır ama domain ortamında, özellikle PSRemoting kullanan scriptlerde, dedicated bir servis hesabı çok daha yönetilebilir bir yapı sunar. Bu hesaba minimum gerekli yetkileri ver: Local Admin veya wsus_admin rolü yeterli.
Gerçek Dünya Senaryosu: Acil Security Patch Deployment
Diyelim ki zero-day açığı çıktı ve Microsoft acil patch yayınladı. IT manager sabah 7’de seni arıyor, “Bu KB’yi bugün mesai bitimine kadar tüm sunuculara uyguladık mı?” diyor. İşte bu senaryo için pratik script:
# emergency_patch.ps1
# Belirli bir KB'yi hedef sunuculara acil olarak uygular
param(
[Parameter(Mandatory=$true)]
[string]$KBNumber,
[Parameter(Mandatory=$true)]
[string]$ServerListFile, # Sunucu listesi TXT dosyası, her satırda bir sunucu
[string]$ReportPath = "C:ReportsEmergencyPatch_$(Get-Date -Format 'yyyyMMdd_HHmm').csv",
[switch]$WhatIf
)
$Servers = Get-Content $ServerListFile | Where-Object { $_ -ne "" -and $_ -notlike "#*" }
Write-Host "Hedef sunucu sayısı: $($Servers.Count)"
Write-Host "Hedef KB: $KBNumber"
if ($WhatIf) {
Write-Host "WHATIF MODU: Gerçek kurulum yapılmayacak"
}
$Results = @()
foreach ($Server in $Servers) {
Write-Host "[$Server] İşleniyor..." -ForegroundColor Cyan
$Result = [PSCustomObject]@{
Sunucu = $Server
KB = $KBNumber
Durum = ""
Mesaj = ""
Tarih = Get-Date
}
try {
$PingResult = Test-Connection -ComputerName $Server -Count 1 -Quiet
if (-not $PingResult) {
$Result.Durum = "PING_FAIL"
$Result.Mesaj = "Sunucuya erişilemiyor"
$Results += $Result
continue
}
$PatchResult = Invoke-Command -ComputerName $Server -ScriptBlock {
param($KB, $TestMode)
Import-Module PSWindowsUpdate
# Güncelleme mevcut mu?
$Update = Get-WindowsUpdate -KBArticleID $KB -AcceptAll -IgnoreReboot
if (($Update | Measure-Object).Count -eq 0) {
# Zaten yüklü mü kontrol et
$History = Get-WUHistory | Where-Object { $_.KB -eq $KB }
if ($History) {
return @{ Status = "ZATEN_YUKLU"; Message = "Önceden yüklendi: $($History[0].Date)" }
}
return @{ Status = "BULUNAMADI"; Message = "KB güncelleme listesinde yok" }
}
if ($TestMode) {
return @{ Status = "WHATIF"; Message = "WhatIf modu, yüklenmedi" }
}
$Install = Install-WindowsUpdate -KBArticleID $KB -AcceptAll -IgnoreReboot
$RebootNeeded = Get-WURebootStatus -Silent
return @{
Status = "YUKLENDI"
Message = "Başarıyla yüklendi. Reboot gerekli: $RebootNeeded"
Reboot = $RebootNeeded
}
} -ArgumentList $KBNumber, $WhatIf.IsPresent
$Result.Durum = $PatchResult.Status
$Result.Mesaj = $PatchResult.Message
$Color = switch ($PatchResult.Status) {
"YUKLENDI" { "Green" }
"ZATEN_YUKLU" { "Yellow" }
"BULUNAMADI" { "Red" }
default { "White" }
}
Write-Host "[$Server] $($PatchResult.Status): $($PatchResult.Message)" -ForegroundColor $Color
}
catch {
$Result.Durum = "HATA"
$Result.Mesaj = $_.Exception.Message
Write-Host "[$Server] HATA: $($_.Exception.Message)" -ForegroundColor Red
}
$Results += $Result
}
$Results | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8
Write-Host "`n=== ÖZET ===" -ForegroundColor White
Write-Host "Toplam: $($Results.Count)"
Write-Host "Yüklendi: $(($Results | Where-Object Durum -eq 'YUKLENDI').Count)" -ForegroundColor Green
Write-Host "Zaten Yüklü: $(($Results | Where-Object Durum -eq 'ZATEN_YUKLU').Count)" -ForegroundColor Yellow
Write-Host "Hata: $(($Results | Where-Object { $_.Durum -in 'HATA','PING_FAIL','BULUNAMADI' } ).Count)" -ForegroundColor Red
Write-Host "Rapor: $ReportPath"
Bu scripti önce -WhatIf parametresiyle çalıştır, hangi sunuculara ulaşıldığını gör, sonra gerçek kurulumu başlat. Acil durumlarda bile test et sonra uygula prensibinden vazgeçme.
Sık Karşılaşılan Sorunlar ve Çözümleri
Production ortamında bu scriptleri kullanırken karşılaşacağın bazı yaygın sorunlar var:
PSRemoting Bağlantı Sorunları
- WinRM servisinin çalışıp çalışmadığını
Test-WSMan -ComputerName sunucuile kontrol et - Firewall’da 5985 (HTTP) veya 5986 (HTTPS) portlarının açık olduğundan emin ol
- Domain dışı sunucular için TrustedHosts ayarını yapılandırman gerekebilir
PSWindowsUpdate Modül Hatası
- Modül remote sunucuda yüklü olmayabilir. Scripte
Install-Modulebloğu ekle ama bunu dikkatli yönet - Execution Policy kısıtlaması:
Set-ExecutionPolicy RemoteSigned -Scope LocalMachineile çöz - Proxy arkasındaki sunucularda PowerShell Gallery erişimi olmayabilir, bu durumda modülü manuel dağıt
Güncelleme Takılı Kalıyor
- Windows Update Agent bazen corrupt duruma geliyor.
wuauclt.exe /detectnowveyaUsoClient.exe StartScanile servis sıfırlama yapabilirsin - Disk doluysa güncelleme indirme başarısız olur. Script içine disk kontrolü ekle
Reboot Sonrası Script Tamamlanmıyor
- Uzun güncelleme süreçlerinde sistem reboot olursa script yarıda kalır
- Çözüm:
Get-WURebootStatuskontrolünü döngü içine al, reboot öncesi sonuçları kaydet
Sonuç
PowerShell ile güncelleme yönetimi, başlangıçta karmaşık görünse de doğru yapılandırılmış scriptlerle sysadmin hayatını ciddi ölçüde kolaylaştırıyor. Bu yazıda anlattığımız yaklaşımı kendi ortamına adapte ederken şu noktaları aklından çıkarma:
Her script önce test ortamında çalışmalı. Özellikle -AutoReboot gibi parametreler production saatlerinde felaket yaratabilir. Maintenance window’ları Scheduled Task tetikleyicilerinde net olarak tanımla, değiştiğinde güncellemeyi unutma.
Raporlama kısmını atlama. Güncelleme yapmak zaten işin yarısı; yaptığını kanıtlamak diğer yarısı. Compliance ve audit süreçlerinde CSV raporları hayat kurtarıyor.
Büyüyen ortamlarda bu scriptleri SCCM, Ansible veya benzeri araçlarla entegre etmeyi düşün. Ama o araçlara geçene kadar, iyi yazılmış bir PowerShell script seti seni çok iyi götürür.
Son olarak: bu scriptleri kopyalayıp direkt kullanma. Kendi ortamına göre yolları, log mekanizmalarını, hata kontrollerini adapte et. Her ortam farklı, her sysadmin’in workflow’u farklı. Scripti senin ihtiyacına göre şekillendir, sahiplen.