Active Directory’de disable kullanıcıların risk seviyesini analiz eden, privileged hesapları tespit eden ve OU bazlı dağılım çıkaran hızlı ve detaylı HTML raporlama yönteminin anlatıldığı teknik rehber. Performans sorunlarını aşmak için optimize edilmiş PowerShell yaklaşımı ve gerçek hayatta kullanılabilir bir dashboard örneği.

Kurumsal yapılarda Active Directory üzerinde disable edilmiş kullanıcı hesaplarının sayısı genellikle fazla olur. Bu hesapların çoğu yıllardır dokunulmamış, ne zaman açıldığı, hangi OU’da durduğu, hangi gruplarda kaldığı ve en önemlisi privileged (Domain Admins, Administrators vb.) yetkilere sahip olup olmadığı bilinmeden yaşamaya devam eder.

Klasik yöntemlerde “Enabled = False” filtresi yapılır ve CSV dosyası dökülür. Fakat bu sadece yüzeysel bir temizlik sağlar. Asıl risk, privileged gruplarda unutulan disable hesaplar, yıllardır logon olmamış ama kritik yetkiler taşıyan objeler, departmanı kapanmış kullanıcılar gibi karmaşık senaryolarda ortaya çıkar.

 

Sonuç olarak ortaya, sadece listeleyen değil; analiz eden, karşılaştıran, renkli dashboard oluşturan, privileged hesapları otomatik işaretleyen, hem teknik hem yönetim seviyesinde okunabilir bir HTML risk raporu çıktı.

Aşağıda, bu raporu üreten hızlı PowerShell script’i paylaşacağım. AD üzerindeki tüm grupları sadece 1 kere çekerek, kullanıcı başına hızlı lookup yapan bu yaklaşım, büyük domain’lerde bile hızlıca rapor üretir.


⭐ Raporun Yaptıkları

Bu script:

  • Tüm disable kullanıcıları toplar

  • Son logon tarihine bakar

  • Parola son set edilme zamanını gösterir

  • OU bazlı dağılım çıkarır

  • Tüm grupları tek seferde toplayarak privileged üyelikleri tespit eder

  • 90+ gündür logon olmayanları işaretler

  • HTML dashboard oluşturarak:

    • Özet kutuları

    • OU dağılım tablosu

    • Privileged kullanıcıları kırmızı satır olarak

    • Badge ikonları

    • Detaylı tablo
      üretir

 


🧩 PowerShell Script 

 

 
<## FAST VERSION – Tüm grup üyelikleri tek seferde çekilir HTML output only ##> param( [string]$ReportPath = "C:\Reports\DisabledUsers", [int]$StaleDays = 90 ) Import-Module ActiveDirectory -ErrorAction Stop # klasör yoksa oluştur if (-not (Test-Path $ReportPath)) { New-Item -Path $ReportPath -ItemType Directory -Force | Out-Null } $today = Get-Date # Privileged group list $privilegedGroupNames = @( 'Domain Admins','Enterprise Admins','Administrators','Schema Admins', 'Account Operators','Backup Operators','Server Operators','DnsAdmins' ) Write-Host "[+] Disable kullanıcılar çekiliyor..." -ForegroundColor Cyan $disabledUsers = Get-ADUser -Filter 'Enabled -eq $false -and ObjectClass -eq "user"' -Properties ` DisplayName,SamAccountName,DistinguishedName,whenCreated,whenChanged,lastLogonDate, pwdLastSet,department,title,mail Write-Host "[+] Tüm grup üyelikleri tek seferde çekiliyor..." -ForegroundColor Cyan $allGroupsRaw = Get-ADGroup -Filter * -Properties Members # DN → Grup listesi $userToGroups = @{} foreach ($grp in $allGroupsRaw) { if ($grp.Members -eq $null) { continue } foreach ($mem in $grp.Members) { if (-not $userToGroups.ContainsKey($mem)) { $userToGroups[$mem] = New-Object System.Collections.Generic.List[string] } $userToGroups[$mem].Add($grp.Name) } } Write-Host "[+] Rapor oluşturuluyor..." -ForegroundColor Cyan $report = foreach ($user in $disabledUsers) { $lastLogon = $user.lastLogonDate $pwdLastSet = if ($user.pwdLastSet -and $user.pwdLastSet -ne 0) { [DateTime]::FromFileTime($user.pwdLastSet) } else { $null } $daysSinceLastLogon = if ($lastLogon) { ($today - $lastLogon).Days } else { $null } $ou = $user.DistinguishedName -replace '^CN=[^,]+,', '' $dn = $user.DistinguishedName $allGroups = @() if ($userToGroups.ContainsKey($dn)) { $allGroups = $userToGroups[$dn] } $privGroups = $allGroups | Where-Object { $_ -in $privilegedGroupNames } $isPrivileged = ($privGroups.Count -gt 0) [pscustomobject]@{ DisplayName = $user.DisplayName SamAccountName = $user.SamAccountName Mail = $user.Mail Department = $user.Department Title = $user.Title WhenCreated = $user.whenCreated WhenDisabledApprox = $user.whenChanged LastLogonDate = $lastLogon DaysSinceLastLogon = $daysSinceLastLogon PasswordLastSet = $pwdLastSet OU = $ou IsPrivileged = $isPrivileged PrivilegedGroups = ($privGroups -join ', ') AllGroups = ($allGroups -join ', ') } } # ----------- SUMMARY ------------- $summary = [pscustomobject]@{ ReportDate = $today TotalDisabled = $report.Count StaleOverThreshold = ($report | Where-Object { $_.DaysSinceLastLogon -ge $StaleDays }).Count PrivilegedDisabled = ($report | Where-Object { $_.IsPrivileged }).Count WithoutLastLogon = ($report | Where-Object { -not $_.LastLogonDate }).Count } # OU breakdown $byOU = $report | Group-Object OU | Sort-Object Count -Descending $htmlPath = Join-Path $ReportPath "DisabledUsers_Report.html" # ----------- CSS ------------- $css = @" <style> body { font-family: Arial; margin: 20px; } h1, h2 { color: #043a77; } table { border-collapse: collapse; width: 100%; margin-bottom: 20px; font-size: 13px; } th, td { border: 1px solid #ddd; padding: 6px 8px; } th { background-color: #043a77; color: #fff; } tr:nth-child(even) { background-color: #f9f9f9; } tr.privileged { background-color: #ffe5e5 !important; } .badge { padding: 2px 6px; border-radius: 4px; font-size: 11px; color: #fff; } .badge-stale { background-color: #c0392b; } .badge-fresh { background-color: #27ae60; } .badge-priv { background-color: #e74c3c; } .summary-box { display: inline-block; min-width: 180px; padding: 10px 15px; margin: 5px 10px 15px 0; border-radius: 6px; background-color: #f4f6f9; border: 1px solid #dfe4ea; } .summary-label { font-size: 12px; color: #555; } .summary-value { font-size: 18px; font-weight: bold; color: #043a77; } small { color: #666; } </style> "@ # ----------- HTML BAŞLANGICI ------------- $html = @" <!DOCTYPE html> <html> <head> <meta charset='UTF-8'> <title>AD Disable Kullanıcı Risk Raporu</title> $css </head> <body> <h1>AD Disable Kullanıcı Risk Raporu</h1> <p><small>Rapor Tarihi: $($summary.ReportDate)</small></p> <div class='summary-box'> <div class='summary-label'>Toplam Disable Kullanıcı</div> <div class='summary-value'>$($summary.TotalDisabled)</div> </div> <div class='summary-box'> <div class='summary-label'>$StaleDays+ gün logon olmayan</div> <div class='summary-value'>$($summary.StaleOverThreshold)</div> </div> <div class='summary-box'> <div class='summary-label'>Privileged Disable Kullanıcı</div> <div class='summary-value'>$($summary.PrivilegedDisabled)</div> </div> <div class='summary-box'> <div class='summary-label'>Hiç logon olmamış</div> <div class='summary-value'>$($summary.WithoutLastLogon)</div> </div> <h2>OU Bazlı Dağılım</h2> <table> <thead><tr><th>OU</th><th>Sayı</th></tr></thead> <tbody> "@ foreach ($ou in $byOU) { $html += "<tr><td>$($ou.Name)</td><td>$($ou.Count)</td></tr>" } $html += "</tbody></table>" # ----------- DETAY TABLO ------------- $html += @" <h2>Detaylı Liste</h2> <p><small>Kırmızı satırlar privileged disable hesapları gösterir.</small></p> <table> <thead> <tr> <th>DisplayName</th> <th>SamAccountName</th> <th>Mail</th> <th>Department</th> <th>Title</th> <th>CreateDate</th> <th>DisabledApprox</th> <th>LastLogon</th> <th>DaysSinceLL</th> <th>PasswordLastSet</th> <th>OU</th> <th>Privileged</th> <th>PrivilegedGroups</th> </tr> </thead> <tbody> "@ $sorted = $report | Sort-Object ` @{Expression="IsPrivileged";Descending=$true}, @{Expression="DaysSinceLastLogon";Descending=$true} foreach ($row in $sorted) { $cssClass = if ($row.IsPrivileged) { "class='privileged'" } else { "" } $badge = "" if ($row.DaysSinceLastLogon -ge $StaleDays) { $badge = "<span class='badge badge-stale'>$($row.DaysSinceLastLogon)</span>" } elseif ($row.DaysSinceLastLogon) { $badge = "<span class='badge badge-fresh'>$($row.DaysSinceLastLogon)</span>" } $privBadge = if ($row.IsPrivileged) { "<span class='badge badge-priv'>Evet</span>" } else { "Hayır" } $html += "<tr $cssClass> <td>$($row.DisplayName)</td> <td>$($row.SamAccountName)</td> <td>$($row.Mail)</td> <td>$($row.Department)</td> <td>$($row.Title)</td> <td>$($row.WhenCreated)</td> <td>$($row.WhenDisabledApprox)</td> <td>$($row.LastLogonDate)</td> <td>$badge</td> <td>$($row.PasswordLastSet)</td> <td>$($row.OU)</td> <td>$privBadge</td> <td>$($row.PrivilegedGroups)</td> </tr>" } $html += "</tbody></table></body></html>" # ----------- DOSYAYA YAZMA ------------- $html | Out-File -FilePath $htmlPath -Encoding UTF8 Write-Host "[✓] HTML raporu oluşturuldu: $htmlPath" -ForegroundColor Green