Bu örnek betiği kopyalayıp yapıştırın ve ortamınız için gerektiği gibi değiştirin:

<# . ÖZET     Dağıtım tamamlanana kadar çalışan Sürekli Güvenli Önyükleme dağıtım düzenleyicisi.

.DESCRIPTION     Bu betik, Güvenli Önyükleme sertifikası dağıtımı için uçtan uca tam otomasyon sağlar:     1.      Toplama verilerine göre dağıtım dalgaları oluşturur     2. Her dalga için AD grupları ve GPO oluşturur     3. Cihaz güncelleştirmeleri için izleyiciler (Olay 1808)     4. Engellenen demetleri algılar (ulaşılamayan cihazlar)     5. Otomatik olarak sonraki dalgaya ilerler     6. TÜM uygun cihazlar güncelleştirilene      kadar çalışır     Tamamlanma Ölçütleri:     - Içinde cihaz kalmadı: Eylem Gerekli, Yüksek Güvenilirlik, Gözlem, Geçici Olarak Duraklatıldı     - Kapsam dışı (tasarım gereği): Desteklenmiyor, Güvenli Önyükleme Devre Dışı     - Tamamlanana kadar sürekli çalışır - rastgele dalga sınırı      yoktur     Dağıtım Stratejisi:     - YÜKSEK GÜVENİlİRLİk: İlk dalgadaki tüm cihazlar (güvenli)     - EYLEM GEREKLİ: Aşamalı çiftler (1→2→4→8...)          Engelleme Mantığı:     - MaxWaitHours'dan sonra, orchestrator henüz güncelleştirilmemiş cihazlara ping işlemi gerçekleştirmektedir     - Cihaz ULAŞILAMIYORSA (ping başarısız oluyorsa) → demet araştırma için ENGELLENDİ     - Cihaz REACHABLE ise ancak güncelleştirilmediyse → beklemeye devam edin (yeniden başlatma gerekebilir)     - Engellenen demetler, yönetici engellerini      kaldırana kadar dışlanır     Engellemeyi Otomatik Olarak Kaldırma:     - Engellenen demetteki bir cihaz daha sonra güncelleştirilmiş olarak görünüyorsa (Olay 1808),       demet otomatik olarak engeli kaldırılır ve dağıtım devam eder     - Bu, geçici olarak çevrimdışı olan ancak geri      gelen cihazları işler     Cihaz İzleme:     - Cihazları konak adına göre izler (dağıtım sırasında adların değişmediği varsayılır)     - Not: JSON koleksiyonu benzersiz bir makine kimliği içermez; daha iyi izleme için bir tane ekleyin

.PARAMETER AggregationInputPath     Ham JSON cihaz verilerinin yolu (Detect betiğinden)

.PARAMETER ReportBasePath     Toplama raporları için temel yol

.PARAMETER TargetOU     GPO'ları bağlamak için OU'nun Ayırt Edici Adı.İsteğe bağlı - belirtilmezse, GPO etki alanı genelinde kapsama için etki alanı köküne bağlanır.Güvenlik grubu filtreleme, ilkeyi yalnızca hedeflenen cihazların almasını sağlar.

.PARAMETER MaxWaitHours     Erişilebilirliği denetlemeden önce cihazların güncelleştirilmesini beklemek için saatler.Bu süreden sonra, güncelleştirilmemiş cihazlara ping işlemi güncelleştirilir.Ulaşılamayan cihazlar demetin engellenmesine neden olur.Varsayılan: 72 (3 gün)

.PARAMETER PollIntervalMinutes     Durum denetimleri arasındaki dakika. Varsayılan: 1440 (1 gün)

.PARAMETER AllowListPath     Ana bilgisayar adlarını içeren bir dosyanın yolu: ALLOW for rollout (hedefli dağıtım)..txt (satır başına bir ana bilgisayar adı) veya .csv (Hostname/ComputerName/Name sütunuyla) destekler.Belirtildiğinde, YALNIZCA bu cihazlar kullanıma sunulacaktır.BlockList, AllowList'in ardından uygulanmaya devam ediyor.

.PARAMETER AllowADGroup     ALLOW için bilgisayar hesapları içeren bir AD güvenlik grubunun adı.Örnek: "SecureBoot-Pilot-Computers" veya "Wave1-Devices"     Belirtildiğinde, YALNIZCA bu gruptaki cihazlar piyasaya sunulacaktır.Hem dosya hem de AD tabanlı hedefleme için AllowListPath ile birleştirin.

.PARAMETER ExclusionListPath     Dağıtımdan DıŞLAMAK için ana bilgisayar adlarını içeren bir dosyanın yolu (VIP/yönetici cihazları)..txt (satır başına bir ana bilgisayar adı) veya .csv (Hostname/ComputerName/Name sütunuyla) destekler.Bu cihazlar hiçbir zaman herhangi bir dağıtım dalgasına dahil edilmeyecektir.BlockList, AllowList filtrelemeden sonra uygulanır.     . PARAMETER ExcludeADGroup     Dışlanması gereken bilgisayar hesaplarını içeren bir AD güvenlik grubunun adı.Örnek: "VIP-Computers" veya "Executive-Devices"     Hem dosya hem de AD tabanlı dışlamalar için ExclusionListPath ile birleştirin.

.PARAMETER UseWinCS     GPO/AvailableUpdatesPolicy yerine WinCS (Windows Yapılandırma Sistemi) kullanın.WinCS, WinCsFlags.exe doğrudan her uç noktada çalıştırarak Güvenli Önyükleme etkinleştirmesini dağıtır.WinCsFlags.exe zamanlanmış bir görev aracılığıyla SİSTEM bağlamı altında çalışır.Bu yöntem şunlar için yararlıdır:     - Daha hızlı dağıtımlar (GPO işleme için beklemeye karşı anında etki)     - Etki alanına katılmamış cihazlar     - AD/GPO altyapısı olmayan ortamlar     Başvuru: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe

.PARAMETER WinCSKey     Güvenli Önyükleme etkinleştirmesi için kullanılacak WinCS anahtarı.Varsayılan: F33E0C8E002     Bu anahtar Güvenli Önyükleme dağıtımı yapılandırmasına karşılık gelir.     . DRYRun PARAMETRESI     Değişiklik yapmadan yapılacakları göster

.PARAMETER ListBlockedBuckets     Şu anda engellenen tüm demetleri ve çıkışı görüntüleme

.PARAMETER UnblockBucket     Belirli bir demetin engellemesini anahtara göre kaldırma ve çıkma

.PARAMETER UnblockAll     Tüm demetlerin ve çıkışların engellemesini kaldırma

.PARAMETER EnableTaskOnDisabled     devre dışı bırakılmış zamanlanmış görevi olan tüm cihazlara Enable-SecureBootUpdateTask.ps1 dağıtın.-Quiet ile betiği etkinleştir seçeneğini çalıştıran tek seferlik zamanlanmış bir göreve sahip bir GPO oluşturur.Bu, Secure-Boot-Update görevi devre dışı bırakılmış cihazları düzeltmek için yararlıdır.

.EXAMPLE     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -TargetOU "OU=Workstations,DC=contoso,DC=com"

.EXAMPLE     # Engellenen demetleri listeleme     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets

.EXAMPLE     # Belirli bir demetin engellemesini kaldırma     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"

.EXAMPLE     # VIP cihazlarını metin dosyası kullanarak piyasaya çıkarma     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExclusionListPath "C:\Admin\VIP-Devices.txt"

.EXAMPLE     # Bir AD güvenlik grubundaki cihazları dışlama (örneğin, yönetici dizüstü bilgisayarlar)     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExcludeADGroup "VIP-Computers"

.EXAMPLE     # GPO/AvailableUpdatesPolicy yerine WinCS (Windows Yapılandırma Sistemi) kullanın     # WinCsFlags.exe zamanlanmış görev aracılığıyla her uç noktada SİSTEM bağlamı altında çalışır     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -UseWinCS '         -WinCSKey "F33E0C8E002" #>

[CmdletBinding()] param(     [Parameter(Mandatory = $false)]     [string]$AggregationInputPath,     [Parameter(Zorunlu = $false)]     [string]$ReportBasePath,     [Parameter(Zorunlu = $false)]     [string]$TargetOU,     [Parameter(Zorunlu = $false)]     [string]$WavePrefix = "SecureBoot-Rollout",     [Parameter(Zorunlu = $false)]     [int]$MaxWaitHours = 72,     [Parameter(Zorunlu = $false)]     [int]$PollIntervalMinutes = 1440,                         

    [Parameter(Mandatory = $false)]     [int]$ProcessingBatchSize = 5000,

    [Parameter(Mandatory = $false)]     [int]$DeviceLogSampleSize = 25,

    [Parameter(Mandatory = $false)]     [switch]$LargeScaleMode,     # ============================================================================     # AllowList / BlockList Parametreleri     # ============================================================================     # AllowList = Yalnızca bu cihazları dahil et (hedeflenen dağıtım)     # BlockList = Bu cihazları hariç tut (hiçbir zaman piyasaya sürülmeyecek)     # İşleme sırası: Önce AllowList (belirtilirse), ardından BlockList     [Parameter(Zorunlu = $false)]     [string]$AllowListPath,     [Parameter(Zorunlu = $false)]     [string]$AllowADGroup,     [Parameter(Zorunlu = $false)]     [string]$ExclusionListPath,     [Parameter(Zorunlu = $false)]     [string]$ExcludeADGroup,     # ============================================================================     # WinCS (Windows Yapılandırma Sistemi) Parametreleri     # ============================================================================     # WinCS, AvailableUpdatesPolicy GPO dağıtımına bir alternatiftir.                              # Güvenli Önyükleme dağıtımını etkinleştirmek için her uç noktada WinCsFlags.exe kullanır.# WinCsFlags.exe uç nokta üzerindeki SYSTEM bağlamı altında çalışır.# Başvuru: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe          [Parameter(Zorunlu = $false)]     [switch]$UseWinCS,          [Parameter(Zorunlu = $false)]     [string]$WinCSKey = "F33E0C8E002",          [Parameter(Zorunlu = $false)]     [switch]$DryRun,          [Parameter(Mandatory = $false)]     [switch]$ListBlockedBuckets,          [Parametre(Zorunlu = $false)]     [string]$UnblockBucket,          [Parameter(Zorunlu = $false)]     [switch]$UnblockAll,          [Parameter(Zorunlu = $false)]     [switch]$EnableTaskOnDisabled )

$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Dağıtım ve İzleme Örnekleri"

# ============================================================================ # BAĞıMLıLıK DOĞRULAMA # ============================================================================

function Test-ScriptDependencies {     param(         [Parameter(Zorunlu = $true)]         [string]$ScriptDirectory,         [Parameter(Zorunlu = $true)]         [dize[]]$RequiredScripts     )     $missingScripts = @()     foreach ($RequiredScripts $script) {         $scriptPath = Join-Path $ScriptDirectory $script         if (-not (Test-Path $scriptPath)) {             $missingScripts += $script         }     }     if ($missingScripts.Count -gt 0) {         "" Write-Host         Write-Host ("=" * 70) -ForegroundColor Red         Write-Host " EKSİk BAĞIMLAR" -ForegroundColor Red         Write-Host ("=" * 70) -ForegroundColor Red         "" Write-Host         Write-Host "Aşağıdaki gerekli betikler bulunamadı:" -ForegroundColor Yellow         foreach ($missingScripts $script) {             Write-Host " - $script" -ForegroundColor White         }         "" Write-Host         Write-Host "Lütfen en son betikleri indirin:" -ForegroundColor Cyan         Write-Host " URL: $DownloadUrl" -ForegroundColor White         Write-Host " Git: '$DownloadSubPage'" -ForegroundColor White         "" Write-Host         Write-Host "Tüm betikleri aynı dizine ayıklayın ve yeniden çalıştırın." -ForegroundColor Yellow         "" Write-Host         return $false     }     $true döndür }                             

# Required scripts for orchestrator $requiredScripts = @(     "Aggregate-SecureBootData.ps1",     "Enable-SecureBootUpdateTask.ps1",     "Deploy-GPO-SecureBootCollection.ps1",     "Detect-SecureBootCertUpdateStatus.ps1" )

if (-not (Test-ScriptDependencies -ScriptDirectory $PSScriptRoot -RequiredScripts $requiredScripts)) {     çıkış 1 }

# ============================================================================ # PARAMETRE DOĞRULAMA # ============================================================================

# Admin commands only need ReportBasePath $isAdminCommand = $ListBlockedBuckets -veya $UnblockBucket -ya da $UnblockAll -veya $EnableTaskOnDisabled

if (-not $ReportBasePath) {     Write-Host "ERROR: -ReportBasePath gerekli." -ForegroundColor Red     çıkış 1 }

if (-not $isAdminCommand -and -not $AggregationInputPath) {     Write-Host "HATA: -AggregationInputPath dağıtım için gerekli (-ListBlockedBuckets, -UnblockBucket, -UnblockAll için gerekli değildir)" -ForegroundColor Red     çıkış 1 }

# ============================================================================ # GPO ALGıLAMA - ALGıLAMA GPO'SU OLUP OLMADıĞıNı DENETLEYIN # ============================================================================

if (-not $isAdminCommand -and -not $DryRun) {     $CollectionGPOName = "SecureBoot-EventCollection"     # GroupPolicy modülünün kullanılabilir olup olmadığını denetleyin     if (Get-Module -ListAvailable -Name GroupPolicy) {         Import-Module GroupPolicy -ErrorAction SilentlyContinue         Write-Host "Algılama GPO'su denetleniyor..." -ForegroundColor Yellow         try {             # GPO olup olmadığını denetleyin             $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue             if ($existingGpo) {                 Write-Host " Algılama GPO'su bulundu: $CollectionGPOName" -ForegroundColor Green             } else {                 "" Write-Host                 Write-Host ("=" * 70) -ForegroundColor Yellow                 Write-Host " UYARI: ALGıLAMA GPO'SU BULUNAMADı" -ForegroundColor Sarı                 Write-Host ("=" * 70) -ForegroundColor Yellow                 "" Write-Host                 Write-Host "Algılama GPO '$CollectionGPOName' bulunamadı." -ForegroundColor Yellow                 Write-Host "Bu GPO olmadan hiçbir cihaz verisi toplanmaz." -ForegroundColor Yellow                 "" Write-Host                 Write-Host "Algılama GPO'sunu dağıtmak için şunu çalıştırın:" -ForegroundColor Cyan                 Write-Host " .\Deploy-GPO-SecureBootCollection.ps1 -DomainName <etki alanı> -AutoDetectOU" -ForegroundColor White                 "" Write-Host                 "Yine de devam et" Write-Host                                     (Y/N)" -ForegroundColor Yellow                 $response = Okuma Konağı                 if ($response -notmatch '^[Yy]') {                     Write-Host "Durdurulıyor. Algılama GPO'sunu önce dağıtın." -ForegroundColor Red                     çıkış 1                 }             }         } catch {             Write-Host " GPO denetlenemiyor: $($_. Exception.Message)" -ForegroundColor Yellow         }     } else {         Write-Host " GroupPolicy modülü kullanılamıyor - GPO denetimi atlanıyor" -ForegroundColor Gray     }     "" Write-Host }

# ============================================================================ # STATE FILE PATHS # ============================================================================

$stateDir = Join-Path $ReportBasePath "RolloutState" if (-not (Test-Path $stateDir)) {     New-Item -ItemType Directory -Path $stateDir -Force | Out-Null }

$rolloutStatePath = Join-Path $stateDir "RolloutState.json" $blockedBucketsPath = Join-Path $stateDir "BlockedBuckets.json" $adminApprovedPath = Join-Path $stateDir "AdminApprovedBuckets.json" $deviceHistoryPath = Join-Path $stateDir "DeviceHistory.json" $processingCheckpointPath = Join-Path $stateDir "ProcessingCheckpoint.json"

# ============================================================================ # PS 5.1 UYUMLULUĞU: ConvertTo-Hashtable # ============================================================================ # ConvertFrom-Json -AsHashtable yalnızca PS7+ şeklindedir. Bu, uyumluluk sağlar.

function ConvertTo-Hashtable {     param(         [Parameter(ValueFromPipeline = $true)]         $InputObject     )     process {         if ($null -eq $InputObject) { return @{} }         if ($InputObject -is [System.Collections.IDictionary]) { return $InputObject }         if ($InputObject -is [PSCustomObject]) {             # Tutarlı anahtar sıralama ve güvenli yinelenen işleme için [ordered] kullanın             $hash = [ordered]@{}             foreach ($InputObject.PSObject.Properties içinde $prop) {                 # Dizine alınan atama, üzerine yazarak yinelenenleri güvenli bir şekilde işler                 $hash[$prop. Ad] = ConvertTo-Hashtable $prop. Değer             }             return $hash         }         if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {             return @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ })         }         return $InputObject     } }

# ============================================================================ # YÖNETİCI KOMUTLARI: Demetleri Listeleme/Engellemesini Kaldırma # ============================================================================

if ($ListBlockedBuckets) {     "" Write-Host     Write-Host ("=" * 80) -ForegroundColor Yellow     Write-Host " ENGELLİ KOVALAR" -ForegroundColor Sarı     Write-Host ("=" * 80) -ForegroundColor Yellow     "" Write-Host     if (Test Yolu $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         if ($blocked. Count -eq 0) {             Write-Host "Engellenen demet yok." -ForegroundColor Green         } else {             Write-Host "Toplam engellendi: $($blocked. Count)" -ForegroundColor Red             "" Write-Host             foreach ($blocked'da $key. Anahtarlar) {                 $info = $blocked[$key]                 Write-Host "Bucket: $key" -ForegroundColor Red                 Write-Host " Engellendi: $($info. BlockedAt)" -ForegroundColor Gray                 Write-Host " Neden: $($info. Reason)" -ForegroundColor Gray                 Write-Host " Başarısız Cihaz: $($info. FailedDevice)" -ForegroundColor Gray                 Write-Host " Son Bildirilen: $($info. LastReported)" -ForegroundColor Gray                 Write-Host " Dalga: $($info. WaveNumber)" -ForegroundColor Gray                 Write-Host " Demetteki Cihazlar: $($info. DevicesInBucket)" -ForegroundColor Gray                 "" Write-Host             }             "Demet engellemesini kaldırmak için:" Write-Host             Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY'" -ForegroundColor Cyan             "" Write-Host             "Tümünün engelini kaldırmak için:" Write-Host             Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockAll" -ForegroundColor Cyan         }     } else {         Write-Host "Engellenen demet dosyası bulunamadı." -ForegroundColor Green     }     "" Write-Host     çıkış 0 }     

if ($UnblockBucket) {     "" Write-Host     if (Test Yolu $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         if ($blocked. Contains($UnblockBucket)) {             $blocked. Kaldır($UnblockBucket)             $blocked | ConvertTo-Json -Derinlik 10 | Out-File $blockedBucketsPath -Kodlama UTF8 -Force             # Yeniden engellemeyi önlemek için yönetici onaylı listeye ekle             $adminApproved = @{}             if (Test Yolu $adminApprovedPath) {                 $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable             }             $adminApproved[$UnblockBucket] = @{                 ApprovedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                 ApprovedBy = $env:USERNAME             }             $adminApproved | ConvertTo-Json -Derinlik 10 | Out-File $adminApprovedPath -Kodlama UTF8 -Force             Write-Host "Engelsiz demet: $UnblockBucket" -ForegroundColor Green             Write-Host "Yönetici onaylı listeye eklendi (otomatik olarak yeniden engellenmez)" -ForegroundColor Cyan         } else {             Write-Host "Demet bulunamadı: $UnblockBucket" -ForegroundColor Yellow             Write-Host "Kullanılabilir demetler:" -ForegroundColor Gray             $blocked. Tuşlar | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }         }     } else {         Write-Host "Engellenen demet dosyası bulunamadı." -ForegroundColor Yellow     }     "" Write-Host     çıkış 0 }                          

if ($UnblockAll) {     "" Write-Host     if (Test Yolu $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         $count = $blocked. Sayısı         @{} | ConvertTo-Json | Out-File $blockedBucketsPath -Kodlama UTF8 -Force         Write-Host "Tüm $count demetlerinin engeli kaldırıldı." -ForegroundColor Green     } else {         Write-Host "Engellenen demet dosyası bulunamadı." -ForegroundColor Yellow     }     "" Write-Host     çıkış 0 }

# ============================================================================ # YARDıMCı İŞLEVLERI # ============================================================================

function Get-RolloutState {     if (Test Yolu $rolloutStatePath) {         try {             $loaded = Get-Content $rolloutStatePath -Raw | ConvertFrom-Json | ConvertTo-Hashtable             # Gerekli özelliklerin mevcut olduğunu doğrulama             if ($null -eq $loaded. CurrentWave) {                 throw "Geçersiz durum dosyası - eksik CurrentWave"             }             # WaveHistory'nin her zaman bir dizi olduğundan emin olun (PS5.1 JSON seri durumdan çıkarmayı düzeltir)             if ($null -eq $loaded. WaveHistory) {                 $loaded. WaveHistory = @()             } elseif ($loaded. WaveHistory -isnot [array]) {                 $loaded. WaveHistory = @($loaded. WaveHistory)             }             return $loaded         } catch {             Write-Log "Bozuk RolloutState.json algılandı: $($_. Exception.Message)" "WARN"             Write-Log "Bozuk dosyayı yedekleme ve yenisini başlatma" "UYARI"             $backupPath = "$rolloutStatePath.corrupted.$(Get-Date -Format 'yyyyMdd-HHmmss')"             Move-Item $rolloutStatePath $backupPath -Force -ErrorAction SilentlyContinue         }     }     return @{         CurrentWave = 0         StartedAt = $null         LastAggregation = $null         TotalDevicesTargeted = 0         TotalDevicesUpdated = 0         Durum = "NotStarted"         WaveHistory = @()     } }

function Save-RolloutState {     param($State)     $State | ConvertTo-Json -Derinlik 10 | Out-File $rolloutStatePath -Kodlama UTF8 -Force }

function Get-WeekdayProjection {     <#     . ÖZET         Hafta sonları için öngörülen tamamlanma tarihini hesaplama (Sat/Sun'da ilerleme yok)     #>     param(         [int]$RemainingDevices,         [double]$DevicesPerDay,         [datetime]$StartDate = (Get-Date)     )     if ($DevicesPerDay -le 0 -veya $RemainingDevices -le 0) {         return @{             ProjectedDate = $null             WorkingDaysNeeded = 0             CalendarDaysNeeded = 0         }     }     # Gereken çalışma günlerini hesapla (hafta sonları hariç)     $workingDaysNeeded = [math]::Ceiling($RemainingDevices / $DevicesPerDay)     # Çalışma günlerini takvim günlerine dönüştürme (hafta sonları ekleme)     $currentDate = $StartDate.Date     $daysAdded = 0     $workingDaysAdded = 0     while ($workingDaysAdded -lt $workingDaysNeeded) {         $currentDate = $currentDate.AddDays(1)         $daysAdded++         # Yalnızca haftanın günlerini say         if ($currentDate.DayOfWeek -ne [DayOfWeek]::Saturday -ve             $currentDate.DayOfWeek -ne [DayOfWeek]::Sunday) {             $workingDaysAdded++         }     }     return @{         ProjectedDate = $currentDate.ToString("yyyy-MM-dd")         WorkingDaysNeeded = $workingDaysNeeded         CalendarDaysNeeded = $daysAdded     } }                                  

function Save-RolloutSummary {     <#     . ÖZET         Pano görüntüsü için yansıtma bilgileriyle dağıtım özetini kaydetme     #>     param(         [hashtable]$State,         [int]$TotalDevices,         [int]$UpdatedDevices,         [int]$NotUpdatedDevices,         [double]$DevicesPerDay     )     $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     # Hafta sonu fark eden yansıtmayı hesaplama     $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay     $summary = @{         GeneratedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")         RolloutStartDate = $State.StartedAt         LastAggregation = $State.LastAggregation         CurrentWave = $State.CurrentWave         Durum = $State.Status         # Cihaz sayısı         TotalDevices = $TotalDevices         UpdatedDevices = $UpdatedDevices         NotUpdatedDevices = $NotUpdatedDevices         PercentUpdated = if ($TotalDevices -gt 0) { [math]::Round(($UpdatedDevices / $TotalDevices) * 100, 1) } else { 0 }         # Hız ölçümleri         DevicesPerDay = [math]::Round($DevicesPerDay, 1)         TotalDevicesTargeted = $State.TotalDevicesTargeted         TotalWaves = $State.CurrentWave         # Hafta sonu uyumlu projeksiyon         ProjectedCompletionDate = $projection. ProjectedDate         WorkingDaysRemaining = $projection. WorkingDaysNeeded         CalendarDaysRemaining = $projection. CalendarDaysNeeded         # Hafta sonu dışlama hakkında not         ProjectionNote = "Öngörülen tamamlama hafta sonlarını (Sat/Sun)"     }     $summary | ConvertTo-Json -Derinlik 5 | Out-File $summaryPath -Kodlama UTF8 -Force     Write-Log "Dağıtım özeti kaydedildi: $summaryPath" "BİlGİ"     dönüş $summary }                                                             

function Get-BlockedBuckets {     if (Test Yolu $blockedBucketsPath) {         return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     @{} döndür }

function Save-BlockedBuckets {     param($Blocked)     $Blocked | ConvertTo-Json -Derinlik 10 | Out-File $blockedBucketsPath -Kodlama UTF8 -Force }

function Get-AdminApproved {     if (Test Yolu $adminApprovedPath) {         return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     @{} döndür }

function Get-DeviceHistory {     if (Test Yolu $deviceHistoryPath) {         return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     @{} döndür }

function Save-DeviceHistory {     param($History)     $History | ConvertTo-Json -Derinlik 10 | Out-File $deviceHistoryPath -Kodlama UTF8 -Force }

function Save-ProcessingCheckpoint {     param(         [string]$Stage,         [int]$Processed,         [int]$Total,         [hashtable]$Metrics = @{}     )

    $checkpoint = @{         Aşama = $Stage         UpdatedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         İşlendi = $Processed         Toplam = $Total         Yüzde = if ($Total -gt 0) { [math]::Round(($Processed / $Total) * 100, 2) } else { 0 }         Ölçümler = $Metrics     }

    $checkpoint | ConvertTo-Json -Depth 6 | Out-File $processingCheckpointPath -Encoding UTF8 -Force }

function Get-NotUpdatedIndexes {     param([dizi]$Devices)

    $hostSet = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)     $bucketCounts = @{}

    foreach ($device in $Devices) {         $hostname = if ($device. Ana bilgisayar adı) { $device. Hostname } elseif ($device. HostName) { $device. HostName } else { $null }         if ($hostname) {             [void]$hostSet.Add($hostname)         }

        $bucketKey = Get-BucketKey $device         if ($bucketKey) {             if ($bucketCounts.ContainsKey($bucketKey)) {                 $bucketCounts[$bucketKey]++             } else {                 $bucketCounts[$bucketKey] = 1             }         }     }

    return @{         HostSet = $hostSet         BucketCounts = $bucketCounts     } }

function Write-Log {     param([dize]$Message, [dize]$Level = "BİlGİ")     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     $color = switch ($Level) {         "Ok" { "Green" }         "WARN" { "Yellow" }         "ERROR" { "Red" }         "ENGELLİ" { "DarkRed" }         "WAVE" { "Cyan" }         default { "White" }     }     Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color     # Ayrıca dosyaya da oturum açın     $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMDd').log"     "[$timestamp] [$Level] $Message" | Out-File $logFile -Append -Encoding UTF8 }               

function Get-BucketKey {     param($Device)     # Varsa JSON cihazından BucketId kullanın (algılama betiğinden SHA256 karması)     if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { return "$($Device.BucketId)" }     # Fallback: üreticiden yapı|model|bios     $mfr = if ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } else { $Device.Manufacturer }     $model = if ($Device.WMI_Model) { $Device.WMI_Model } else { $Device.Model }     $bios = if ($Device.BIOSDescription) { $Device.BIOSDescription } else { $Device.BIOS }     "$mfr|$model|$bios" döndür }

# ============================================================================ # VIP/DıŞLAMA LISTESI YÜKLENIYOR # ============================================================================

function Get-ExcludedHostnames {     param(         [string]$ExclusionFilePath,         [dize]$ADGroupName     )     $excluded = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)     # Dosyadan yükle (.txt veya .csv destekler)     if ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) {         $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower()         if ($extension -eq ".csv") {             # CSV biçimi: 'Hostname' veya 'ComputerName' sütunu bekler             $csvData = Import-Csv $ExclusionFilePath             $hostCol = if ($csvData[0]. PSObject.Properties.Name -contains 'Hostname') { 'Hostname' }                        elseif ($csvData[0]. PSObject.Properties.Name -contains 'ComputerName') { 'ComputerName' }                        elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                        else { $null }             if ($hostCol) {                 foreach ($csvData'da $row) {                     if (![ string]::IsNullOrWhiteSpace($row.$hostCol)) {                         [void]$excluded. Add($row.$hostCol.Trim())                     }                 }             }         } else {             # Düz metin: Satır başına bir konak adı             Get-Content $ExclusionFilePath | ForEach-Object {                 $line = $_. Trim()                 if ($line -and -not $line. StartsWith('#')) {                     [void]$excluded. Ekle($line)                 }             }         }         Write-Log "Yüklü $($excluded. Count) dışlama dosyasından konak adları: $ExclusionFilePath" "BİlGİ"     }     # AD güvenlik grubundan yükleme     if ($ADGroupName) {         try {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }             foreach ($groupMembers'da $member) {                 [void]$excluded. Ekle($member. Ad)             }             Write-Log "AD grubundan yüklenen $($groupMembers.Count) bilgisayarlar: $ADGroupName" "BİlGİ"         } catch {             Write-Log "'$ADGroupName' AD grubu yüklenemedi: $_" "WARN"         }     }     return @($excluded) }                                                                             

# ============================================================================ # ALLOW LIST LOADING (Hedefli Dağıtım) # ============================================================================

function Get-AllowedHostnames {     <#     . ÖZET         Hedeflenen dağıtım için AllowList dosyasından ve/veya AD grubundan konak adlarını yükler.AllowList belirtildiğinde, YALNIZCA bu cihazlar kullanıma sunulacaktır.#>     param(         [string]$AllowFilePath,         [string]$ADGroupName     )          $allowed = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)          # Dosyadan yükle (.txt veya .csv destekler)     if ($AllowFilePath -and (Test Yolu $AllowFilePath)) {         $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower()                  if ($extension -eq ".csv") {             # CSV biçimi: 'Hostname' veya 'ComputerName' sütunu bekler             $csvData = Import-Csv $AllowFilePath             if ($csvData.Count -gt 0) {                 $hostCol = if ($csvData[0]. PSObject.Properties.Name -contains 'Hostname') { 'Hostname' }                            elseif ($csvData[0]. PSObject.Properties.Name -contains 'ComputerName') { 'ComputerName' }                            elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                            else { $null }                                  if ($hostCol) {                     foreach ($csvData $row) {                         if (![ string]::IsNullOrWhiteSpace($row.$hostCol)) {                             [void]$allowed. Add($row.$hostCol.Trim())                         }                     }                 }             }         } else {             # Düz metin: Satır başına bir konak adı             Get-Content $AllowFilePath | ForEach-Object {                 $line = $_. Trim()                 if ($line -and -not $line. StartsWith('#')) {                     [void]$allowed. Ekle($line)                 }             }         }                  Write-Log "Yüklü $($allowed. İzin ver listesi dosyasından sayı) konak adları: $AllowFilePath" "BİlGİ"     }          # AD güvenlik grubundan yükleme     if ($ADGroupName) {         try {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }                          foreach ($groupMembers'da $member) {                 [void]$allowed. Ekle($member. Ad)             }                          Write-Log "AD izin grubundaki yüklü $($groupMembers.Count) bilgisayarlar: $ADGroupName" "BİlGİ"         } catch {             Write-Log "AD grubu '$ADGroupName' yüklenemedi: $_" "WARN"         }     }          @($allowed) döndür }

# ============================================================================ # VERI TAZELIĞI VE IZLEME # ============================================================================

function Get-DataFreshness {     <#     . ÖZET         JSON dosya zaman damgalarını inceleyerek algılama verilerinin ne kadar yeni olduğunu denetler.Uç noktaların en son ne zaman bildirildiğine ilişkin istatistikleri döndürür.#>     param([dize]$JsonPath)     $jsonFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue     if ($jsonFiles.Count -eq 0) {         return @{             TotalFiles = 0             FreshFiles = 0             StaleFiles = 0             NoDataFiles = 0             OldestFile = $null             NewestFile = $null             AvgAgeHours = 0             Uyarı = "JSON dosyası bulunamadı - algılama dağıtılamayabilir"         }     }     $now = Get-Date     son 24 saatte güncelleştirilen $freshThresholdHours = 24 # Files "yeni"     $staleThresholdHours = 72 # Files 72 saatten eski olan "eski"     $fresh = 0     $stale = 0     $ages = @()     foreach ($jsonFiles $file) {         $ageHours = ($now - $file. LastWriteTime). TotalHours         $ages += $ageHours         if ($ageHours -le $freshThresholdHours) {             $fresh++         } elseif ($ageHours -ge $staleThresholdHours) {             $stale++         }     }     $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object -İlk 1     $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descending | Select-Object -İlk 1     $warning = $null     if ($stale -gt ($jsonFiles.Count * 0.5)) {         $warning = "Cihazların %50'sinden fazlası eski verilere sahip (>72 saat) - algılama GPO'sunun denetimi"     } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) {         $warning = "Yakın zamanda bildirilen cihazların %30'undan azı - algılama çalışmıyor olabilir"     }     return @{         TotalFiles = $jsonFiles.Count         FreshFiles = $fresh         StaleFiles = $stale         MediumFiles = $jsonFiles.Count - $fresh - $stale         OldestFile = $oldestFile.LastWriteTime         NewestFile = $newestFile.LastWriteTime         AvgAgeHours = [math]::Round(($ages | Measure-Object -Average). Ortalama, 1)         Uyarı = $warning     } }                                                 

function Test-DetectionGPODeployed {     <#     . ÖZET         Algılama/izleme altyapısının yerinde olduğunu doğrular.#>     param([dize]$JsonPath)     # Denetim 1: JSON yolu var     if (-not (Test-Path $JsonPath)) {         return @{             IsDeployed = $false             message = "JSON giriş yolu yok: $JsonPath"         }     }     # Check 2: En azından bazı JSON dosyaları var     $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Sayısı     if ($jsonCount -eq 0) {         return @{             IsDeployed = $false             İleti = "$JsonPath'de JSON dosyası yok - Algılama GPO'sunun dağıtılmaması veya cihazların henüz bildirilmemesi"         }     }     # Check 3: Files makul düzeyde yenidir (en azından geçen hafta bazılarında)     $recentFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue |         Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) }     if ($recentFiles.Count -eq 0) {         return @{             IsDeployed = $false             İleti = "Son 7 gün içinde güncelleştirilen JSON dosyası yok - Algılama GPO'sunun bozuk veya cihazlar çevrimdışı olabilir"         }     }     return @{         IsDeployed = $true         İleti = "Algılama etkin görünüyor: $jsonCount dosyaları, $($recentFiles.Count) yakın zamanda güncelleştirildi"         FileCount = $jsonCount         RecentCount = $recentFiles.Count     } }                         

# ============================================================================ # CIHAZ IZLEME (HOSTNAME'E GÖRE) # ============================================================================

function Update-DeviceHistory {     <#     . ÖZET         Benzersiz bir makine tanımlayıcımız olmadığından cihazları konak adına göre izler.Not: BucketId bire çok 'dır (aynı donanım yapılandırması = aynı demet).JSON koleksiyonuna benzersiz bir tanımlayıcı eklenirse bu işlevi güncelleştirin.#>     param(         [dizi]$CurrentDevices,         [hashtable]$DeviceHistory     )          foreach ($CurrentDevices $device) {         $hostname = $device. Hostname         if (-not $hostname) { continue }                  # Cihazı ana bilgisayar adına göre izleme         $DeviceHistory[$hostname] = @{             Hostname = $hostname             BucketId = $device. BucketId             Üretici = $device. WMI_Manufacturer             Model = $device. WMI_Model             LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             Durum = $device. UpdateStatus         }     } }

# ============================================================================ # ENGELLİ KOVA ALGıLAMA (Cihaz Erişilebilirliğine Göre) # ============================================================================

<# . AÇIKLAMA     Engelleme Mantığı:     - Demet YALNIZCA şu durumda engellenir:       1. Cihaz bir dalgada hedeflendi       2. Dalga başladığından beri MaxWaitHours geçti       3. Cihaza ULAŞILAMIYOR (ping başarısız oluyor)          - Cihaz ulaşılabilir ancak henüz güncelleştirilmediyse, beklemeye devam ederiz       (güncelleştirme yeniden başlatma bekliyor olabilir - Olay 1808 yalnızca yeniden başlatma sonrasında tetikleniyor)          - Ulaşılamayan cihaz bir sorun oluştuğuna işaret ediyor ve araştırılması      gerekiyor     Engeli kaldırma:     - Engellenen demetleri görmek için -ListBlockedBuckets kullanma     - Belirli demeti kaldırmak için -UnblockBucket "BucketKey" kullanın     - Tüm demetlerin engellemesini kaldırmak için -UnblockAll komutunu kullanın #>

function Test-DeviceReachable {     param(         [string]$Hostname,         [string]$DataPath # Cihaz JSON dosyalarının yolu     )     # Yöntem 1: JSON dosya zaman damgasını denetleyin (en hızlı — dosya ayrıştırma gerekmez)     # Algılama betiği yakın zamanda çalıştırıldıysa, dosya yazıldı/güncelleştirildi ve cihazın etkin olduğunu kanıtlıyor     if ($DataPath) {         $deviceFile = Get-ChildItem -Path $DataPath -Filter "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object -İlk 1         if ($deviceFile) {             $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). TotalHours             if ($hoursSinceWrite -lt 72) { return $true }         }     }     # Yöntem 2: Ping'e geri dönüş (yalnızca JSON eskiyse veya eksikse)     try {         $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue         return $ping     } catch {         iade $false     } }          

function Update-BlockedBuckets {     param(         $RolloutState,         $BlockedBuckets,         $AdminApproved,         [dizi]$NotUpdatedDevices,         [hashtable]$NotUpdatedIndexes,         [int]$MaxWaitHours,         [bool]$DryRun = $false     )     $now = Get-Date     $newlyBlocked = @()     $stillWaiting = @()     $devicesToCheck = @()     $hostSet = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     $bucketCounts = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). BucketCounts }     # Bekleme süresini geçmiş ve hala güncelleştirilmemiş cihazları toplama     foreach ($RolloutState.WaveHistory'de $wave) {         if (-not $wave. StartedAt) { continue }         $waveStart = [DateTime]::P arse($wave. StartedAt)         $hoursSinceWave = ($now - $waveStart). TotalHours         if ($hoursSinceWave -lt $MaxWaitHours) {             # Hala bekleme süresi içinde - henüz denetleme             Devam         }         # Bu dalgadan her cihazı kontrol et         foreach ($wave'da $deviceInfo. Cihazlar) {             $hostname = $deviceInfo.Hostname             $bucketKey = $deviceInfo.BucketKey             # Demet zaten engellenmişse atla             if ($BlockedBuckets.Contains($bucketKey)) { continue }             # Demet yönetici onaylıysa atla VE onaydan önce dalga başlat             # (yeniden engelleme için yalnızca yönetici onayının ardından hedeflenen cihazları denetleyin)             if ($AdminApproved -ve $AdminApproved.Contains($bucketKey)) {                 $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. ApprovedAt)                 if ($waveStart -lt $approvalTime) {                     # Bu cihaz yönetici onayı öncesinde hedeflendi - atla                     Devam                 }                 # Dalga onaydan sonra başladı - bu yeni hedefleme, kontrol edebilir             }             # Bu cihaz hala NotUpdated listesinde mi?             if ($hostSet.Contains($hostname)) {                 $devicesToCheck += @{                     Hostname = $hostname                     BucketKey = $bucketKey                     WaveNumber = $wave. WaveNumber                     HoursSinceWave = [math]::Round($hoursSinceWave, 1)                 }             }         }     }     if ($devicesToCheck.Count -eq 0) {         $newlyBlocked döndür     }     Write-Log "$($devicesToCheck.Count) cihazlarının bekleme süresi geçmiş erişilebilirliği denetleniyor..." "BİlGİ"     # Karar alma için demet başına hataları izleme     $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Alive=@() }     # Her cihazın erişilebilirliğini denetleyin     foreach ($devicesToCheck'da $device) {         $hostname = $device. Hostname         $bucketKey = $device. BucketKey         if ($DryRun) {             Write-Log "[DRYRUN] $hostname ulaşılabilirliği denetler" "BİlGİ"             Devam         }         if (-not $bucketFailures.ContainsKey($bucketKey)) {             $bucketFailures[$bucketKey] = @{ Ulaşılamıyor = @(); AliveButFailed = @(); WaveNumber = $device. WaveNumber; HoursSinceWave = $device. HoursSinceWave }         }         $isReachable = Test-DeviceReachable -Hostname $hostname -DataPath $AggregationInputPath         if (-not $isReachable) {             $bucketFailures[$bucketKey]. Ulaşılamıyor += $hostname         } else {             # Cihaz ulaşılabilir ancak henüz güncelleştirilmemiş - geçici bir hata olabilir veya yeniden başlatma bekleniyor olabilir             $bucketFailures[$bucketKey]. AliveButFailed += $hostname             $stillWaiting += $hostname         }     }     # Demet başına karar: Yalnızca cihazlar gerçekten ULAŞILAMIYORSA engelle     # Başarısız olan canlı cihazlar = geçici, dağıtıma devam et     foreach ($bucketFailures.Keys'de $bucketKey) {         $bf = $bucketFailures[$bucketKey]         $unreachableCount = $bf. Ulaşılamıyor.Sayı         $aliveFailedCount = $bf. AliveButFailed.Count         # Bu demette herhangi bir başarı olup olmadığını denetleyin (güncelleştirilmiş cihaz verilerinden)         $bucketHasSuccesses = $stSuccessBuckets -ve $stSuccessBuckets.Contains($bucketKey)         if ($unreachableCount -gt 0 -and $aliveFailedCount -eq 0) {             # Tüm başarısız cihazlara ulaşılamıyor - demeti engelleyin             if ($newlyBlocked -notcontains $bucketKey) {                 $BlockedBuckets[$bucketKey] = @{                     BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                     Reason = "$($bf) sonrasında tüm $unreachableCount cihazlara ulaşılamıyor. HoursSinceWave) hours"                     FailedDevices = ($bf. Ulaşılamıyor -join ", ")                     WaveNumber = $bf. WaveNumber                     DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } else { 0 }                 }                 $newlyBlocked += $bucketKey                 Write-Log "KOVA ENGELLİ: $bucketKey ($unreachableCount cihazlara ulaşılamıyor: $($bf. Ulaşılamıyor -join ', '))" "ENGELLİ"             }         } elseif ($aliveFailedCount -gt 0) {             # Cihazlar etkin ama güncelleştirilmedi - geçici hata, ENGELLEMEYİN             Write-Log "Bucket $($bucketKey.Substring(0, [Math]::Min(16, $bucketKey.Length)))...: $aliveFailedCount cihaz(lar) etkin ancak bekliyor, $unreachableCount ulaşılamıyor - ENGELLENMİYOR (geçici)" "BİlGİ"             if ($unreachableCount -gt 0) {                 Write-Log " Ulaşılamıyor: $($bf. Ulaşılamıyor -join ', ')" "WARN"             }             Write-Log " Etkin ama beklemede: $($bf. AliveButFailed -join ', ')" "INFO"             # İzleme için dağıtım durumunda hata sayısını izleme             if (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} }             $RolloutState.TemporaryFailures[$bucketKey] = @{                 AliveButFailed = $bf. AliveButFailed                 Ulaşılamıyor = $bf. Ulaşıla -maz                 LastChecked = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             }         }     }     if ($stillWaiting.Count -gt 0) {         Write-Log "Cihazlara ulaşılabilir ancak bekleyen güncelleştirme (yeniden başlatma gerekebilir): $($stillWaiting.Count)" "BİlGİ"     }     dönüş $newlyBlocked }                                                                                                                                                                                  

# ============================================================================ # AUTO-UNBLOCK: Cihazlar başarıyla güncelleştirildiğinde demetlerin engellemesini kaldırın # ============================================================================

function Update-AutoUnblockedBuckets {     <#     . AÇIKLAMA         Engellenen demetlerdeki cihazların güncelleştirilip güncelleştirilmediğini denetler (Olay 1808).         Demetteki TÜM hedeflenen cihazlar güncelleştirildiyse engellemeyi otomatik olarak kaldırır.Yalnızca BAZI cihazlar güncelleştirildiyse, yöneticiye engeli el ile kaldırabileceklerini bildirir.                  Yönetici kullanarak engeli el ile kaldırabilirsiniz:           .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "path" -UnblockBucket "BucketKey"     #>     param(         $BlockedBuckets,         $RolloutState,         [dizi]$NotUpdatedDevices,         [string]$ReportBasePath,         [hashtable]$NotUpdatedIndexes,         [int]$LogSampleSize = 25     )     $autoUnblocked = @()     $bucketsToCheck = @($BlockedBuckets.Keys)     $hostSet = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     foreach ($bucketsToCheck'da $bucketKey) {         $bucketInfo = $BlockedBuckets[$bucketKey]         # Bu demetten hedeflediğimiz tüm cihazları geçmişe alın         $targetedDevicesInBucket = @()         foreach ($RolloutState.WaveHistory'de $wave) {             $targetedDevicesInBucket += @($wave. Cihazlar | Where-Object { $_. BucketKey -eq $bucketKey })         }         if ($targetedDevicesInBucket.Count -eq 0) { continue }         # NotUpdated ile güncelleştirilmiş durumdaki hedeflenen cihazların sayısını denetleyin         $updatedDevices = @()         $stillPendingDevices = @()         foreach ($targetedDevicesInBucket $targetedDevice) {             if ($hostSet.Contains($targetedDevice.Hostname)) {                 $stillPendingDevices += $targetedDevice.Hostname             } else {                 $updatedDevices += $targetedDevice.Hostname             }         }         if ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -eq 0) {             # TÜM hedeflenen cihazlar güncelleştirildi - engellemeyi otomatik olarak kaldır!             $BlockedBuckets.Remove($bucketKey)             $autoUnblocked += @{                 BucketKey = $bucketKey                 UpdatedDevices = $updatedDevices                 PreviouslyBlockedAt = $bucketInfo.BlockedAt                 Neden = "Tüm $($updatedDevices.Count) hedeflenen cihazlar başarıyla güncelleştirildi"             }             Write-Log "AUTO-UNBLOCKED: $bucketKey (Tüm $($updatedDevices.Count) hedeflenen cihazlar başarıyla güncelleştirildi)" "Tamam"             # Bu kovanın OEM'i için OEM dalga sayısını artırma (OEM başına izleme)             $bucketOEM = if ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } else { 'Unknown' } # OEM'i kanalla ayrılmış anahtardan veya varsayılan anahtardan ayıkla             if (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             $currentWave = if ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } else { 0 }             $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1             Write-Log " OEM '$bucketOEM' dalga sayısı $($currentWave + 1) (sonraki ayırma: $([int][Math]::P ow(2, $currentWave + 1)) cihazlar)" "BİlGİ"         }         elseif ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -gt 0) {             # BAZı cihazlar güncelleştirildi ancak diğerleri hala beklemede - yöneticiye bildir (yalnızca bir kez)             if (-not $bucketInfo.UnblockCandidate) {                 $bucketInfo.UnblockCandidate = $true                 $bucketInfo.UpdatedDevices = $updatedDevices                 $bucketInfo.PendingDevices = $stillPendingDevices                 $bucketInfo.NotifiedAt = (Get-Date). ToString("yyyy-MM-dd HH:mm:ss")                 "" "BİlGİ" Write-Log                 Write-Log "ENGELLİ DEMETTE KISMİ GÜNCELLEŞTIRME ========== ==========" "BİlGİ"                 Write-Log "Demet: $bucketKey" "BİlGİ"                 $updatedSample = @($updatedDevices | Select-Object -first $LogSampleSize)                 $pendingSample = @($stillPendingDevices | Select-Object -İlk $LogSampleSize)                 $updatedSuffix = if ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count - $LogSampleSize) daha fazlası)" } else { "" }                 $pendingSuffix = if ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count - $LogSampleSize) daha fazlası)" } else { "" }                 Write-Log "Güncelleştirilmiş cihazlar ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "Tamam"                 Write-Log "Hala bekleniyor ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN"                 Write-Log "" "BİlGİ"                 Write-Log "Doğrulamadan sonra bu demetin engellemesini el ile kaldırmak için şunu çalıştırın:" "BİlGİ"                 Write-Log " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'"" "INFO"                 "=======================================================" "BİlGİ" Write-Log                 "" "BİlGİ" Write-Log             }         }     }     dönüş $autoUnblocked }                                                                                          

# ============================================================================ # WAVE GENERATION (INLINED - engellenen demetleri dışlar) # ============================================================================

function New-RolloutWave {     param(         [string]$AggregationPath,         $BlockedBuckets,         $RolloutState,         [int]$MaxDevicesPerWave = 50,         [string[]]$AllowedHostnames = @(),         [string[]]$ExcludedHostnames = @()     )     # Toplama verilerini yükleme     $notUptodateCsv = Get-ChildItem -Path $AggregationPath -Filter "*NotUptodate*.csv" |          Where-Object { $_. Name -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object -İlk 1     if (-not $notUptodateCsv) {         Write-Log "NotUptodate CSV bulunamadı" "HATA"         return $null     }     $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName)     # Tutarlılık için Ana BilgisayarAdı -> Ana Bilgisayar Adını normalleştirin (CSV HostName kullanır, kod Hostname kullanır)     foreach ($allNotUpdated'da $device) {         if ($device. PSObject.Properties['HostName'] -and -not $device. PSObject.Properties['Hostname']) {             $device | Add-Member -NotePropertyName 'Hostname' -NotePropertyValue $device. HostName -Force         }     }     # Engellenen demetleri filtrele     $eligibleDevices = @($allNotUpdated | Where-Object {         $bucketKey = Get-BucketKey $_         -not $BlockedBuckets.Contains($bucketKey)     })     # YALNIZCA izin verilen cihazlara filtre uygula (AllowList belirtilirse)     # AllowList = hedeflenen dağıtım - yalnızca bu cihazlar dikkate alınır     if ($AllowedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Ana bilgisayar adı -in $AllowedHostnames         })         $allowedCount = $eligibleDevices.Count         Write-Log "AllowList uygulandı: $beforeCount cihazların $allowedCount izin verilenler listesinde" "BİlGİ"     }     # VIP/hariç tutulan cihazları filtreleme (BlockList)     # BlockList allowlist sonra uygulanır     if ($ExcludedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Ana bilgisayar adı -$ExcludedHostnames         })         $excludedCount = $beforeCount - $eligibleDevices.Count         if ($excludedCount -gt 0) {             Write-Log "VIP/korumalı cihazları piyasaya çıkarmanın dışında $excludedCount" "BİlGİ"         }     }     if ($eligibleDevices.Count -eq 0) {         Write-Log "Uygun cihaz kalmadı (tümü güncelleştirildi veya engellendi)" "Tamam"         dönüş $null     }     # Cihazları kullanıma alma (önceki dalgalardan)     $devicesAlreadyInRollout = @()     if ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) {         $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object {             $_. Cihazlar | ForEach-Object { $_. Ana bilgisayar adı }         } | Where-Object { $_ })     }     Write-Log "Cihazlar zaten piyasaya çıktı: $($devicesAlreadyInRollout.Count)" "BİlGİ"     # Güven düzeyine göre ayırın     $highConfidenceDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -eq "Yüksek Güvenilirlik" -ve         $_. Ana bilgisayar adı -$devicesAlreadyInRollout     })     # Eylem Gerekli şunları içerir:     # - Açık "Eylem Gerekli"     # - Boş/null ConfidenceLevel     # - Bilinmeyen/tanınmayan ConfidenceLevel değeri (Eylem Gerekli olarak kabul edilir)     $knownSafeCategories = @(         "Yüksek Güvenilirlik",         "Geçici Olarak Duraklatıldı",         "Gözlem Altında",         "Gözlem Altında - Daha Fazla Veri Gerekiyor",         "Desteklenmiyor",         "Desteklenmiyor - Bilinen Sınırlama"     )     $actionRequiredDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -notin $knownSafeCategories -ve         $_. Ana bilgisayar adı -$devicesAlreadyInRollout     })     Write-Log "Yüksek Güvenilirlik (dağıtımda değil): $($highConfidenceDevices.Count)" "BİlGİ"     Write-Log "Eylem Gerekli (dağıtımda değil): $($actionRequiredDevices.Count)" "BİlGİ"     # Dalga cihazları oluşturma     $waveDevices = @()     # YÜKSEK GÜVENİlİRLİk: ALL'ı dahil edin (dağıtım için güvenli)     if ($highConfidenceDevices.Count -gt 0) {         Write-Log "Tüm $($highConfidenceDevices.Count) Yüksek Güvenilirlikli cihazları ekleme" "WAVE"         $waveDevices += $highConfidenceDevices     } # EYLEM GEREKLİ: Aşamalı dağıtım (sıfır başarı demetleri için OEM'ye yayılmış demet tabanlı)     # Strateji:     # - 0 başarıya sahip demetler: OEM'lere yayılır (OEM başına 1 -oem başına 1 -oem başına > 2 -OEM başına > 4)     # - ≥1 başarısı olan demetler: OEM kısıtlaması olmadan iki kat serbestçe     if ($actionRequiredDevices.Count -gt 0) {         # Güncelleştirilmiş cihazlar CSV'sinden (başarıyla güncelleştirilen cihazlar) demet başarı sayılarını yükleme         $updatedCsv = Get-ChildItem -Path $AggregationPath -Filter "*updated_devices*.csv" |             Sort-Object LastWriteTime -Descending | Select-Object -İlk 1         $bucketStats = @{}         if ($updatedCsv) {             $updatedDevices = Import-Csv $updatedCsv.FullName             # BucketId başına başarıları say             $updatedDevices | ForEach-Object {                 $key = Get-BucketKey $_                 if ($key) {                     if (-not $bucketStats.ContainsKey($key)) {                         $bucketStats[$key] = @{ Successes = 0; Beklemede = 0; Toplam = 0 }                     }                     $bucketStats[$key]. Successes++                     $bucketStats[$key]. Total++                 }             }             Write-Log "$($updatedDevices.Count) yüklü cihazlar $($bucketStats.Count) demetleri arasında güncelleştirildi" "BİlGİ"         } else {             # Geri dönüş: CSV ActionRequired_Buckets deneyin             $bucketsCsv = Get-ChildItem -Path $AggregationPath -Filter "*ActionRequired_Buckets*.csv" |                 Sort-Object LastWriteTime -Descending | Select-Object -İlk 1             if ($bucketsCsv) {                 Import-Csv $bucketsCsv.FullName | ForEach-Object {                     $key = if ($_. BucketId) { $_. BucketId } else { "$($_. Üretici)|$($_. Model)|$($_. BIOS)" }                     $bucketStats[$key] = @{                         Successes = [int]$_. Başarı                         Beklemede = [int]$_. Bekleyen                         Toplam = [int]$_. TotalDevices                     }                 }             }         }         # NotUpdated cihazlarını demetlere göre gruplandır (Üretici|Model|BIOS)         $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ }         # Ayrı demetler: sıfır başarı ve başarı-başarı         $zeroSuccessBuckets = @()         $hasSuccessBuckets = @()         foreach ($buckets $bucket) {             $bucketKey = $bucket. Adı             $bucketDevices = @($bucket. Grup)             $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Ana bilgisayar adı })             # Bu demetteki başarıları say             $stats = $bucketStats[$bucketKey]             $successes = if ($stats) { $stats. Successes } else { 0 }             # Dalga geçmişinden bu demete dağıtılan cihazları bulun             $deployedToBucket = @()             foreach ($RolloutState.WaveHistory'de $wave) {                 foreach ($wave'da $device. Cihazlar) {                     if ($device. BucketKey -eq $bucketKey ve $device. Ana bilgisayar adı) {                         $deployedToBucket += $device. Hostname                     }                 }             }             $deployedToBucket = @($deployedToBucket | Sort-Object -Unique)             # Dağıtılan TÜM cihazların başarılı olduğunu bildirip bildirmediğini denetleyin             $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames })             $confirmedSuccess = $deployedToBucket.Count - $stillPending.Count             # Bekliyorsa, tüm onaylanana kadar bu demeti atlayın             if ($stillPending.Count -gt 0) {                 $parts = $bucketKey -split '\|'                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " Demet: $displayName - Deployed=$($deployedToBucket.Count), Confirmed=$confirmedSuccess, Pending=$($stillPending.Count) (bekleniyor)" "BİlGİ"                 Devam             }             # Kalan uygun = henüz dağıtılmayan cihazlar             $devicesNotYetTargeted = @($bucketDevices | Where-Object {                 $_. Ana bilgisayar adı -$deployedToBucket             })             if ($devicesNotYetTargeted.Count -eq 0) { continue }             # Başarı sayımına göre kategorilere ayırma             $bucketInfo = @{                 BucketKey = $bucketKey                 Cihazlar = $devicesNotYetTargeted                 ConfirmedSuccess = $confirmedSuccess                 Başarılar = $successes                 OEM = if ($bucket. Grup[0]. WMI_Manufacturer) { $bucket. Grup[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } else { 'Unknown' }             }             if ($successes -eq 0) {                 $zeroSuccessBuckets += $bucketInfo             } else {                 $hasSuccessBuckets += $bucketInfo             }         }         # === SÜREÇ HAS-SUCCESS DEMETLERI (≥1 başarı) ===         # Başarı sayısının iki katı — 14 başarılıysa sonraki adımda 28'i dağıtın         foreach ($hasSuccessBuckets $bucketInfo) {             $nextBatchSize = $bucketInfo.Successes * 2             $nextBatchSize = [Matematik]::Min($nextBatchSize, $MaxDevicesPerWave)             $nextBatchSize = [Matematik]::Min($nextBatchSize, $bucketInfo.Devices.Count)             if ($nextBatchSize -gt 0) {                 $selectedDevices = @($bucketInfo.Devices | Select-Object -First $nextBatchSize)                 $waveDevices += $selectedDevices                 $parts = if ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length)))) }                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x onaylandı)" "BİlGİ"             }         }         # === SIFIR BAŞARI DEMETSİ İŞLEMİ (OEM başına izleme ile OEM'lere yayılmış) ===         # Hedef: Riski farklı OEM'lere yayma, OEM başına ilerleme durumunu bağımsız olarak izleme         # Her OEM kendi başarı geçmişine göre ilerler:         # - Başarılı OEM: Sonraki dalgada daha fazla cihaz alır (2^waveCount)         # - OEM başarıları olmadan: Başarı onaylanana kadar geçerli düzeyde kalır         if ($zeroSuccessBuckets.Count -gt 0) {             # Yoksa OEM başına dalga sayılarını başlat             if (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             # Sıfır başarı demetlerini OEM'e göre gruplandır             $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OEM }             $totalZeroSuccessAdded = 0             $oemsDeployedTo = @()             foreach ($oemBuckets'da $oemGroup) {                 $oemName = $oemGroup.Name                 # Bu OEM'in dalga sayısını alın (0'da başlar)                 $oemWaveCount = if ($RolloutState.OEMWaveCounts[$oemName]) {                     $RolloutState.OEMWaveCounts[$oemName]                 } else { 0 }                 # BU OEM için cihazları hesapla: 2^waveCount (1, 2, 4, 8...)                 $devicesForThisOEM = [int][Math]::P ow(2, $oemWaveCount)                 $devicesForThisOEM = [Matematik]::Max(1, $devicesForThisOEM)                 $oemDevicesAdded = 0                 # Bu OEM'in altındaki her demetten seçim                 foreach ($oemGroup.Group'ta $bucketInfo) {                     $remaining = $devicesForThisOEM - $oemDevicesAdded                     if ($remaining -le 0) { break }                     $toTake = [Matematik]::Min($remaining, $bucketInfo.Devices.Count)                     if ($toTake -gt 0) {                         $selectedDevices = @($bucketInfo.Devices | Select-Object -First $toTake)                         $waveDevices += $selectedDevices                         $oemDevicesAdded += $toTake                         $totalZeroSuccessAdded += $toTake                         $parts = if ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length)))) }                         $displayName = "$($parts[0]) - $($parts[1])"                         Write-Log " [ZERO-SUCCESS] $displayName - Deploying=$toTake (OEM wave $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN"                     }                 }                 if ($oemDevicesAdded -gt 0) {                     Write-Log " OEM: $oemName - Dalga $oemWaveCount, $oemDevicesAdded cihazlar eklendi" "BİlGİ"                     $oemsDeployedTo += $oemName                 }             }             # Dağıtılan OEM'leri izleme (sonraki başarı denetiminde artırma için)             if ($oemsDeployedTo.Count -gt 0) {                 $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo                 Write-Log "Sıfır başarılı dağıtım: $($oemsDeployedTo.Count) OEM'ler arasında cihazları $totalZeroSuccessAdded" "BİlGİ"             }         }     }     if (@($waveDevices). Count -eq 0) {         return $null     }     dönüş $waveDevices }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

# ============================================================================ # GPO DEPLOYMENT (INLINED - GPO, güvenlik grubu, bağlantılar oluşturur) # ============================================================================

function Deploy-GPOForWave {     param(         [string]$GPOName,         [string]$TargetOU,         [string]$SecurityGroupName,         [dizi]$WaveHostnames,         [bool]$DryRun = $false     )     # ADMX İlkesi: SecureBoot.admx - SecureBoot_AvailableUpdatesPolicy     # Kayıt Defteri Yolu: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot     # Değer Adı: AvailableUpdatesPolicy     # Etkin Değer: 22852 (0x5944) - Tüm Güvenli Önyükleme anahtarlarını güncelleştir + bootmgr     # Devre Dışı Değer: 0     #     # Güvenilir HKLM\SYSTEM yolu dağıtımı için grup ilkesi Tercihlerini (GPP) kullanma     # GPP, altında ayarlar oluşturur: Windows Ayarları > Kayıt Defteri > Bilgisayar Yapılandırması > Tercihleri     $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot"     $RegistryValueName = "AvailableUpdatesPolicy"     $RegistryValue = 22852 # 0x5944 - ADMX enabledValue ile eşleşir     Write-Log "GPO dağıtma: $GPOName" "WAVE"     Write-Log "Kayıt Defteri: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X'))" "BİlGİ"     if ($DryRun) {         Write-Log "[DRYRUN] GPO oluşturur: $GPOName" "BİlGİ"         Write-Log "[DRYRUN] Güvenlik grubu oluşturur: $SecurityGroupName" "BİlGİ"         Write-Log "[DRYRUN] $(@($WaveHostnames) ekler. Sayı) gruplandıracak bilgisayarlar" "BİlGİ"         Write-Log "[DRYRUN] GPO'ya bağlanır: $TargetOU" "BİlGİ"         dönüş $true     }     try {         # Gerekli modülleri içeri aktarma         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } catch {         Write-Log "Gerekli modüller (GroupPolicy, ActiveDirectory) içeri aktarılamadı: $($_. Exception.Message)" "ERROR"         iade $false     }     # 1. Adım: GPO oluşturma veya alma     $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($existingGPO) {         Write-Log "GPO zaten var: $GPOName" "BİlGİ"         $gpo = $existingGPO     } else {         try {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Oluşturulan GPO: $GPOName" "Tamam"         } catch {             Write-Log "GPO oluşturulamadı: $($_. Exception.Message)" "ERROR"             return $false         }     }     # 2. Adım: grup ilkesi Tercihleri (GPP) kullanarak kayıt defteri değerini ayarlama     # GPP, HKLM\SYSTEM yolları için Set-GPRegistryValue'dan daha güvenilirdir     try {         # Önce bu değer için var olan tercihleri kaldırmayı deneyin (yinelemeleri önlemek için)         Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue         # "Değiştir" eylemiyle GPP kayıt defteri tercihi oluşturma         # Replace = Yoksa oluştur, Varsa güncelleştir (en güvenilir)         # Update = Yalnızca varsa güncelleştir (değer yoksa başarısız olur)         Set-GPPrefRegistryValue -Name $GPOName '             -Bağlam Bilgisayarı '             -Eylem Değiştir '             -Key $RegistryKey '             -ValueName $RegistryValueName '             -Type DWord '             -Değer $RegistryValue         Write-Log "Yapılandırılmış GPP kayıt defteri tercihi: $RegistryValueName = 0x5944 (Action=Replace)" "Tamam"     } catch {         Write-Log "GPP başarısız oldu, Set-GPRegistryValue: $($_. Exception.Message)" "WARN"         # Set-GPRegistryValue geri dönüş (ADMX dağıtılırsa çalışır)         try {             Set-GPRegistryValue -Name $GPOName '                 -Key $RegistryKey '                 -ValueName $RegistryValueName '                 -Type DWord '                 -Değer $RegistryValue             Write-Log "Set-GPRegistryValue aracılığıyla yapılandırılmış kayıt defteri: $RegistryValueName = 0x5944" "Tamam"         } catch {             Write-Log "Kayıt defteri değeri ayarlanamadı: $($_. Exception.Message)" "ERROR"             return $false         }     }     # 3. Adım: Güvenlik grubu oluşturma veya alma     $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $existingGroup) {         try {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Güvenliği '                 -GroupScope DomainLocal '                 -Description "Güvenli Önyükleme dağıtımı için hedeflenen bilgisayarlar - $GPOName" '                 -PassThru             Write-Log "Güvenlik grubu oluşturuldu: $SecurityGroupName" "Tamam"         } catch {             Write-Log "Güvenlik grubu oluşturulamadı: $($_. Exception.Message)" "ERROR"             iade $false         }     } else {         Write-Log "Güvenlik grubu var: $SecurityGroupName" "BİlGİ"         $group = $existingGroup     }     # 4. Adım: Güvenlik grubuna bilgisayar ekleme     $added = 0     $failed = 0     foreach ($WaveHostnames'da $hostname) {         try {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } catch {             $failed++         }     }     Write-Log "Güvenlik grubuna $added bilgisayarlar eklendi ($FAILED AD'de bulunamadı)" "Tamam"     # 5. Adım: GPO'da güvenlik filtrelemesini yapılandırma     try {         # Varsayılan "Kimliği Doğrulanmış Kullanıcılar" Uygulama iznini kaldır (Okuma'yı koru)         Set-GPPermission -Name $GPOName -TargetName "Kimliği Doğrulanmış Kullanıcılar" -TargetType Grubu -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         # Güvenlik grubumuz için Uygulama izni ekle         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Için yapılandırılmış güvenlik filtrelemesi: $SecurityGroupName" "Tamam"     } catch {         Write-Log "Güvenlik filtrelemesi yapılandırılamadı: $($_. Exception.Message)" "WARN"         Write-Log "GPO bağlı OU'daki tüm bilgisayarlara uygulanabilir - el ile doğrula" "UYAR"     }     # 6. Adım: GPO'yı OU'ya bağlama (İlkenin uygulanması için KRITIK)     if ($TargetOU) {         try {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Bağlı GPO: $TargetOU" "Tamam"                 Write-Log "GPO hedef bilgisayarlarda sonraki gpupdate'te geçerli olacak" "BİlGİ"             } else {                 Write-Log "Hedef OU'ya zaten bağlı GPO" "BİlGİ"             }         } catch {             Write-Log "CRITICAL: GPO OU'ya bağlanamadı: $($_. Exception.Message)" "ERROR"             Write-Log "GPO oluşturuldu ama BAĞLANTILI DEĞIL - herhangi bir bilgisayara uygulanmaz!" "HATA"             Write-Log "El ile düzeltme gerekiyor: New-GPLink -Name '$GPOName' -Target '$TargetOU' -LinkEnabled Yes" "ERROR"             return $false         }     } else {         Write-Log "UYARI: TargetOU belirtilmedi - GPO oluşturuldu ama BAĞLANTILI DEĞIL!" "HATA"         Write-Log "GPO'nin etkili olması için el ile bağlama gerekiyor" "HATA"         Write-Log "Çalıştır: New-GPLink -Name '$GPOName' -Target '<Your-Domain-DN>' -LinkEnabled Yes" "ERROR"     }     # 7. Adım: GPO yapılandırmasını doğrulama     "GPO yapılandırması doğrulanıyor..." Write-Log "BİlGİ"     try {         $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop         Write-Log "GPO Durumu: $($gpoReport.GpoStatus)" "BİlGİ"         # Kayıt defteri ayarının yapılandırılıp yapılandırılmamış olduğunu denetleyin         $regSettings = Get-GPRegistryValue -Name $GPOName -Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue         if (-not $regSettings) {             # GPP kayıt defteri denetimini deneyin (GPO'da farklı yol)             "GPP kayıt defteri tercihleri denetleniyor..." Write-Log "BİlGİ"         }     } catch {         Write-Log "GPO doğrulanamadı: $($_. Exception.Message)" "WARN"     }     dönüş $true }                                                                                                

# ============================================================================ # WINCS DEPLOYMENT (AvailableUpdatesPolicy GPO'ya alternatif) # ============================================================================ # Başvuru: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe # # WinCS Komutları (SYSTEM bağlamı altında uç noktada çalıştırın): # Sorgu: WinCsFlags.exe /query --key F33E0C8E002 # Uygula: WinCsFlags.exe /apply --key "F33E0C8E002" # Sıfırla: WinCsFlags.exe /reset --key "F33E0C8E002" # # Bu yöntem, /apply WinCsFlags.exe çalıştıran zamanlanmış bir göreve sahip bir GPO dağıtır # hedeflenen uç noktalarda SYSTEM olarak kullanılır. Algılama betiğinin dağıtılma şekline benzer şekilde, # ancak günlük yerine bir kez (başlangıçta) çalışır.

function Deploy-WinCSGPOForWave {     <#     . ÖZET         GPO zamanlanmış görevi aracılığıyla WinCS Güvenli Önyükleme etkinleştirmesini dağıtın.. AÇIKLAMA         /apply WinCsFlags.exe çalıştırmak için zamanlanmış görev dağıtan bir GPO oluşturur         altında, makine başlangıcında SİSTEM bağlamı altında. Güvenlik grubu denetimleri hedefleme.. PARAMETER GPOName         GPO adı.. PARAMETER TargetOU         GPO'ya bağlanılacak OU.. PARAMETER SecurityGroupName         GPO filtreleme için güvenlik grubu.. PARAMETER WaveHostnames         Güvenlik grubuna eklenecek ana bilgisayar adları.. PARAMETRE WinCSKey         Uygulanacak WinCS anahtarı (varsayılan: F33E0C8E002).. DRYRun PARAMETRESI         True ise, yalnızca yapılacakları günlüğe kaydeder.#>     param(         [Parameter(Zorunlu = $true)]         [string]$GPOName,                  [Parameter(Zorunlu = $false)]         [string]$TargetOU,                  [Parameter(Zorunlu = $true)]         [string]$SecurityGroupName,                  [Parameter(Zorunlu = $true)]         [dizi]$WaveHostnames,                  [Parametre(Zorunlu = $false)]         [string]$WinCSKey = "F33E0C8E002",                  [Parameter(Zorunlu = $false)]         [bool]$DryRun = $false     )          # WinCsFlags.exe için Zamanlanmış Görev yapılandırması     $TaskName = "SecureBoot-WinCS-Apply"     $TaskPath = "\Microsoft\Windows\SecureBoot\"     $TaskDescription = "WinCS aracılığıyla Güvenli Önyükleme yapılandırmasını uygular - Anahtar: $WinCSKey"          Write-Log "WinCS GPO Dağıtma: $GPOName" "WAVE"     Write-Log "Görev çalışacak: WinCsFlags.exe /apply --key '"$WinCSKey'"" "BİlGİ"     Write-Log "Tetikleyici: Sistem başlangıcında (bir kez SISTEM olarak çalışır)" "BİlGİ"          if ($DryRun) {         Write-Log "[DRYRUN] GPO oluşturur: $GPOName" "BİlGİ"         Write-Log "[DRYRUN] Güvenlik grubu oluşturur: $SecurityGroupName" "BİlGİ"         Write-Log "[DRYRUN] $(@($WaveHostnames) ekler. Sayı) gruplandıracak bilgisayarlar" "BİlGİ"         Write-Log "[DRYRUN] Zamanlanmış görevi dağıtır: $TaskName" "BİlGİ"         Write-Log "[DRYRUN] GPO'ya bağlanır: $TargetOU" "BİlGİ"         return @{             Başarı = $true             GPOCreated = $false             GroupCreated = $false             BilgisayarlarAdded = 0         }     }          try {         # Gerekli modülleri içeri aktarma         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } catch {         Write-Log "Gerekli modüller (GroupPolicy, ActiveDirectory) içeri aktarılamadı: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Hata = $_. Exception.Message }     }          # 1. Adım: GPO oluşturma veya alma     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($gpo) {         Write-Log "GPO zaten var: $GPOName" "BİlGİ"     } else {         try {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Oluşturulan GPO: $GPOName" "Tamam"         } catch {             Write-Log "GPO oluşturulamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = $_. Exception.Message }         }     }          # 2. Adım: GPO dağıtımı için zamanlanmış görev XML'i oluşturma     # Bu, başlangıçta /apply WinCsFlags.exe çalışan bir görev oluşturur     $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Görev sürümü="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">   <RegistrationInfo>     <Açıklama>$TaskDescription</Açıklama>     WinCsFlags.exe1 Author>SYSTEM</Author>   WinCsFlags.exe5 /RegistrationInfo>   WinCsFlags.exe7 Tetikleyicileri>     BootTrigger>WinCsFlags.exe9       <Etkin>true</Enabled>       PT5M</Delay>>< Gecikme     /BootTrigger><   </Triggers>   <Sorumluları>     <Principal id="Author">       <UserId>S-1-5-18</UserId>       <RunLevel>HighestAvailable</RunLevel>     </Principal>   </Principals>   <Ayarları>     <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>     <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>     <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>     AllowHardTerminate>true</AllowHardTerminate><     <StartWhenAvailable>true</StartWhenAvailable>     <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>     IdleSettings><       <StopOnIdleEnd>false</StopOnIdleEnd>       <RestartOnIdle>false</RestartOnIdle>     </IdleSettings>     AllowStartOnDemand>true</AllowStartOnDemand><     <Etkin>true</Enabled>     <Gizli>yanlış</Gizli>     <RunOnlyIfIdle>false</RunOnlyIfIdle>     WinCsFlags.exe03 DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>     WinCsFlags.exe07 UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>     WinCsFlags.exe11 WakeToRun>false</WakeToRun>     WinCsFlags.exe15 ExecutionTimeLimit>PT1H</ExecutionTimeLimit>     WinCsFlags.exe19 DeleteExpiredTaskAfter>P30D</DeleteExpiredTaskAfter>     WinCsFlags.exe23 Önceliği>7</Öncelik>   WinCsFlags.exe27 /Ayarlar>   WinCsFlags.exe29 Actions Context="Author"WinCsFlags.exe30     WinCsFlags.exe31 Exec>       WinCsFlags.exe33 Komut>WinCsFlags.exe</Command>       WinCsFlags.exe37 Bağımsız Değişkenleri>/apply --key "$WinCSKey"WinCsFlags.exe39 /Arguments>     WinCsFlags.exe41 /Exec>   WinCsFlags.exe43 /Actions> WinCsFlags.exe45 /Görev> " @

    # Step 3: Deploy scheduled task via GPO Preferences     # Görev XML'sini GPO Zamanlanmış Görevler Anlık Görevi için SYSVOL'de depola     try {         $gpoId = $gpo. Id.ToString()         $sysvolPath = "\\$((Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Policies\{$gpoId}\Machine\Preferences\ScheduledTasks"         if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Directory -Path $sysvolPath -Force | Out-Null         }         # GPP için ScheduledTasks.xml oluşturma         $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">   <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U">       <Görev sürümü="1.3">         <RegistrationInfo>           <Açıklama>$TaskDescription</Description>         </RegistrationInfo>         <Sorumluları>           <Principal id="Author">             NT AUTHORITY\System</UserId>>UserId <             <LogonType>S4U</LogonType>             <RunLevel>HighestAvailable</RunLevel>           </Principal>         </Principals>         <Ayarları>           IdleSettings><             <Süre>PT5M</Süre>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle>false</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           AllowHardTerminate>true</AllowHardTerminate><           <StartWhenAvailable>true</StartWhenAvailable>           AllowStartOnDemand>true</AllowStartOnDemand><           <Etkin>true</Enabled>           <Gizli>yanlış</Gizli>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <Önceliği>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </Ayarlar>         <Tetikleyicileri>           <TimeTrigger>             <StartBoundary>$(Get-Date -Format 'yyyy-MM-dd')T00:00:00</StartBoundary>             <Etkin>true</Enabled>           </TimeTrigger>         </Triggers>         <Eylemleri>           <Exec>             <Komut>WinCsFlags.exe</Command>             <Bağımsız Değişkenler>/apply --key "$WinCSKey"</Arguments>           </Exec>         </Actions>       /Görev><     </Properties>   </ImmediateTaskV2> </ScheduledTasks> "@         $gppTaskXml | Out-File -FilePath (Join-Path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force         Write-Log "GPO'ya zamanlanmış görev dağıtıldı: $TaskName" "Tamam"     } catch {         Write-Log "Zamanlanmış görev XML'i dağıtılamadı: $($_. Exception.Message)" "WARN"         Write-Log "Kayıt defteri tabanlı WinCS dağıtımına geri dönme" "BİlGİ"         # Geri dönüş: GPP zamanlanmış görevi başarısız olursa WinCS kayıt defteri yaklaşımını kullanın         # WinCS kayıt defteri anahtarı aracılığıyla da tetiklenebilir         # (Uygulama, varsa WinCS kayıt defteri API'lerine bağlıdır)     }     # 4. Adım: Güvenlik grubu oluşturma veya alma     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $group) {         try {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Güvenliği '                 -GroupScope DomainLocal '                 -Açıklama "Güvenli Önyükleme WinCS dağıtımı için hedeflenen bilgisayarlar - $GPOName" '                 -PassThru             Write-Log "Oluşturulan güvenlik grubu: $SecurityGroupName" "Tamam"         } catch {             Write-Log "Güvenlik grubu oluşturulamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = $_. Exception.Message }         }     } else {         Write-Log "Güvenlik grubu var: $SecurityGroupName" "BİlGİ"     }     # 5. Adım: Güvenlik grubuna bilgisayar ekleme     $added = 0     $failed = 0     foreach ($WaveHostnames'da $hostname) {         try {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } catch {             $failed++         }     }     Write-Log "Güvenlik grubuna $added bilgisayarlar eklendi ($failed AD'de bulunamadı)" "Tamam"     # 6. Adım: GPO'da güvenlik filtrelemesini yapılandırma     try {         Set-GPPermission -Name $GPOName -TargetName "Kimliği Doğrulanmış Kullanıcılar" -TargetType Grubu -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Için yapılandırılmış güvenlik filtrelemesi: $SecurityGroupName" "Tamam"     } catch {         Write-Log "Güvenlik filtrelemesi yapılandırılamadı: $($_. Exception.Message)" "WARN"     }     # 7. Adım: GPO'yi OU'ya bağlama     if ($TargetOU) {         try {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Bağlı GPO: $TargetOU" "Tamam"             } else {                 Write-Log "Hedef OU'ya zaten bağlı GPO" "BİlGİ"             }         } catch {             Write-Log "CRITICAL: GPO OU'ya bağlanamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = "GPO bağlantısı başarısız oldu: $($_. Exception.Message)" }         }     }     Write-Log "WinCS GPO dağıtımı tamamlandı" "Tamam"     Write-Log "Makineler sonraki GPO yenilemesinde WinCsFlags.exe çalışacak + yeniden başlatma/başlatma" "BİlGİ"     return @{         Başarı = $true         GPOCreated = $true         GroupCreated = $true         ComputersAdded = $added         ComputersFailed = $failed     } }                                                                                        

# Wrapper function to maintain compatibility with main loop işlev Deploy-WinCSForWave {     param(         [Parameter(Zorunlu = $true)]         [dizi]$WaveHostnames,         [Parameter(Zorunlu = $false)]         [string]$WinCSKey = "F33E0C8E002",         [Parameter(Zorunlu = $false)]         [string]$WavePrefix = "SecureBoot-Rollout",         [Parameter(Mandatory = $false)]         [int]$WaveNumber = 1,         [Parametre(Zorunlu = $false)]         [string]$TargetOU,         [Parameter(Zorunlu = $false)]         [bool]$DryRun = $false     )     $gpoName = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $securityGroup = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $result = Deploy-WinCSGPOForWave '         -GPOName $gpoName '         -TargetOU $TargetOU '         -SecurityGroupName $securityGroup '         -WaveHostnames $WaveHostnames '         -WinCSKey $WinCSKey '         -DryRun $DryRun     # Beklenen dönüş biçimine dönüştür     return @{         Success = $result. Başarı         Uygulanan = $result. BilgisayarlarEkli         Atlandı = 0         Failed = if ($result. ComputersFailed) { $result. ComputersFailed } else { 0 }         Sonuçlar = @()     } }                                                            

# ============================================================================ # GÖREV DAĞıTıMLARıNı ETKINLEŞTIR # ============================================================================ # devre dışı bırakılmış zamanlanmış görevi olan cihazlara Enable-SecureBootUpdateTask.ps1 dağıtın.# Bir kez çalıştırılan anında zamanlanmış bir görevle GPO kullanır.

function Deploy-EnableTaskGPO {     <#     . ÖZET         GPO zamanlanmış görevi aracılığıyla Enable-SecureBootUpdateTask.ps1 dağıtın.. AÇIKLAMA         Etkinleştirmek için tek seferlik zamanlanmış bir görev dağıtan bir GPO oluşturur         Hedef cihazlarda Güvenli Önyükleme-Güncelleştirme zamanlanmış görevi.. PARAMETER TargetOU         GPO'ya bağlanılacak OU.. PARAMETER TargetHostnames         Devre dışı bırakılmış görevi olan cihazların ana bilgisayar adları (toplama raporundan).. DRYRun PARAMETRESI         True ise, yalnızca yapılacakları günlüğe kaydeder.#>     param(         [Parameter(Zorunlu = $false)]         [string]$TargetOU,                  [Parameter(Zorunlu = $true)]         [dizi]$TargetHostnames,                  [Parameter(Zorunlu = $false)]         [bool]$DryRun = $false     )          $GPOName = "SecureBoot-EnableTask-Remediation"     $SecurityGroupName = "SecureBoot-EnableTask-Devices"     $TaskName = "SecureBoot-EnableTask-OneTime"     $TaskDescription = "Secure-Boot-Update zamanlanmış görevini etkinleştirmek için tek seferlik görev"          Write-Log "=" * 70 "BİlGİ"     Write-Log "GÖREV DÜZELTMESİ ETKİnLEŞTİrMEYİ ETKİnLEŞTİrME" "BİlGİ"     Write-Log "=" * 70 "BİlGİ"     Write-Log "Hedef cihazlar: $($TargetHostnames.Count)" "BİlGİ"     Write-Log "GPO: $GPOName" "BİlGİ"     Write-Log "Güvenlik Grubu: $SecurityGroupName" "BİlGİ"          if ($DryRun) {         Write-Log "[DRYRUN] GPO oluşturur: $GPOName" "BİlGİ"         Write-Log "[DRYRUN] Güvenlik grubu oluşturur: $SecurityGroupName" "BİlGİ"         Write-Log "[DRYRUN] Gruplandırmak için $($TargetHostnames.Count) bilgisayarları ekler" "BİlGİ"         Write-Log "[DRYRUN] Secure-Boot-Update'i etkinleştirmek için tek seferlik zamanlanmış görev dağıtır" "BİlGİ"         Write-Log "[DRYRUN] GPO'ya bağlanır: $TargetOU" "BİlGİ"         return @{             Başarı = $true             BilgisayarlarAdded = 0             DryRun = $true         }     }          try {         # Gerekli modülleri içeri aktarma         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } catch {         Write-Log "Gerekli modüller içeri aktarılamadı: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Hata = $_. Exception.Message }     }          # 1. Adım: GPO oluşturma veya alma     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($gpo) {         Write-Log "GPO zaten var: $GPOName" "BİlGİ"     } else {         try {             $gpo = New-GPO -Name $GPOName -Comment "Güvenli Önyükleme Görevi Düzeltmeyi Etkinleştir - Oluşturuldu $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Oluşturulan GPO: $GPOName" "Tamam"         } catch {             Write-Log "GPO oluşturulamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = $_. Exception.Message }         }     }          # 2. Adım: Zamanlanmış görev XML'sini GPO SYSVOL'e dağıtma     # Görev, Secure-Boot-Update görevini etkinleştirmek için bir PowerShell komutu çalıştırır     try {         $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks"                  if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Directory -Path $sysvolPath -Force | Out-Null         }                  # Secure-Boot-Update görevini etkinleştirmek için PowerShell komutu         $enableCommand = 'schtasks.exe /Change /TN "\Microsoft\Windows\PI\Secure-Boot-Update" /ENABLE 2>$null; if ($LASTEXITCODE -ne 0) { Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI\" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue | Enable-ScheduledTask }'                  Güvenli XML ekleme için # Kodlama komutu         $encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableCommand))                  $taskGuid = [guid]::NewGuid(). ToString("B"). ToUpper()                  # GPP Zamanlanmış Görev XML'i - Bir kez çalıştırılan anında görev         $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">   <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="$taskGuid" removePolicy="1" userContext="0">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\SYSTEM" logonType="S4U">       <Görev sürümü="1.3">         <RegistrationInfo>           <Açıklama>$TaskDescription</Description>         </RegistrationInfo>         <Sorumluları>           <Principal id="Author">             <UserId>S-1-5-18</UserId>             <RunLevel>HighestAvailable</RunLevel>           </Principal>         </Principals>         <Ayarları>           <IdleSettings>             <Süre>PT5M</Süre>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle>false</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesİlke>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           AllowHardTerminate>true</AllowHardTerminate><           <StartWhenAvailable>true</StartWhenAvailable>           AllowStartOnDemand>true</AllowStartOnDemand><           <Etkin>true</Enabled>           <Gizli>yanlış</Gizli>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <Önceliği>7</Priority>           DeleteExpiredTaskAfter><PT0S</DeleteExpiredTaskAfter>         </Ayarlar>         <Eylemleri>           <Exec>             <Komut>powershell.exe</Command>             <Bağımsız Değişkenleri>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Arguments>           </Exec>         </Actions>       /Görev><     </Properties>   </ImmediateTaskV2> /ScheduledTasks>< "@                  $gppTaskXml | Out-File -FilePath (Join-Path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force         Write-Log "GPO'ya tek seferlik zamanlanmış görev dağıtıldı: $TaskName" "Tamam"              } catch {         Write-Log "Zamanlanmış görev XML'i dağıtılamadı: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Hata = $_. Exception.Message }     }          # 3. Adım: Güvenlik grubu oluşturma veya alma     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $group) {         try {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Güvenliği '                 -GroupScope DomainLocal '                 -Açıklama "Devre dışı bırakılmış Secure-Boot-Update görevi olan bilgisayarlar - düzeltme için hedeflendi" '                 -PassThru             Write-Log "Oluşturulan güvenlik grubu: $SecurityGroupName" "Tamam"         } catch {             Write-Log "Güvenlik grubu oluşturulamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = $_. Exception.Message }         }     } else {         Write-Log "Güvenlik grubu var: $SecurityGroupName" "BİlGİ"     }          # 4. Adım: Güvenlik grubuna bilgisayar ekleme     $added = 0     $failed = 0     foreach ($TargetHostnames'da $hostname) {         try {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } catch {             $failed++             Write-Log "Bilgisayar AD'de bulunamadı: $hostname" "UYARI"         }     }     Write-Log "Güvenlik grubuna $added bilgisayarlar eklendi ($failed AD'de bulunamadı)" "Tamam"          # 5. Adım: GPO'da güvenlik filtrelemesini yapılandırma     try {         Set-GPPermission -Name $GPOName -TargetName "Kimliği Doğrulanmış Kullanıcılar" -TargetType Grubu -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Için yapılandırılmış güvenlik filtrelemesi: $SecurityGroupName" "Tamam"     } catch {         Write-Log "Güvenlik filtreleme yapılandırılamadı: $($_. Exception.Message)" "WARN"     }          # 6. Adım: GPO'yi OU'ya bağlama     if ($TargetOU) {         try {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }                          if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Bağlı GPO: $TargetOU" "Tamam"             } else {                 Write-Log "GPO zaten hedef OU'ya bağlı" "BİlGİ"             }         } catch {             Write-Log "GPO OU'ya bağlanamadı: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Hata = "GPO bağlantısı başarısız oldu: $($_. Exception.Message)" }         }     } else {         Write-Log "Belirtilmemiş TargetOU - GPO el ile bağlanmalıdır" "UYARI"     }          "" "BİlGİ" Write-Log     Write-Log "GÖREV DAĞITIMINI ETKİnLEŞTİr" "Tamam"     Write-Log "Cihazlar sonraki GPO yenilemesinde etkinleştirme görevini çalıştırır (gpupdate)" "BİlGİ"     Write-Log "Görev bir kez SYSTEM olarak çalışır ve Secure-Boot-Update'i etkinleştirir" "BİlGİ"     "" "BİlGİ"      Write-Log     return @{         Başarı = $true         ComputersAdded = $added         ComputersFailed = $failed         GPOName = $GPOName         SecurityGroup = $SecurityGroupName     } }

# ============================================================================ # DEVRE DıŞı BıRAKıLMıŞ CIHAZLARDA GÖREVI ETKINLEŞTIR # ============================================================================ if ($EnableTaskOnDisabled) {     "" Write-Host     Write-Host ("=" * 70) -ForegroundColor Yellow     Write-Host " ETKİn GÖREV DÜZELTMESİ - Devre Dışı Bırakılmış Zamanlanmış Görevleri Düzeltme" -ForegroundColor Sarı     Write-Host ("=" * 70) -ForegroundColor Yellow     "" Write-Host     # Toplama verilerinden devre dışı bırakılmış görevi olan cihazları bulma     if (-not $AggregationInputPath) {         Write-Host "HATA: -AggregationInputPath devre dışı bırakılmış görevi olan cihazları tanımlamak için gereklidir" -ForegroundColor Red         Write-Host "Kullanım: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <yolu> -ReportBasePath <yolu>" -ForegroundColor Gray         çıkış 1     }     Write-Host "Devre dışı bırakılmış Secure-Boot-Update görevi olan cihazlar taranıyor..." -ForegroundColor Cyan     # JSON dosyalarını yükleme ve devre dışı bırakılmış görev içeren cihazları bulma     $jsonFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue |                  Where-Object { $_. Name -notmatch "ScanHistory|RolloutState|RolloutPlan" }     $disabledTaskDevices = @()     foreach ($jsonFiles $file) {         try {             $device = Get-Content $file. FullName -Raw | ConvertFrom-Json             if ($device. SecureBootTaskEnabled -eq $false -or                 $device. SecureBootTaskStatus -eq 'Disabled' -or                 $device. SecureBootTaskStatus -eq 'NotFound') {                 # Yalnızca henüz güncelleştirmemiş cihazları dahil et (Olay 1808 yok)                 if ([int]$device. Event1808Count -eq 0) {                     $disabledTaskDevices += $device. Hostname                 }             }         } catch {             # Geçersiz dosyaları atla         }     }     $disabledTaskDevices = $disabledTaskDevices | Select-Object -Benzersiz     if ($disabledTaskDevices.Count -eq 0) {         "" Write-Host         Write-Host "Devre dışı Bırakılmış Secure-Boot-Update görevine sahip cihaz bulunamadı." -ForegroundColor Green         Write-Host "Tüm cihazlarda görev etkin veya zaten güncelleştirilmiş." -ForegroundColor Gray         çıkış 0     }     Write-Host ""     Write-Host "Devre dışı bırakılmış $($disabledTaskDevices.Count) cihazları bulundu:" -ForegroundColor Yellow     $disabledTaskDevices | Select-Object -İlk 20 | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray }     if ($disabledTaskDevices.Count -gt 20) {         Write-Host " ... ve $($disabledTaskDevices.Count - 20) daha fazla" -ForegroundColor Gray     }     "" Write-Host     # Görev GPO'sunu Etkinleştir'i dağıtma     $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun     if ($result. Başarılı) {         "" Write-Host         Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green         Write-Host " Güvenlik grubuna eklenen bilgisayarlar: $($result. ComputersAdded)" -ForegroundColor Cyan         if ($result. ComputersFailed -gt 0) {             Write-Host " BILGISAYARLAR AD'de bulunamadı: $($result. ComputersFailed)" -ForegroundColor Yellow         }         "" Write-Host         Write-Host "SONRAKI ADIMLAR:" -ForegroundColor White         Write-Host " 1.                                              Cihazlar sonraki yenilemede GPO'ya sahip olacak (gpupdate /force)" -ForegroundColor Gray         Write-Host " 2. Tek seferlik görev Secure-Boot-Update'i etkinleştirir" -ForegroundColor Gray         Write-Host " 3. Görevin etkinleştirildiğini doğrulamak için toplamayı yeniden çalıştırın" -ForegroundColor Gray     } else {         "" Write-Host         "FAILED: Enable Task GPO" Write-Host -ForegroundColor Red         Write-Host "Hata: $($result. Hata)" -ForegroundColor Red     }          çıkış 0 }

# ============================================================================ # MAIN ORCHESTRATION DÖNGÜSÜ # ============================================================================

Write-Host "" Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - CONTINUOUS DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Cyan "" Write-Host

if ($DryRun) {     Write-Host "[DRY RUN MODE]" -ForegroundColor Magenta }

if ($UseWinCS) {     Write-Host "[WinCS MODE]" -ForegroundColor Yellow     Write-Host "GPO/AvailableUpdatesPolicy yerine WinCsFlags.exe kullanma" -ForegroundColor Yellow     Write-Host "WinCS Anahtarı: $WinCSKey" -ForegroundColor Gray     "" Write-Host }

Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Giriş Yolu: $AggregationInputPath" "BİlGİ" Write-Log "Rapor Yolu: $ReportBasePath" "BİlGİ" if ($UseWinCS) {     Write-Log "Dağıtım Yöntemi: WinCS (WinCsFlags.exe /apply --key '"$WinCSKey'")" "BİlGİ" } else {     Write-Log "Dağıtım Yöntemi: GPO (AvailableUpdatesPolicy)" "BİlGİ" }

# Resolve TargetOU - default to domain root for domain-wide coverage # Yalnızca GPO dağıtım yöntemi için gereklidir (WinCS AD/GPO gerektirmez) if (-not $UseWinCS -and -not $TargetOU) {     try {         # Etki alanı DN'sini almak için birden çok yöntem deneyin         $domainDN = $null         # Yöntem 1: Get-ADDomain (RSAT-AD-PowerShell gerektirir)         try {             Import-Module ActiveDirectory -ErrorAction Stop             $domainDN = (Get-ADDomain -ErrorAction Stop). DistinguishedName         } catch {             Write-Log "Get-ADDomain başarısız oldu: $($_. Exception.Message)" "WARN"         }         # Yöntem 2: ADSI aracılığıyla RootDSE kullanma         if (-not $domainDN) {             try {                 $rootDSE = [ADSI]"LDAP://RootDSE"                 $domainDN = $rootDSE.defaultNamingContext.ToString()             } catch {                 Write-Log "ADSI RootDSE başarısız oldu: $($_. Exception.Message)" "WARN"             }         }         # Yöntem 3: Bilgisayarın etki alanı üyeliğinden ayrıştırma         if (-not $domainDN) {             try {                 $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()                 $domainDN = "DC=" + ($domain. Ad -replace '\.', ',DC=')             } catch {                 Write-Log "GetComputerDomain başarısız oldu: $($_. Exception.Message)" "WARN"             }         }         if ($domainDN) {             $TargetOU = $domainDN             Write-Log "Hedef: Etki Alanı Kökü ($domainDN) - GPO, güvenlik grubu filtrelemesi aracılığıyla etki alanı genelinde uygulayacak" "BİlGİ"         } else {             Write-Log "Etki alanı DN'i belirlenemedi - GPO oluşturulacak ancak BAĞLANTILI DEĞIL!" "HATA"             Write-Log "Lütfen -TargetOU parametresini belirtin veya oluşturulduktan sonra GPO'yu el ile bağlayın" "HATA"             $TargetOU = $null         }     } catch {         Write-Log "Etki alanı DN'leri alınamadı - GPO oluşturulacak ancak bağlanmayacak.                                     Gerekirse el ile bağlan." "UYAR"         Write-Log "Hata: $($_. Exception.Message)" "WARN"         $TargetOU = $null     } } else {     Write-Log "Hedef OU: $TargetOU" "BİlGİ" }

Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Yoklama Aralığı: $PollIntervalMinutes dakika" "BİlGİ" if ($LargeScaleMode) {     Write-Log "LargeScaleMode etkin (toplu iş boyutu: $ProcessingBatchSize, günlük örneği: $DeviceLogSampleSize)" "BİlGİ" }

# ============================================================================ # ÖN KOŞUL DENETIMI: Algılamanın dağıtıldığından ve çalıştığından emin olun # ============================================================================

Write-Host "" "Önkoşullar denetleniyor..." Write-Log "BİlGİ"

$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath if (-not $detectionCheck.IsDeployed) {     Write-Log $detectionCheck.Message "HATA"     "" Write-Host     Write-Host "GEREKLI: Algılama altyapısını önce dağıt:" -ForegroundColor Yellow     Write-Host " 1. Çalıştır: Deploy-GPO-SecureBootCollection.ps1 -OUPath 'OU=...' -OutputPath '\\server\SecureBootLogs$'" -ForegroundColor Cyan     Write-Host " 2. Cihazların raporlanmasını bekleyin (12-24 saat)" -ForegroundColor Cyan     Write-Host " 3. Bu düzenleyiciyi yeniden çalıştırın" -ForegroundColor Cyan     "" Write-Host     if (-not $DryRun) {         Dönüş     } } else {     Write-Log $detectionCheck.Message "Tamam" }

# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Veri güncelliği: $($freshness. TotalFiles) dosyaları, $($freshness. FreshFiles) fresh (<24h), $($freshness. StaleFiles) eski (>72h)" "BİlGİ" if ($freshness. Uyarı) {     Write-Log $freshness. Uyarı "UYAR" }

# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() if ($AllowListPath -veya $AllowADGroup) {     $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup     if ($allowedHostnames.Count -gt 0) {         Write-Log "AllowList: YALNIZCA $($allowedHostnames.Count) cihazlar dağıtım için dikkate alınır" "BİlGİ"     } else {         Write-Log "AllowList belirtildi ancak cihaz bulunamadı - bu, tüm dağıtımları engeller!" "UYAR"     } }

# Load VIP/exclusion list (BlockList) $excludedHostnames = @() if ($ExclusionListPath -veya $ExcludeADGroup) {     $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup     if ($excludedHostnames.Count -gt 0) {         Write-Log "VIP Dışlama: $($excludedHostnames.Count) cihazları dağıtımdan atlanacak" "BİlGİ"     } }

# Load state $rolloutState = Get-RolloutState $blockedBuckets = Get-BlockedBuckets $adminApproved = Get-AdminApproved $deviceHistory = Get-DeviceHistory

if ($rolloutState.Status -eq "NotStarted") {     $rolloutState.Status = "InProgress"     $rolloutState.StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     Write-Log "Yeni dağıtım başlatılıyor" "WAVE" }

Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Engellenen Demetler: $($blockedBuckets.Count)" "BİlGİ"

# Main loop - runs until all eligible devices are updated $iterationCount = 0 while ($true) {     $iterationCount++     "" Write-Host     Write-Host ("=" * 80) -ForegroundColor White     Write-Log "=== YINELEME $iterationCount ===" "DALGA"     Write-Host ("=" * 80) -ForegroundColor White     # 1. Adım: Toplamayı çalıştırma     Write-Log "1. Adım: Toplama çalıştırılıyor..." "BİlGİ"     # Orchestrator disk şişmesini önlemek için her zaman tek bir klasörü (LargeScaleMode) yeniden kullanıyor     # Toplayıcıyı çalıştıran yöneticiler belirli bir noktaya anlık görüntüler için zaman damgalı klasörleri el ile alır     $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current"     # Toplamadan önce veri güncelliğini denetleyin     $freshness = Get-DataFreshness -JsonPath $AggregationInputPath     Write-Log "Veri güncelliği: $($freshness. FreshFiles)/$($freshness. TotalFiles) cihazları son 24 saat içinde bildirilen "BİlGİ"     if ($freshness. Uyarı) {         Write-Log $freshness. Uyarı "UYAR"     }     $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1"     $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json"     $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     if (Test Yolu $aggregateScript) {         if (-not $DryRun) {             # Orchestrator verimlilik için her zaman akış + artımlı kullanır             # Toplayıcı, en iyi performans için kullanılabilirse ps7'ye otomatik olarak yükseltir             $aggregateParams = @{                 InputPath = $AggregationInputPath                 OutputPath = $aggregationPath                 StreamingMode = $true                 IncrementalMode = $true                 SkipReportIfUnchanged = $true                 ParallelThreads = 8             }             # Varsa dağıtım özetini geçirme (hız/projeksiyon verileri için)             if (Test Yolu $rolloutSummaryPath) {                 $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath             }             & $aggregateScript @aggregateParams             # Cihaz tablolarıyla tam HTML panosu oluşturmak için komutu göster             "" Write-Host             Write-Host "Üretici/Model tablolarıyla tam HTML panosu oluşturmak için şunu çalıştırın:" -ForegroundColor Yellow             Write-Host " $aggregateScript -InputPath '"$AggregationInputPath'" -OutputPath '"$aggregationPath'"" -ForegroundColor Yellow             Write-Host ""         } else {             Write-Log "[DRYRUN] Toplama çalıştırılır" "BİlGİ"             # DryRun'da, doğrudan ReportBasePath'ten mevcut toplama verilerini kullanın             $aggregationPath = $ReportBasePath         }     }     $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     # 2. Adım: Geçerli cihaz durumunu yükleme     Write-Log "2. Adım: Cihaz durumu yükleniyor..." "BİlGİ"     $notUptodateCsv = Get-ChildItem -Path $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue |          Where-Object { $_. Name -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object -İlk 1     if (-not $notUptodateCsv -and -not $DryRun) {         Write-Log "Toplama verisi bulunamadı.                                            Bekleniyor..." "UYAR"         Start-Sleep -Seconds ($PollIntervalMinutes * 60)         Devam     }     $notUpdatedDevices = if ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } else { @() }     Write-Log "Cihazlar güncelleştirilmedi: $($notUpdatedDevices.Count)" "BİlGİ"     $notUpdatedIndexes = Get-NotUpdatedIndexes -Cihazlar $notUpdatedDevices     # 3. Adım: Cihaz geçmişini güncelleştirme (konak adına göre izleme)     Write-Log "3. Adım: Cihaz geçmişi güncelleştiriliyor..." "BİlGİ"     Update-DeviceHistory -CurrentDevices $notUpdatedDevices -DeviceHistory $deviceHistory     Save-DeviceHistory -Geçmiş $deviceHistory     # 4. Adım: Engellenen demetleri denetleme (ulaşılamayan cihazlar)     $existingBlockedCount = $blockedBuckets.Count     Write-Log "4. Adım: Engellenen demetleri denetleme (bekleme süresi geçmiş cihazlara ping işlemi)..." "BİlGİ"     if ($existingBlockedCount -gt 0) {         "Şu anda önceki çalıştırmalardan gelen engellenen demetler: $existingBlockedCount" "BİlGİ" Write-Log     }     if ($adminApproved.Count -gt 0) {         Write-Log "Yönetici onaylı demetler (yeniden engellenmez): $($adminApproved.Count)" "BİlGİ"     }     $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState -BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun     if ($newlyBlocked.Count -gt 0) {         Save-BlockedBuckets -Engellenen $blockedBuckets         Write-Log "Yeni engellenen demetler (bu yineleme): $($newlyBlocked.Count)" "ENGELLİ"     }     # 4b. Adım: Cihazların güncelleştirildiği demetlerin engellemesini otomatik olarak kaldırma     $autoUnblocked = Update-AutoUnblockedBuckets -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize     if ($autoUnblocked.Count -gt 0) {         Save-BlockedBuckets -Engellenen $blockedBuckets         Write-Log "Otomatik engelsiz demetler (cihazlar güncelleştirildi): $($autoUnblocked.Count)" "Tamam"     }     # 5. Adım: Kalan uygun cihazları hesaplama     $eligibleCount = 0     foreach ($notUpdatedDevices'da $device) {         $bucketKey = Get-BucketKey $device         if (-not $blockedBuckets.Contains($bucketKey)) {             $eligibleCount++         }     }     Write-Log "Kalan uygun cihazlar: $eligibleCount" "BİlGİ"     Write-Log "Engellenen demetler: $($blockedBuckets.Count)" "BİlGİ"     # 6. Adım: Tamamlanmasını denetleme     if ($eligibleCount -eq 0) {         Write-Log "ROLLOUT COMPLETE - Tüm uygun cihazlar güncelleştirildi!" "Tamam"         $rolloutState.Status = "Tamamlandı"         $rolloutState.CompletedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         Save-RolloutState -State $rolloutState         Mola     }     # 6. Adım: Sonraki dalgayı oluşturma ve dağıtma     Write-Log "6. Adım: Dağıtım dalgası oluşturuluyor..." "BİlGİ"     $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames     # Dağıtılacak cihazlarımız olup olmadığını denetleyin ($waveDevices $null, boş veya gerçek cihazlarla olabilir)     $hasDevices = $waveDevices -ve @($waveDevices | Where-Object { $_ }). Say -gt 0     if ($hasDevices) {         # Yalnızca dağıtılacak cihazlarımız olduğunda dalga sayısını artırma         $rolloutState.CurrentWave++         Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Count) devices" "WAVE"         # Satır içi işlevi kullanarak GPO dağıtma         $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $hostnames = @($waveDevices | ForEach-Object {             if ($_. Ana bilgisayar adı) { $_. Hostname } elseif ($_. HostName) { $_. HostName } else { $null }         } | Where-Object { $_ })         # Başvuru/denetim için konak adları dosyasını kaydetme         $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt"         $hostnames | Out-File $hostnamesFile -UTF8 Kodlaması         # Dağıtılacak ana bilgisayar adlarımız olduğunu doğrulayın         if ($hostnames. Count -eq 0) {             Write-Log "$($rolloutState.CurrentWave) dalgasında geçerli ana bilgisayar adı bulunamadı - cihazlarda Hostname özelliği eksik olabilir" "WARN"             Write-Log "Bu dalga için dağıtım atlanıyor - cihaz verilerini denetleyin" "UYARI"             # Yine de sonraki yinelemeden önce bekleyin             if (-not $DryRun) {                 Write-Log "Yeniden denemeden önce $PollIntervalMinutes dakika uyku..." "BİlGİ"                 Start-Sleep -Seconds ($PollIntervalMinutes * 60)             }             Devam         }         Write-Log "$($hostnames'a dağıtma. Count) wave $($rolloutState.CurrentWave)" "INFO" içindeki konak adları         # -UseWinCS parametresini temel alan WinCS veya GPO yöntemini kullanarak dağıtma         if ($UseWinCS) {             # WinCS Yöntemi: WinCsFlags.exe her uç noktada SYSTEM olarak çalıştırmak için zamanlanmış görevle GPO oluşturma             Write-Log "WinCS dağıtım yöntemini kullanma (Anahtar: $WinCSKey)" "WAVE"             $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames '                 -WinCSKey $WinCSKey '                 -WavePrefix $WavePrefix '                 -WaveNumber $rolloutState.CurrentWave '                 -TargetOU $TargetOU '                 -DryRun:$DryRun             if (-not $wincsResult.Success) {                 Write-Log "WinCS dağıtımında hatalar vardı - Uygulandı: $($wincsResult.Applied), Failed: $($wincsResult.Failed)" "WARN"             } else {                 Write-Log "WinCS dağıtımı başarılı - Uygulandı: $($wincsResult.Applied), Atlandı: $($wincsResult.Atlandı)" "Tamam"             }             # WinCS sonuçlarını denetim için kaydetme             $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json"             $wincsResult | ConvertTo-Json -Derinlik 5 | Out-File $wincsResultFile -UTF8 Kodlaması         } else {             # GPO Yöntemi: AvailableUpdatesPolicy kayıt defteri ayarıyla GPO oluşturma             $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun             if (-not $gpoResult) {                 Write-Log "GPO dağıtımı başarısız oldu - sonraki yinelemeyi yeniden deneyecek" "HATA"             }         }         # Kayıt dalga durumu         $waveRecord = @{             WaveNumber = $rolloutState.CurrentWave             StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             DeviceCount = @($waveDevices). Sayısı             Cihazlar = @($waveDevices | ForEach-Object {                 @{                     Ana bilgisayar adı = if ($_. Ana bilgisayar adı) { $_. Hostname } elseif ($_. HostName) { $_. HostName } else { $null }                     BucketKey = Get-BucketKey $_                 }             })         }         # Eklemeden önce WaveHistory'nin her zaman bir dizi olduğundan emin olun (karma tablo birleştirme sorunlarını önler)         $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord)         $rolloutState.TotalDevicesTargeted += @($waveDevices). Sayısı         Save-RolloutState -State $rolloutState         Write-Log "Wave $($rolloutState.CurrentWave) dağıtıldı.                                                                                                                                                                                        $PollIntervalMinutes dakika bekleniyor..." "Tamam"     } else {         # Güncelleştirmeleri bekleyen dağıtılan cihazların durumunu göster         "" "BİlGİ" Write-Log         Write-Log "DAĞıTıLAN TÜM CIHAZLARı ========== - DURUM ========== BEKLENIYOR" "BİlGİ"                  # Dalga geçmişinden dağıtılan tüm cihazları alma         $allDeployedLookup = @{}         foreach ($rolloutState.WaveHistory'de $wave) {             foreach ($wave'da $device. Cihazlar) {                 if ($device. Ana bilgisayar adı) {                     $allDeployedLookup[$device. Ana bilgisayar adı] = @{                         Hostname = $device. Hostname                         BucketKey = $device. BucketKey                         DeployedAt = $wave. StartedAt                         WaveNumber = $wave. WaveNumber                     }                 }             }         }         $allDeployedDevices = @($allDeployedLookup.Values)                  if ($allDeployedDevices.Count -gt 0) {             # Hangi dağıtılan cihazların hala beklemede olduğunu bulun (NotUpdated listesinde)             $stillPendingCount = 0             $noLongerPendingCount = 0             $pendingSample = @()             foreach ($allDeployedDevices'da $deployed) {                 if ($notUpdatedIndexes.HostSet.Contains($deployed. Ana bilgisayar adı)) {                     $stillPendingCount++                     if ($pendingSample.Count -lt $DeviceLogSampleSize) {                         $pendingSample += $deployed. Hostname                     }                 } else {                     $noLongerPendingCount++                 }             }                          # Toplamadan gerçek Güncelleştirilmiş sayıları alma - Olay 1808 ile UEFICA2023Status karşılaştırmasını ayırt etme             $summaryCsv = Get-ChildItem -Path $aggregationPath -Filter "*Summary*.csv" |                  Sort-Object LastWriteTime -Descending | Select-Object -İlk 1             $actualUpdated = 0             $totalDevicesFromSummary = 0             $event 1808Count = 0             $uefiStatusUpdated = 0             $needsRebootSample = @()                          if ($summaryCsv) {                 $summary = Import-Csv $summaryCsv.FullName | Select-Object -İlk 1                 if ($summary. Güncelleştirildi) { $actualUpdated = [int]$summary. Güncelleştirildi }                 if ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices }             }                          # Dalga geçmişinden hızı hesaplama (cihazlar günde güncelleştirilir)             $devicesPerDay = 0             if ($rolloutState.StartedAt -and $actualUpdated -gt 0) {                 $startDate = [datetime]::P arse($rolloutState.StartedAt)                 $daysElapsed = ((Get-Date) - $startDate). TotalDays                 if ($daysElapsed -gt 0) {                     $devicesPerDay = $actualUpdated / $daysElapsed                 }             }                          # Hafta sonu fark eden projeksiyonlarla dağıtım özetini kaydetme             # Tutarlılık için toplayıcının NotUptodate sayısını kullanın (SB OFF cihazlarını hariç tutar)             $notUpdatedCount = if ($summary -ve $summary. NotUptodate) { [int]$summary. NotUptodate } else { $totalDevicesFromSummary - $actualUpdated }             Save-RolloutSummary -State $rolloutState '                 -TotalDevices $totalDevicesFromSummary '                 -UpdatedDevices $actualUpdated '                 -NotUpdatedDevices $notUpdatedCount '                 -DevicesPerDay $devicesPerDay                          # UEFICA2023Status=Updated ancak Event 1808 olmayan cihazlar için ham verileri denetleyin (yeniden başlatma gerekiyor)             $dataFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue             $totalDataFiles = @($dataFiles). Sayısı             $batchSize = [Matematik]::Max(500, $ProcessingBatchSize)             if ($LargeScaleMode) {                 $batchSize = [Matematik]::Max(2000, $ProcessingBatchSize)             }

            if ($totalDataFiles -gt 0) {                 for ($idx = 0; $idx -lt $totalDataFiles; $idx += $batchSize) {                     $end = [Matematik]::Min($idx + $batchSize - 1, $totalDataFiles - 1)                     $batchFiles = $dataFiles[$idx.. $end]

                    foreach ($file in $batchFiles) {                         try {                             $deviceData = Get-Content $file. FullName -Raw | ConvertFrom-Json                             $hostname = $deviceData.Hostname                             if (-not $hostname) { continue }                             $has 1808 = [int]$deviceData.Event1808Count -gt 0                             $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Güncelleştirildi"                             if ($has 1808) {                                 $event 1808Count++                             } elseif ($hasUefiUpdated) {                                 $uefiStatusUpdated++                                 if ($needsRebootSample.Count -lt $DeviceLogSampleSize) {                                     $needsRebootSample += $hostname                                 }                             }                         } catch { }                     }                                                          

                    Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{                         Event1808Count = $event 1808Count                         UEFIUpdatedAwaitingReboot = $uefiStatusUpdated                     }                 }             }             Write-Log "Dağıtılan toplam: $($allDeployedDevices.Count)" "BİlGİ"             Write-Log "Güncelleştirildi (Olay 1808 onaylandı): $event 1808Count" "Tamam"             if ($uefiStatusUpdated -gt 0) {                 Write-Log "Güncelleştirildi (UEFICA2023Status=Updated, yeniden başlatma bekleniyor): $uefiStatusUpdated" "Tamam"                 $rebootSuffix = if ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated - $DeviceLogSampleSize) more)" } else { "" }                 Write-Log " Olay 1808 için yeniden başlatması gereken cihazlar (örnek): $($needsRebootSample -join ', ')$rebootSuffix" "BİlGİ"                 Write-Log " Bu cihazlar bir sonraki yeniden başlatmadan sonra Olay 1808'i bildirecek" "BİlGİ"             }             Write-Log "Artık beklemede değil: $noLongerPendingCount (SecureBoot OFF, eksik cihazlar içerir)" "BİlGİ"             Write-Log "Durum bekleniyor: $stillPendingCount" "BİlGİ"             if ($stillPendingCount -gt 0) {                 $pendingSuffix = if ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount - $DeviceLogSampleSize) more)" } else { "" }                 Write-Log "Bekleyen cihazlar (örnek): $($pendingSample -join ', ')$pendingSuffix" "WARN"             }         } else {             Write-Log "Henüz hiçbir cihaz dağıtılmadı" "BİlGİ"         }         Write-Log "================================================================" "BİlGİ"         "" "BİlGİ" Write-Log     }     # Sonraki yinelemeden önce bekleyin     if (-not $DryRun) {         Write-Log "$PollIntervalMinutes dakika uyku..." "BİlGİ"         Start-Sleep -Seconds ($PollIntervalMinutes * 60)     } else {         Write-Log "[DRYRUN] $PollIntervalMinutes dakika bekler" "BİlGİ"         kesme # Kuru çalıştırmada bir yinelemeden sonra çık     } }                               

# ============================================================================ # SON ÖZET # ============================================================================

Write-Host "" Write-Host ("=" * 80) -ForegroundColor Green Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -ForegroundColor Green Write-Host ("=" * 80) -ForegroundColor Green "" Write-Host

$finalState = Get-RolloutState $finalBlocked = Get-BlockedBuckets

Write-Host "Status:              $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Toplam Dalgalar: $($finalState.CurrentWave)" Write-Host "Hedeflenen Cihazlar: $($finalState.TotalDevicesTargeted)" Write-Host "Engellenen Demetler: $($finalBlocked.Count)" -ForegroundColor $(if ($finalBlocked.Count -gt 0) { "Red" } else { "Green" }) Write-Host "İzlenen Cihazlar: $($deviceHistory.Count)" -ForegroundColor Gray "" Write-Host

if ($finalBlocked.Count -gt 0) {     Write-Host "ENGELLİ KOVALAR (el ile gözden geçirme gerekir):" -ForegroundColor Red     foreach ($finalBlocked.Keys'de $key) {         $info = $finalBlocked[$key]         Write-Host " - $key" -ForegroundColor Red         Write-Host " Neden: $($info. Reason)" -ForegroundColor Gray     }     "" Write-Host     Write-Host "Engellenen demetler dosyası: $blockedBucketsPath" -ForegroundColor Yellow }

Write-Host "" Write-Host "State files:" -ForegroundColor Cyan Write-Host " Dağıtım Durumu: $rolloutStatePath" Write-Host " Engellenen Demetler: $blockedBucketsPath" Write-Host " Cihaz Geçmişi: $deviceHistoryPath" "" Write-Host  

​​​​​​​

Daha fazla yardıma mı ihtiyacınız var?

Daha fazla seçenek mi istiyorsunuz?

Abonelik avantajlarını keşfedin, eğitim kurslarına göz atın, cihazınızın güvenliğini nasıl sağlayacağınızı öğrenin ve daha fazlasını yapın.