PowerShell ile Zamanlanmış Görev Oluşturma ve Yönetme
Windows ortamlarında tekrarlayan görevleri otomatikleştirmek, bir sysadmin’in hayatını kurtaran en temel becerilerden biridir. Task Scheduler GUI’si güzel görünür, ama onlarca sunucuya aynı zamanlanmış görevi dağıtmak gerektiğinde fare tıklamak yerine PowerShell’e sarılmak çok daha mantıklıdır. Bu yazıda PowerShell ile zamanlanmış görev oluşturmayı, yönetmeyi ve gerçek dünya senaryolarında nasıl kullanacağını ele alacağız.
Zamanlanmış Görevlere Neden PowerShell ile Bakmalıyız?
Task Scheduler arayüzü yeterince işlevsel, bunu kabul edelim. Ama düşün: 20 sunucuya aynı backup scriptini zamanlanmış görev olarak eklemen gerekiyor. Her birine RDP açıp GUI’den tıklamak mı? Ya da bir PowerShell scripti çalıştırıp işi 2 dakikada bitirmek mi?
PowerShell’in ScheduledTasks modülü, Windows 8 ve Windows Server 2012’den itibaren kullanılabilir durumda. Bu modül sayesinde zamanlanmış görevleri tamamen komut satırından yönetebilir, configuration management araçlarına (Ansible, DSC, vb.) entegre edebilir ve değişiklikleri version control altında tutabilirsin.
Temel Cmdlet’leri Tanıyalım
Başlamadan önce mevcut cmdlet’lere göz atalım:
Get-Command -Module ScheduledTasks
En çok kullanacağın cmdlet’ler şunlardır:
- New-ScheduledTask: Yeni bir görev objesi oluşturur (henüz kaydetmez)
- Register-ScheduledTask: Görevi sisteme kaydeder
- Set-ScheduledTask: Mevcut görevi günceller
- Get-ScheduledTask: Görevleri listeler
- Start-ScheduledTask: Görevi manuel olarak başlatır
- Stop-ScheduledTask: Çalışan görevi durdurur
- Unregister-ScheduledTask: Görevi sistemden siler
- Get-ScheduledTaskInfo: Son çalışma zamanı, sonuç kodu gibi bilgileri getirir
İlk Zamanlanmış Görevi Oluşturmak
Zamanlanmış görev oluşturmak birkaç parçadan oluşur: Action (ne yapılacak), Trigger (ne zaman çalışacak) ve Settings (nasıl davranacak). Bu parçaları ayrı ayrı oluşturup sonra birleştiriyoruz.
Basit bir örnekle başlayalım. Her gün gece 02:00’de bir PowerShell scriptini çalıştıran görev:
# Action tanımla - ne çalışacak
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File C:ScriptsDailyCleanup.ps1" `
-WorkingDirectory "C:Scripts"
# Trigger tanımla - ne zaman çalışacak
$trigger = New-ScheduledTaskTrigger `
-Daily `
-At "02:00AM"
# Principal tanımla - hangi kullanıcı altında çalışacak
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
# Ayarları tanımla
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 10) `
-MultipleInstances IgnoreNew
# Görevi kaydet
Register-ScheduledTask `
-TaskName "DailyCleanup" `
-TaskPath "CustomTasks" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Günlük temizlik işlemi - Her gece 02:00"
Bu yapıda dikkat etmem gereken birkaç nokta var. TaskPath parametresi görevin Task Scheduler’da hangi klasörde görüneceğini belirliyor. CustomTasks gibi özel klasörler kullanmak, yüzlerce görev arasında düzen sağlamak açısından kritik.
Farklı Trigger Türleri
Gerçek hayatta çok farklı tetikleyici ihtiyaçları çıkıyor. İşte en sık kullandığım trigger tipleri:
# Haftalık - Her Pazartesi ve Cuma 06:00'da
$triggerWeekly = New-ScheduledTaskTrigger `
-Weekly `
-DaysOfWeek Monday, Friday `
-At "06:00AM"
# Sistem başlangıcında
$triggerAtStartup = New-ScheduledTaskTrigger `
-AtStartup
# Kullanıcı oturum açtığında
$triggerAtLogon = New-ScheduledTaskTrigger `
-AtLogOn `
-User "DOMAINServiceAccount"
# Tek seferlik - Belirli tarih ve saatte
$triggerOnce = New-ScheduledTaskTrigger `
-Once `
-At "2024-12-31 23:59:00"
# Her X dakikada bir tekrar (RepetitionInterval ile)
$triggerRepeat = New-ScheduledTaskTrigger `
-RepetitionInterval (New-TimeSpan -Minutes 15) `
-Once `
-At (Get-Date)
Burada RepetitionInterval biraz kafa karıştırıcı olabiliyor. -Once ile başlatıp RepetitionInterval eklediğinde aslında “şu andan itibaren her 15 dakikada bir çalıştır” demiş oluyorsun. Bunu kullanırken RepetitionDuration ile de ne kadar süre tekrar edeceğini belirtebilirsin, belirtmezsen sonsuza kadar devam eder.
Servis Hesabı ile Çalıştırma
Production ortamlarında görevleri SYSTEM hesabıyla değil, özel servis hesaplarıyla çalıştırmak en iyi pratiktir. Şifre gerektiren durumlarda:
# Domain servis hesabı ile - şifre gerektirir
$credential = Get-Credential -UserName "DOMAINsvc_scheduler" -Message "Servis hesabı şifresi"
$principal = New-ScheduledTaskPrincipal `
-UserId "DOMAINsvc_scheduler" `
-LogonType Password `
-RunLevel Highest
Register-ScheduledTask `
-TaskName "DatabaseBackup" `
-TaskPath "DBTasks" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Password $credential.GetNetworkCredential().Password
Güvenlik açısından şifreyi düz metin olarak script içinde tutmaktan kaçın. Yukarıdaki Get-Credential yöntemi interaktif senaryolarda çalışır. Otomasyon için Windows Credential Manager veya Azure Key Vault gibi çözümlere bakman gerekir.
Mevcut Görevleri Listeleme ve Sorgulama
Sunucudaki görevleri incelemenin çeşitli yolları var:
# Tüm custom görevleri listele (Windows dahili görevleri hariç)
Get-ScheduledTask -TaskPath "CustomTasks*" |
Select-Object TaskName, State, Description |
Format-Table -AutoSize
# Sadece çalışan görevleri bul
Get-ScheduledTask | Where-Object {$_.State -eq "Running"}
# Son çalışma bilgileri ile birlikte detaylı liste
Get-ScheduledTask -TaskPath "CustomTasks*" | ForEach-Object {
$info = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath
[PSCustomObject]@{
TaskName = $_.TaskName
State = $_.State
LastRunTime = $info.LastRunTime
LastResult = $info.LastTaskResult
NextRunTime = $info.NextRunTime
}
} | Format-Table -AutoSize
# Hata veren görevleri bul (LastTaskResult 0 değilse hata var)
Get-ScheduledTask | ForEach-Object {
$info = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath
if ($info.LastTaskResult -ne 0 -and $info.LastRunTime -ne $null) {
Write-Host "HATA - $($_.TaskName): Sonuç kodu $($info.LastTaskResult)" -ForegroundColor Red
}
}
LastTaskResult değeri 0 ise başarılı demektir. 0x41301 görevi hala çalışıyor, 0x1 genel hata anlamına gelir. Bu kodları Task Scheduler’ın kendi dökümanlarında bulabilirsin.
Gerçek Dünya Senaryosu: Log Temizleme Görevi
Şimdi gerçekten işe yarar bir şey yapalım. IIS log dosyalarını belirli bir süreden eski olanları temizleyen otomatik bir sistem kuralım:
# Önce log temizleme scriptini oluştur
$cleanupScript = @'
param(
[int]$DaysOld = 30,
[string]$LogPath = "C:inetpublogsLogFiles"
)
$cutoffDate = (Get-Date).AddDays(-$DaysOld)
$deletedCount = 0
$freedSpace = 0
Get-ChildItem -Path $LogPath -Recurse -Filter "*.log" |
Where-Object {$_.LastWriteTime -lt $cutoffDate} |
ForEach-Object {
$freedSpace += $_.Length
Remove-Item $_.FullName -Force
$deletedCount++
Write-EventLog -LogName Application -Source "LogCleanup" `
-EventId 1000 -EntryType Information `
-Message "Silindi: $($_.FullName)"
}
$freedMB = [math]::Round($freedSpace / 1MB, 2)
Write-EventLog -LogName Application -Source "LogCleanup" `
-EventId 1001 -EntryType Information `
-Message "Temizlik tamamlandı. Silinen: $deletedCount dosya, Kazanılan alan: $freedMB MB"
'@
$cleanupScript | Out-File "C:ScriptsCleanIISLogs.ps1" -Encoding UTF8
# Şimdi zamanlanmış görevi oluştur
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File C:ScriptsCleanIISLogs.ps1 -DaysOld 30"
$trigger = New-ScheduledTaskTrigger `
-Weekly `
-DaysOfWeek Sunday `
-At "03:00AM"
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable:$false `
-MultipleInstances IgnoreNew
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
Register-ScheduledTask `
-TaskName "IIS-LogCleanup-Weekly" `
-TaskPath "Maintenance" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Haftalık IIS log temizleme - 30 günden eski dosyaları siler"
Write-Host "Görev başarıyla oluşturuldu!" -ForegroundColor Green
-StartWhenAvailable parametresi çok değerli. Görev tetiklendiği anda sunucu kapalıysa, açıldığında görevi otomatik başlatır. Gece çalışması gereken görevler için bu ayarı mutlaka etkinleştir.
Çoklu Sunuculara Görev Dağıtmak
İşte PowerShell’in gerçek gücü burada ortaya çıkıyor. Aynı görevi 20 sunucuya dağıtmak:
# Hedef sunucu listesi
$servers = @(
"WEB01", "WEB02", "WEB03",
"APP01", "APP02",
"DB01"
)
$scriptBlock = {
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File C:ScriptsHealthCheck.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "01:00AM"
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-StartWhenAvailable
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-LogonType ServiceAccount `
-RunLevel Highest
# Görev zaten varsa güncelle, yoksa oluştur
$existingTask = Get-ScheduledTask -TaskName "NightlyHealthCheck" -ErrorAction SilentlyContinue
if ($existingTask) {
Set-ScheduledTask `
-TaskName "NightlyHealthCheck" `
-Action $action `
-Trigger $trigger `
-Settings $settings
Write-Output "$env:COMPUTERNAME: Görev güncellendi"
} else {
Register-ScheduledTask `
-TaskName "NightlyHealthCheck" `
-TaskPath "Monitoring" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings
Write-Output "$env:COMPUTERNAME: Görev oluşturuldu"
}
}
# Tüm sunuculara paralel olarak gönder
$results = Invoke-Command -ComputerName $servers -ScriptBlock $scriptBlock -ThrottleLimit 5
$results | ForEach-Object { Write-Host $_ -ForegroundColor Green }
-ThrottleLimit parametresi aynı anda kaç sunucuya bağlanacağını sınırlar. Ağ üzerinde ani yük oluşturmamak için bunu makul bir değerde tutmak önemli.
Görev Güncelleme ve Silme
Mevcut görevi güncellemek için Set-ScheduledTask kullanıyoruz:
# Sadece trigger'ı güncelle
$newTrigger = New-ScheduledTaskTrigger -Daily -At "04:00AM"
Set-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks" -Trigger $newTrigger
# Sadece action'ı güncelle
$newAction = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File C:ScriptsDailyCleanup-v2.ps1"
Set-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks" -Action $newAction
# Görevi devre dışı bırak (silmeden)
Disable-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks"
# Tekrar etkinleştir
Enable-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks"
# Görevi sil - onay ister
Unregister-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks"
# Onay sormadan sil
Unregister-ScheduledTask -TaskName "DailyCleanup" -TaskPath "CustomTasks" -Confirm:$false
# Bir klasördeki tüm görevleri sil
Get-ScheduledTask -TaskPath "OldTasks*" |
Unregister-ScheduledTask -Confirm:$false
Settings Parametrelerini Doğru Kullanmak
New-ScheduledTaskSettingsSet cmdlet’i, görevin nasıl davranacağını belirleyen çok sayıda parametre içeriyor. Production’da mutlaka düşünmen gerekenler:
- -ExecutionTimeLimit: Görevin maksimum çalışma süresi. Varsayılan 72 saat, bu genellikle çok uzun.
- -StartWhenAvailable: Planlanan zaman kaçırılırsa, sistem uygun olduğunda çalıştır.
- -MultipleInstances: Aynı görevin birden fazla instance’ı için davranış.
IgnoreNewgenellikle en güvenli seçenek. - -RestartCount ve -RestartInterval: Hata durumunda kaç kez ve ne sıklıkla yeniden dene.
- -RunOnlyIfIdle: Sistem boşta değilse çalıştırma. Server ortamlarında bunu kapatman gerekebilir.
- -WakeToRun: Görev tetiklendiğinde bilgisayarı uyku modundan uyandır.
- -Priority: Görevin işlemci önceliği. 1 (en yüksek) ile 10 (en düşük) arası.
- -Hidden: Görevi Task Scheduler arayüzünde gizle (dikkatli kullan).
# Production için önerilen settings kombinasyonu
$productionSettings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 4) `
-StartWhenAvailable `
-MultipleInstances IgnoreNew `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 30) `
-RunOnlyIfIdle:$false `
-Priority 7
Görev Çıktısını ve Loglarını Yönetmek
Zamanlanmış görevlerin başarıyla çalışıp çalışmadığını takip etmek kritik. PowerShell scriptlerin çıktısını dosyaya yönlendirmek için action tanımına bunu ekleyebilirsin:
# Script çıktısını log dosyasına yönlendir
$logDate = (Get-Date -Format "yyyyMMdd")
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -NoProfile -ExecutionPolicy Bypass -Command `"& 'C:ScriptsBackup.ps1' *> 'C:LogsBackup_$logDate.log'`""
Alternatif olarak, scriptin içinde transcript kullanmak daha temiz bir yaklaşım:
# Script içi loglama örneği
$logScript = @'
$logPath = "C:LogsTaskLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
Start-Transcript -Path $logPath -Append
try {
Write-Host "$(Get-Date) - Görev başladı"
# Ana iş burada yapılır
Write-Host "$(Get-Date) - Görev tamamlandı"
}
catch {
Write-Host "$(Get-Date) - HATA: $_" -ForegroundColor Red
exit 1
}
finally {
Stop-Transcript
}
'@
$logScript | Out-File "C:ScriptsBackupWithLogging.ps1" -Encoding UTF8
Görev Durumlarını İzlemek için Monitoring Scripti
Sunucularındaki tüm özel görevlerin sağlık durumunu tek bakışta görmek için:
function Get-ScheduledTaskHealth {
param(
[string]$TaskPath = "CustomTasks",
[int]$AlertIfNotRunInDays = 2
)
$tasks = Get-ScheduledTask -TaskPath "$TaskPath*"
foreach ($task in $tasks) {
$info = Get-ScheduledTaskInfo -TaskName $task.TaskName -TaskPath $task.TaskPath
$status = "OK"
$color = "Green"
# Son çalışmadan bu yana çok zaman geçtiyse
if ($info.LastRunTime -and $info.LastRunTime -lt (Get-Date).AddDays(-$AlertIfNotRunInDays)) {
$status = "UYARI - Uzun süredir çalışmadı"
$color = "Yellow"
}
# Hata kodu varsa
if ($info.LastTaskResult -ne 0 -and $info.LastTaskResult -ne $null) {
$status = "HATA - Kod: 0x{0:X}" -f $info.LastTaskResult
$color = "Red"
}
# Görev devre dışıysa
if ($task.State -eq "Disabled") {
$status = "DISABLED"
$color = "Gray"
}
Write-Host ("[{0}] {1}: {2}" -f $status.PadRight(20), $task.TaskName, $info.LastRunTime) -ForegroundColor $color
}
}
# Kullanımı
Get-ScheduledTaskHealth -TaskPath "Maintenance" -AlertIfNotRunInDays 3
Sık Karşılaşılan Sorunlar ve Çözümleri
Zamanlanmış görevlerle uğraşırken sıkça karşılaştığım problemler ve çözümleri:
- Görev çalışıyor ama hiçbir şey yapmıyor: ExecutionPolicy sorunu.
-ExecutionPolicy Bypassparametresini action’a eklediğinden emin ol. - “Erişim engellendi” hatası: Görevin çalıştığı kullanıcının gerekli izinleri olmayabilir. Principal ayarlarını kontrol et.
- Interaktif pencere açılıyor:
-NonInteractive -WindowStyle Hiddenparametrelerini ekle. - 32-bit vs 64-bit sorunları:
C:WindowsSystem32WindowsPowerShellv1.0powershell.exeyerine sadecepowershell.exekullanmak bazen 32-bit versiyonu çağırabilir. Açıkça tam yol belirt. - Network sürücüsüne erişilemiyor: SYSTEM hesabı network sürücülerine erişemeyebilir. Domain hesabı kullan veya UNC path kullan (
\serversharegibi). - Görev çalıştı ama sonuç kodu 0x1: Scriptin içindeki ilk hata göreve yansır. Script içine try-catch ekle ve hataları logla.
Sonuç
PowerShell ile zamanlanmış görev yönetimi, GUI’ye göre başlangıçta biraz fazla kod yazmayı gerektiriyor gibi görünebilir. Ama bu kod bir kez yazıldığında tekrar kullanılabilir, version control altına alınabilir ve otomatik deployment pipeline’larına dahil edilebilir hale geliyor.
Özetlemek gerekirse temel akışı hatırlamak işleri kolaylaştırır: Action, Trigger, Principal ve Settings objelerini ayrı ayrı oluştur, sonra Register-ScheduledTask ile bunları bir araya getir. Çoklu sunucu senaryolarında Invoke-Command ile kombinle. Görev çıktılarını her zaman logla ve Get-ScheduledTaskInfo ile düzenli izle.
Bir sonraki adım olarak bu yapıyı DSC (Desired State Configuration) ile entegre etmeyi veya bir configuration management aracına bağlamayı düşünebilirsin. Böylece görev tanımları merkezi bir yerde tutulur ve tüm sunucularda tutarlı kalması otomatik olarak sağlanır.
