Kurumsal ortamlarda Exchange kullanıcılarının takvim çalışma saatleri (Working Hours) çoğu zaman standart dışı olabiliyor.
Bazı kullanıcılar 17:00’da bitirirken, bazıları 17:30, bazıları ise tamamen farklı saatlerde görünebiliyor.
Bu durum özellikle toplantı planlama, Free/Busy ve otomatik randevu senaryolarında ciddi karışıklıklara yol açıyor.
Bu yazıda, Exchange Online / Exchange Management Shell üzerinde çalışan ve bu sorunu uçtan uca çözen bir PowerShell script’ini paylaşıyorum.
Script Ne Yapar?
Bu script:
- Çalışma saatlerini hardcode etmez
- Başlangıç ve bitiş saatini kullanıcıdan alır
- Tüm mailbox’ları sıfırdan tarar
- Girilen saatlere uymayan kullanıcıları altta listeler
- Kullanıcıya sorar:
- “Bu hatalıları düzelteyim mi?”
- Onay verilirse (
y) kalıcı olarak düzeltir - Onay verilmezse (
n) script’i kapatır
Yani hem kontrol, hem raporlama, hem de düzeltme tek script içindedir.
Neden Kullanıcıdan Saat Alıyor?
Her firmanın çalışma saatleri farklıdır.
- 08:00 – 17:00
- 08:30 – 17:30
- 09:00 – 18:00
Bu yüzden script:
08.00,17.30gibi formatları kabul eder- Nokta (
.) veya iki nokta (:) farkını tolere eder - Yanlış format girilirse script’i durdurur
Bu sayede farklı ortamlarda tekrar tekrar düzenleme ihtiyacı olmaz.
Teknik Detaylar
Get-Mailboxile tüm mailbox’lar taranır- Her kullanıcı için:
Get-MailboxCalendarConfigurationçağrılır
- Hedef saatler ile karşılaştırma yapılır
- Uymayanlar memory’de bir listeye alınır
- Düzeltme aşamasında:
Set-MailboxCalendarConfigurationkullanılır
- Değişiklik kalıcıdır, sadece PowerShell objesi değil Exchange tarafı güncellenir
Script, sadece RAM’de değişiklik yapıp tekrar taramada eski veriyi getiren hatalı yaklaşımlardan özellikle kaçınır.
Kimler İçin Uygun?
- Exchange Online yöneten sistem ekipleri
- Hybrid veya Cloud Exchange ortamları
- Toplu kullanıcı yönetimi yapan IT ekipleri
- Çalışma saatlerini standartlaştırmak isteyen firmalar
Sonuç
Bu script sayesinde:
- Çalışma saatleri tek tek kontrol etmek zorunda kalmazsınız
- Hatalı kullanıcıları net şekilde görürsünüz
- Onay almadan otomatik değişiklik yapılmaz
- Farklı firma saatlerine kolayca uyarlanır
İhtiyaca göre:
- CSV export
- Sadece UserMailbox filtreleme
- Düzeltme sonrası otomatik yeniden tarama
gibi geliştirmeler de rahatlıkla eklenebilir.
# ========================= # Working Hours Audit + Fix # Exchange Online / EMS # ========================= function Convert-ToTimeSpan { param( [Parameter(Mandatory)] [string]$TimeText ) # Accept: 08.00 / 08:00 / 8.00 / 8:00 $t = $TimeText.Trim().Replace('.', ':') if ($t -notmatch '^\d{1,2}:\d{2}$') { throw "Saat formatı hatalı: '$TimeText' (örn: 08.00 veya 17.30)" } $parts = $t.Split(':') $h = [int]$parts[0] $m = [int]$parts[1] if ($h -lt 0 -or $h -gt 23 -or $m -lt 0 -or $m -gt 59) { throw "Saat değeri geçersiz: '$TimeText'" } return New-TimeSpan -Hours $h -Minutes $m } try { Write-Host "===== Working Hours TARAMA =====" -ForegroundColor Cyan $startInput = Read-Host "Başlangıç saati (örn. 08.00)" $endInput = Read-Host "Bitiş saati (örn. 17.30)" $targetStart = Convert-ToTimeSpan -TimeText $startInput $targetEnd = Convert-ToTimeSpan -TimeText $endInput Write-Host "" Write-Host "Hedef saatler: $($targetStart.ToString()) - $($targetEnd.ToString())" -ForegroundColor Green Write-Host "Tarama başlıyor..." -ForegroundColor Cyan Write-Host "" $hatalilar = New-Object System.Collections.Generic.List[object] Get-Mailbox -ResultSize Unlimited | ForEach-Object { $mbx = $_ try { $cal = Get-MailboxCalendarConfiguration -Identity $mbx.Identity -ErrorAction Stop $curStart = $cal.WorkingHoursStartTime $curEnd = $cal.WorkingHoursEndTime if ($curStart -ne $targetStart -or $curEnd -ne $targetEnd) { $hatalilar.Add([pscustomobject]@{ DisplayName = $mbx.DisplayName Identity = $mbx.Identity CurrentStart = $curStart CurrentEnd = $curEnd }) | Out-Null } } catch { # Okunamayanları da hatalı listeye düşürelim (yetki/nesne sorunu vs.) $hatalilar.Add([pscustomobject]@{ DisplayName = $mbx.DisplayName Identity = $mbx.Identity CurrentStart = "OKUNAMADI" CurrentEnd = "OKUNAMADI" }) | Out-Null } } Write-Host "" Write-Host "===== ❌ HATALILAR (Hedef: $($targetStart.ToString()) - $($targetEnd.ToString())) =====" -ForegroundColor Red if ($hatalilar.Count -eq 0) { Write-Host "Hatalı kullanıcı bulunmadı." -ForegroundColor Green return } # Altta topluca listele $hatalilar | Sort-Object DisplayName | Format-Table DisplayName, CurrentStart, CurrentEnd -AutoSize Write-Host "" $answer = Read-Host "Bu hatalıları düzelteyim mi? (y/n)" if ($answer.ToLower() -ne "y") { Write-Host "İşlem iptal edildi. Script kapanıyor." -ForegroundColor Yellow return } Write-Host "" Write-Host "===== 🔧 DÜZELTME BAŞLIYOR =====" -ForegroundColor Cyan foreach ($u in $hatalilar) { # OKUNAMADI olanlara dokunmayalım if ($u.CurrentStart -eq "OKUNAMADI" -or $u.CurrentEnd -eq "OKUNAMADI") { Write-Host "ATLANDI (okunamadı): $($u.DisplayName)" -ForegroundColor Yellow continue } try { Set-MailboxCalendarConfiguration ` -Identity $u.Identity ` -WorkingHoursStartTime $targetStart ` -WorkingHoursEndTime $targetEnd ` -ErrorAction Stop Write-Host "DÜZELTİLDİ: $($u.DisplayName)" -ForegroundColor Green } catch { Write-Host "HATA: $($u.DisplayName) -> $($_.Exception.Message)" -ForegroundColor Red } } Write-Host "" Write-Host "===== ✅ DÜZELTME BİTTİ. İstersen tekrar tarat. =====" -ForegroundColor Green } catch { Write-Host "Script hata ile durdu: $($_.Exception.Message)" -ForegroundColor Red }