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