Skopírujte a prilepte tento vzorový skript a podľa potreby ho upravte pre svoje prostredie:

<# . PREHĽADU     Orchestrator zavádzania kontinuálneho zabezpečeného spustenia, ktorý sa spúšťa až do dokončenia nasadenia.

.DESCRIPTION     Tento skript poskytuje úplnú úplnú automatizáciu pre zavedenie certifikátu zabezpečeného spustenia:     1.      Generuje vlny zavádzania na základe údajov agregácie     2. Vytvorí skupiny AD a objekt GPO pre každú vlnu     3. Monitory aktualizácií zariadení (udalosť 1808)     4. Zisťuje blokované sektory (nedostupné zariadenia)     5. Automaticky prejde na ďalšiu vlnu     6. Spúšťa sa, kým sa neaktualizujú      všetky oprávnené zariadenia     Kritériá dokončenia:     - Žiadne zariadenia zostávajúce v: Vyžaduje sa akcia, Vysoká spoľahlivosť, Pozorovanie, Dočasne pozastavené     - Mimo rozsahu (podľa návrhu): Nepodporované, Zabezpečené spustenie zakázané     - Beží nepretržite až do dokončenia - žiadny ľubovoľný limit      vĺn     Stratégia uvedenia:     - VYSOKÁ SPOĽAHLIVOSŤ: Všetky zariadenia v prvej vlne (bezpečné)     - VYŽADUJE SA AKCIA: Progresívna štvorhra (1→2→4→8...)          Logika blokovania:     - Po MaxWaitHours, orchestrator ping zariadenia, ktoré neboli aktualizované     - Ak je zariadenie NEDOSTUPNÉ (ping zlyhá), → sektor je zablokovaný na skúmanie     - Ak je zariadenie dosiahnuteľné, ale neaktualizované, → čakať (môže sa vyžadovať reštart)     - Blokované sektory sú vylúčené, kým ich      správca neodblokuje     Automatické odblokovanie:     - Ak sa zariadenie v zablokovanom sektore neskôr zobrazuje ako aktualizované (udalosť 1808),       sektor sa automaticky odblokuje a výnosy z uvedenia     – Táto možnosť spracováva zariadenia, ktoré boli dočasne offline, ale vrátili sa          Sledovanie zariadenia:     – sleduje zariadenia podľa názvu hostiteľa (predpokladá sa, že počas uvedenia sa názvy nemenia)     - Poznámka: Kolekcia JSON neobsahuje jedinečné ID počítača. pridať jeden na lepšie sledovanie

.PARAMETER AggregationInputPath     Cesta k nespracovaným údajom zariadenia JSON (z detekcie skriptu)

.PARAMETER ReportBasePath     Základná cesta pre agregačné zostavy

.PARAMETER TargetOU     Rozlišujúci názov jednotky na prepojenie GPO.Voliteľné – ak nie je zadané, objekt GPO je prepojený s koreňovým adresárom domény pre pokrytie celej domény.Filtrovanie skupín zabezpečenia zabezpečí, že politiku dostanú iba cieľové zariadenia.

.PARAMETER MaxWaitHours     Hodiny čakania na aktualizáciu zariadení pred kontrolou dostupnosti.Po uplynutí tohto času sa zariadenia, ktoré sa neaktualizovali, zobrazia príkaz ping.Nedostupné zariadenia spôsobia zablokovanie sektora.Predvolené: 72 (3 dni)

.PARAMETER PollIntervalMinutes     Minúty medzi kontrolami stavu. Predvolené: 1 440 (1 deň)

.PARAMETER AllowListPath     Cesta k súboru obsahujúcemu názvy hostiteľov, ktoré sa majú povoliť pre zavedenie (cielené zavádzanie).Podporuje .txt (jeden názov hostiteľa na riadok) alebo .csv (so stĺpcom Hostname/ComputerName/Name).Ak je zadaný, do uvedenia sa zahrnú len tieto zariadenia.BlockList sa stále používa po AllowList.

.PARAMETER AllowADGroup     Názov skupiny zabezpečenia služby AD obsahujúcej počítačové kontá, ktoré sa majú povoliť.Príklad: SecureBoot-Pilot-Computers alebo Wave1-Devices     Ak je zadané, do uvedenia sa zahrnú len zariadenia v tejto skupine.Kombinovať s AllowListPath pre zameranie na súbor aj na základe AD.

.PARAMETER ExclusionListPath     Cesta k súboru obsahujúcemu názvy hostiteľov na vylúčenie z uvedenia (VIP/výkonné zariadenia).Podporuje .txt (jeden názov hostiteľa na riadok) alebo .csv (so stĺpcom Hostname/ComputerName/Name).Tieto zariadenia nikdy nebudú zahrnuté do žiadnej vlny zavádzania.Filter BlockList sa použije PO filtrovaní AllowList.     . PARAMETER ExcludeADGroup     Názov skupiny zabezpečenia AD obsahujúcej počítačové kontá, ktoré sa majú vylúčiť.Príklad: "VIP-Computers" alebo "Executive-Devices"     Kombinovať s exclusionListPath pre vylúčenia oboch súborov a AD.

.PARAMETER UseWinCS     Namiesto objektu GPO/AvailableUpdatesPolicy použite WinCS (Windows Configuration System).WinCS nasadzuje povolenie zabezpečeného spustenia spustením WinCsFlags.exe priamo v každom koncovom bode.WinCsFlags.exe sa spúšťa v kontexte SYSTEM prostredníctvom naplánovanej úlohy.Táto metóda je užitočná pre:     - Rýchlejšie zavádzanie (okamžitý účinok vs čakanie na spracovanie GPO)     – zariadenia pripojené k doméne     - Prostredia bez infraštruktúry AD/GPO     Odkaz: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe

.PARAMETER WinCSKey     Kľúč WinCS, ktorý sa má použiť na povolenie zabezpečeného spustenia.Predvolené: F33E0C8E002     Tento kľúč zodpovedá konfigurácii zavádzania zabezpečeného spustenia.     . PARAMETER DryRun     Zobraziť, čo by sa robilo bez vykonania zmien

.PARAMETER ListBlockedBuckets     Zobraziť všetky aktuálne blokované sektory a skončiť

.PARAMETER UnblockBucket     Odblokovanie konkrétneho sektora podľa kľúča a ukončenie

.PARAMETER UnblockAll     Odblokovať všetky sektory a skončiť

.PARAMETER EnableTaskOnDisabled     Nasaďte Enable-SecureBootUpdateTask.ps1 do všetkých zariadení s vypnutou naplánovanou úlohou.Vytvorí objekt GPO s jednorazovo naplánovanou úlohou, ktorá spúšťa skript Povoliť s možnosťou -Quiet.Je to užitočné na opravu zariadení, ktoré majú vypnutú úlohu Secure-Boot-Update.

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

.EXAMPLE     # Zoznam blokovaných sektorov     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets

.EXAMPLE     # Odblokovať konkrétny sektor     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"

.EXAMPLE     # Vylúčiť VIP zariadenia z uvedenia pomocou textového súboru     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExclusionListPath "C:\Admin\VIP-Devices.txt"

.EXAMPLE     # Vylúčiť zariadenia v skupine zabezpečenia AD (napr. výkonné prenosné počítače)     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExcludeADGroup "VIP-Computers"

.EXAMPLE     # Použite WinCS (Windows Configuration System) namiesto GPO/AvailableUpdatesPolicy     # WinCsFlags.exe sa spúšťa v kontexte SYSTEM v každom koncovom bode prostredníctvom naplánovanej úlohy     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -UseWinCS '         -WinCSKey "F33E0C8E002" #>

[CmdletBinding()] param(     [Parameter(Povinné = $false)]     [reťazec]$AggregationInputPath,     [Parameter(Povinné = $false)]     [reťazec]$ReportBasePath,     [Parameter(Povinné = $false)]     [reťazec]$TargetOU,     [Parameter(Povinné = $false)]     [reťazec]$WavePrefix = "SecureBoot-Rollout",     [Parameter(Povinné = $false)]     [int]$MaxWaitHours = 72,     [Parameter(Povinné = $false)]     [int]$PollIntervalMinutes = 1440,                         

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

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

    [Parameter(Mandatory = $false)]     [prepínač]$LargeScaleMode,     # ============================================================================     # Parametre AllowList/BlockList     Počet ============================================================================     # AllowList = Zahrnúť iba tieto zariadenia (cielené zavádzanie)     # BlockList = Vylúčiť tieto zariadenia (nikdy sa nebudú zavádzať)     # Objednávka spracovania: Najskôr AllowList (ak je zadaný), potom BlockList     [Parameter(Povinné = $false)]     [reťazec]$AllowListPath,     [Parameter(Povinné = $false)]     [reťazec]$AllowADGroup,     [Parameter(Povinné = $false)]     [reťazec]$ExclusionListPath,     [Parameter(Povinné = $false)]     [reťazec]$ExcludeADGroup,     # ============================================================================     # WinCS (Windows Configuration System) Parametre     # ============================================================================     # WinCS je alternatívou k nasadeniu GPO AvailableUpdatesPolicy.                              # Používa WinCsFlags.exe na každom koncovom bode na povolenie spustenia zabezpečeného spustenia.# WinCsFlags.exe sa spustí v rámci kontextu SYSTEM v koncovom bode.# Referencia: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe          [Parameter(Povinné = $false)]     [prepínač]$UseWinCS,          [Parameter(Povinné = $false)]     [reťazec]$WinCSKey = "F33E0C8E002",          [Parameter(Povinné = $false)]     [prepínač]$DryRun,          [Parameter(Povinné = $false)]     [prepínač]$ListBlockedBuckets,          [Parameter(Povinné = $false)]     [reťazec]$UnblockBucket,          [Parameter(Povinné = $false)]     [prepínač]$UnblockAll,          [Parameter(Povinné = $false)]     [prepínač]$EnableTaskOnDisabled )

$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Nasadenie a monitorovanie ukážok"

# ============================================================================ # OVERENIE ZÁVISLOSTI # ============================================================================

function Test-ScriptDependencies {     param(         [Parameter(Povinné = $true)]         [reťazec]$ScriptDirectory,         [Parameter(Povinné = $true)]         [reťazec[]]$RequiredScripts     )     $missingScripts = @()     foreach ($script in $RequiredScripts) {         $scriptPath = Join-Path $ScriptDirectory $script         if (-not (Test-Path $scriptPath)) {             $missingScripts += $script         }     }     if ($missingScripts.Count -gt 0) {         Write-Host ""         Write-Host ("=" * 70) -Farba popredia červená         Write-Host "MISSING DEPENDENCIES" -ForegroundColor Red         Write-Host ("=" * 70) -Farba popredia červená         Write-Host ""         Write-Host "Nenašli sa nasledujúce požadované skripty:" -ForegroundColor Yellow         foreach ($script in $missingScripts) {             Write-Host " - $script" -Farba popredia Biela         }         Write-Host ""         Write-Host "Stiahnite si najnovšie skripty z:" -ForegroundColor Cyan         Write-Host " URL: $DownloadUrl" -Farba popredia biela         Write-Host " Prejsť na: '$DownloadSubPage'" -ForegroundColor White         Write-Host ""         Write-Host "Extrahovať všetky skripty do rovnakého adresára a spustiť znova." -ForegroundColor Yellow         Write-Host ""         vrátenie $false     }     vrátenie $true }                             

# 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)) {     ukončiť 1 }

# ============================================================================ # OVERENIE PARAMETRA # ============================================================================

# Admin commands only need ReportBasePath $isAdminCommand = $ListBlockedBuckets -or $UnblockBucket -or $UnblockAll -or $EnableTaskOnDisabled

if (-not $ReportBasePath) {     Write-Host "ERROR: -ReportBasePath je povinný." -Farba popredia červená     ukončiť 1 }

if (-not $isAdminCommand -and -not $AggregationInputPath) {     Write-Host "CHYBA: -AggregationInputPath sa vyžaduje pre zavedenie (nie je potrebné pre -ListBlockedBuckets, -UnblockBucket, -UnblockAll)" -ForegroundColor Red     ukončiť 1 }

# ============================================================================ # GPO DETECTION - CHECK FOR DETECTION GPO # ============================================================================

if (-not $isAdminCommand -and -not $DryRun) {     $CollectionGPOName = "SecureBoot-EventCollection"     # Skontrolujte, či je modul GroupPolicy k dispozícii     if (Get-Module -ListAvailable -Name GroupPolicy) {         Import-Module GroupPolicy -ErrorAction SilentlyContinue         Write-Host "Checking for Detection GPO..." -ForegroundColor Yellow         vyskúšať {             # Skontrolujte, či objekt GPO existuje             $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue             ak ($existingGpo) {                 Write-Host " Detection GPO found: $CollectionGPOName" -ForegroundColor Green             } else {                 Write-Host ""                 Write-Host ("=" * 70) -Farba popredia žltá                 Write-Host " UPOZORNENIE: DETEKCIA OBJEKT GPO SA NENAŠIEL" -ForegroundColor Yellow                 Write-Host ("=" * 70) -Farba popredia žltá                 Write-Host ""                 Write-Host "The detection GPO '$CollectionGPOName' was not found." -ForegroundColor Yellow                 Write-Host "Without this GPO, no device data will be collected." -ForegroundColor Yellow                 Write-Host ""                 Write-Host "Ak chcete nasadiť objekt GPO zisťovania, spustite:" -Popredie Azúrová farba                 Write-Host " .\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domain> -AutoDetectOU" -ForegroundColor White                 Write-Host ""                 Write-Host "Chcete napriek tomu pokračovať?                                     (Y/N)" -ForegroundColor Yellow                 $response = Read-Host                 if ($response -notmatch '^[Yy]') {                     Write-Host "Ruší sa. Najskôr nasadiť objekt GPO zisťovania." -ForegroundColor Red                     ukončiť 1                 }             }         } chytiť {             Write-Host " Nepodarilo sa skontrolovať objekt GPO: $($_. Exception.Message)" -ForegroundColor Yellow         }     } else {         Write-Host " GroupPolicy module not available - skipping GPO check" -ForegroundColor Gray     }     Write-Host "" }

# ============================================================================ # CESTY K SÚBORU STAVU # ============================================================================

$stateDir = Join-Path $ReportBasePath "RolloutState" if (-not (Test-Path $stateDir)) {     New-Item -ItemType Directory -Path $stateDir -Force | Hodnota 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 KOMPATIBILITA: ConvertTo-Hashtable # ============================================================================ # ConvertFrom-Json -AsHashtable je len PS7+ . Táto funkcia poskytuje kompatibilitu.

function ConvertTo-Hashtable {     param(         [Parameter(ValueFromPipeline = $true)]         $InputObject     )     proces {         if ($null -eq $InputObject) { return @{} }         if ($InputObject -is [System.Collections.IDictionary]) { return $InputObject }         if ($InputObject -is [PSCustomObject]) {             # Použite [ordered] na konzistentné zoraďovanie kľúčov a bezpečné spracovanie duplikátov             $hash = [ordered]@{}             foreach ($prop in $InputObject.PSObject.Properties) {                 # Indexované priradenie bezpečne spracováva duplicity prepísaním                 $hash[$prop. Názov] = ConvertTo-Hashtable $prop. Hodnota             }             vrátenie $hash         }         if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [reťazec]) {             vrátiť @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ })         }         vrátenie $InputObject     } }

# ============================================================================ # PRÍKAZY SPRÁVCU: Zoznam alebo Odblokovanie sektorov # ============================================================================

if ($ListBlockedBuckets) {     Write-Host ""     Write-Host ("=" * 80) -Farba popredia žltá     Write-Host "BLOCKED BUCKETS" -ForegroundColor Yellow     Write-Host ("=" * 80) -Farba popredia žltá     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku         ak ($blocked. Počet -eq 0) {             Write-Host "No blocked buckets" (Žiadne blokované sektory). -ForegroundColor Green         } else {             Write-Host "Total blocked: $($blocked. Count)" -ForegroundColor Red             Write-Host ""             foreach ($key in $blocked. Kľúče) {                 $info = $blocked[$key]                 Write-Host "Bucket: $key" -ForegroundColor Red                 Write-Host " Blokované v: $($info. BlockedAt)" -ForegroundColor Gray                 Write-Host " Dôvod: $($info. Reason)" -ForegroundColor Gray                 Write-Host " Failed Device: $($info. FailedDevice)" -ForegroundColor Gray                 Write-Host " Naposledy nahlásené: $($info. LastReported)" -ForegroundColor Gray                 Write-Host " Wave: $($info. WaveNumber)" -Farba popredia Sivá                 Write-Host " Devices in Bucket: $($info. DevicesInBucket)" -ForegroundColor Gray                 Write-Host ""             }             Write-Host "Odblokovanie sektora:"             Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY'" -ForegroundColor Cyan             Write-Host ""             Write-Host "Ak chcete odblokovať všetko:"             Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockAll" -ForegroundColor Cyan         }     } else {         Write-Host "Nenašiel sa žiadny blokovaný súbor sektorov." -ForegroundColor Green     }     Write-Host ""     ukončiť 0 }     

if ($UnblockBucket) {     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku         ak ($blocked. Contains($UnblockBucket)) {             $blocked. Remove($UnblockBucket)             $blocked | ConvertTo-Json -Hĺbka 10 | Out-File $blockedBucketsPath -kódovanie UTF8 -Force             # Ak chcete zabrániť opätovnému blokovaniu, pridajte ho do zoznamu schváleného správcom             $adminApproved = @{}             if (test-path $adminApprovedPath) {                 $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku             }             $adminApproved[$UnblockBucket] = @{                 ApprovedAt = Get-Date -Format "rrrr-MM-dd HH:mm:ss"                 ApprovedBy = $env:USERNAME             }             $adminApproved | ConvertTo-Json -Hĺbka 10 | Out-File $adminApprovedPath -kódovanie UTF8 -Force             Write-Host "Unblocked bucket: $UnblockBucket" -ForegroundColor Green             Write-Host "Added to admin-approved list (will not re-blocked automatically)" -ForegroundColor Cyan         } else {             Write-Host "Bucket not found: $UnblockBucket" -ForegroundColor Yellow             Write-Host "Dostupné sektory:" -PopredieFarebná sivá             $blocked. Kľúče | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }         }     } else {         Write-Host "Nenašiel sa žiadny blokovaný súbor sektorov." -ForegroundColor Yellow     }     Write-Host ""     ukončiť 0 }                          

if ($UnblockAll) {     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku         $count = $blocked. Počítať         @{} | ConvertTo-Json | Out-File $blockedBucketsPath -kódovanie UTF8 -Force         Write-Host "Unblocked all $count buckets." -ForegroundColor Green     } else {         Write-Host "Nenašiel sa žiadny blokovaný súbor sektorov." -ForegroundColor Yellow     }     Write-Host ""     ukončiť 0 }

# ============================================================================ # HELPER FUNCTIONS # ============================================================================

function Get-RolloutState {     if (testovacia cesta $rolloutStatePath) {         vyskúšať {             $loaded = Get-Content $rolloutStatePath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku             # Overiť existenciu požadovaných vlastností             if ($null -eq $loaded. CurrentWave) {                 hodiť "Neplatný súbor stavu - chýba CurrentWave"             }             # Zabezpečiť WaveHistory je vždy pole (opravy PS5.1 JSON deserialization)             if ($null -eq $loaded. WaveHistory) {                 $loaded. WaveHistory = @()             } elseif ($loaded. WaveHistory -isnot [pole]) {                 $loaded. WaveHistory = @($loaded. WaveHistory)             }             vrátenie $loaded         } chytiť {             Write-Log zistené poškodené RolloutState.json: $($_. Exception.Message)" "WARN"             Write-Log "Zálohovanie poškodeného súboru a začatie odznova" "WARN"             $backupPath = "$rolloutStatePath.corrupted.$(Get-Date -Format 'yyyyMdd-HHmmss')"             Move-Item $rolloutStatePath $backupPath -Force -ErrorAction SilentlyContinue         }     }     vrátiť @{         CurrentWave = 0         StartedAt = $null         LastAggregation = $null         TotalDevicesTargeted = 0         TotalDevicesUpdated = 0         Status = "NotStarted"         WaveHistory = @()     } }

function Save-RolloutState {     param($State)     $State | ConvertTo-Json -Hĺbka 10 | Out-File $rolloutStatePath -kódovanie UTF8 -Force }

function Get-WeekdayProjection {     <#     . PREHĽADU         Výpočet plánovaného dátumu dokončenia účtovania víkendov (bez pokroku v sobotu/nedeľu)     #>     param(         [int]$RemainingDevices,         [dvojitá]$DevicesPerDay,         [datetime]$StartDate = (Get-Date)     )     if ($DevicesPerDay -le 0 -or $RemainingDevices -le 0) {         vrátiť @{             ProjectedDate = $null             WorkingDaysNeeded = 0             CalendarDaysNeeded = 0         }     }     # Výpočet potrebných pracovných dní (okrem víkendov)     $workingDaysNeeded = [matematika]::Strop($RemainingDevices / $DevicesPerDay)     # Konvertovať pracovné dni na kalendárne dni (pridať víkendy)     $currentDate = $StartDate.Date     $daysAdded = 0     $workingDaysAdded = 0     zatiaľ čo ($workingDaysAdded -lt $workingDaysNeeded) {         $currentDate = $currentDate.AddDays(1)         $daysAdded + +         # Počítať iba pracovné dni         if ($currentDate.DayOfWeek -ne [DayOfWeek]::Saturday -and             $currentDate.DayOfWeek -ne [DayOfWeek]::Sunday) {             $workingDaysAdded+ +         }     }     vrátiť @{         ProjectedDate = $currentDate.ToString("rrrr-MM-dd")         WorkingDaysNeeded = $workingDaysNeeded         CalendarDaysNeeded = $daysAdded     } }                                  

function Save-RolloutSummary {     <#     . PREHĽADU         Uloženie súhrnu zavádzania s informáciami o projekcii na zobrazenie tabule     #>     param(         [hashtable]$State,         [int]$TotalDevices,         [int]$UpdatedDevices,         [int]$NotUpdatedDevices,         [double]$DevicesPerDay     )     $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     # Vypočítať projekciu založenú na víkende     $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay     $summary = @{         GeneratedAt = (Get-Date -Format "rrrr-MM-dd HH:mm:ss")         RolloutStartDate = $State.StartedAt         LastAggregation = $State.LastAggregation         CurrentWave = $State.CurrentWave         Stav = $State.Status         Počet zariadení         TotalDevices = $TotalDevices         UpdatedDevices = $UpdatedDevices         NotUpdatedDevices = $NotUpdatedDevices         PercentUpdated = if ($TotalDevices -gt 0) { [matematika]::Round(($UpdatedDevices/$TotalDevices) * 100, 1) } else { 0 }         # Metrika rýchlosti         DevicesPerDay = [matematika]::Round($DevicesPerDay; 1)         TotalDevicesTargeted = $State.TotalDevicesTargeted         TotalWaves = $State.CurrentWave         # Víkend-aware projekcie         ProjectedCompletionDate = $projection. ProjectedDate (PlánovanýDátum)         WorkingDaysRemaining = $projection. WorkingDaysNeeded         CalendarDaysRemaining = $projection. CalendarDaysNeeded         # Poznámka k vylúčeniu z víkendu         ProjectionNote = "Projected completion excludes weekends (Sat/Sun)"     }     $summary | ConvertTo-Json -Hĺbka 5 | Out-File $summaryPath -kódovanie UTF8 -Force     Write-Log "Rollout summary saved: $summaryPath" "INFO"     vrátenie $summary }                                                             

function Get-BlockedBuckets {     if (test-path $blockedBucketsPath) {         return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku     }     vrátiť @{} }

function Save-BlockedBuckets {     param($Blocked)     $Blocked | ConvertTo-Json -Hĺbka 10 | Out-File $blockedBucketsPath -kódovanie UTF8 -Force }

function Get-AdminApproved {     if (test-path $adminApprovedPath) {         return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku     }     vrátiť @{} }

function Get-DeviceHistory {     if (Testovacia cesta $deviceHistoryPath) {         return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | Konvertovať na hashovateľnú tabuľku     }     vrátiť @{} }

function Save-DeviceHistory {     param($History)     $History | ConvertTo-Json -Hĺbka 10 | Out-File $deviceHistoryPath -kódovanie UTF8 -Force }

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

    $checkpoint = @{         Fáza = $Stage         UpdatedAt = Get-Date -Format "rrrr-MM-dd HH:mm:ss"         Spracované = $Processed         Súčet = $Total         Percento = if ($Total -gt 0) { [matematika]::Round(($Processed/$Total) * 100, 2) } else { 0 }         Metrika = $Metrics     }

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

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

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

    foreach ($device in $Devices) {         $hostname = if ($device. Názov hostiteľa) { $device. Hostname } elseif ($device. HostName) { $device. HostName } else { $null }         ak ($hostname) {             [void]$hostSet.Add($hostname)         }

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

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

function Write-Log {     param([reťazec]$Message; [reťazec]$Level = "INFO")     $timestamp = Get-Date -Format "rrrr-MM-dd HH:mm:ss"     $color = prepínač ($Level) {         "OK" { "Zelená" }         "WARN" { "Yellow" }         "CHYBA" { "Červená" }         "ZABLOKOVANÉ" { "Tmavočervené" }         "WAVE" { "Azúrová" }         predvolená { "Biela" }     }     Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color     # Do súboru sa tiež zapíše     $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMDd').log"     "[$timestamp] [$Level] $Message" | Out-File $logFile -Append-Encoding UTF8 }               

function Get-BucketKey {     param($Device)     # Použiť BucketId zo zariadenia JSON, ak je k dispozícii (SHA256 hash z detekčného skriptu)     if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { return "$($Device.BucketId)" }     # Záložná chyba: konštrukcia od výrobcu|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 }     vrátenie položky "$mfr|$model|$bios" }

# ============================================================================ NAČÍTAVA SA ZOZNAM VIP/VYLÚČENIA # ============================================================================

function Get-ExcludedHostnames {     param(         [reťazec]$ExclusionFilePath,         [reťazec]$ADGroupName     )     $excluded = [System.Collections.Generic.HashSet[reťazec]]::new([StringComparer]::OrdinalIgnoreCase)     # Načítať zo súboru (podporuje .txt alebo .csv)     if ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) {         $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower()         if ($extension -eq ".csv") {             # FORMÁT CSV: očakáva stĺpec Hostname alebo ComputerName             $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 -obsahuje "Name") { 'Name' }                        else { $null }             ak ($hostCol) {                 foreach ($row in $csvData) {                     ak (![ reťazec]::IsNullOrWhiteSpace($row.$hostCol)) {                         [neplatné]$excluded. Add($row.$hostCol.Trim())                     }                 }             }         } else {             # Obyčajný text: jeden názov hostiteľa na riadok             Get-Content $ExclusionFilePath | ForEach-Object {                 $line = $_. Orezanie()                 if ($line -and-not $line. StartsWith('#')) {                     [neplatné]$excluded. Add($line)                 }             }         }         Write-Log "Loaded $($excluded. Count) hostnames from exclusion file: $ExclusionFilePath" "INFO"     }     # Načítať zo skupiny zabezpečenia služby AD     ak ($ADGroupName) {         vyskúšať {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Rekurzívna -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }             foreach ($member in $groupMembers) {                 [neplatné]$excluded. Add($member. Názov)             }             Write-Log "Loaded $($groupMembers.Count) computers from AD group: $ADGroupName" "INFO"         } chytiť {             Write-Log "Nepodarilo sa načítať skupinu AD "$ADGroupName": $_" "WARN"         }     }     return @($excluded) }                                                                             

# ============================================================================ # POVOLIŤ NAČÍTANIE ZOZNAMU (cielené zavádzanie) # ============================================================================

function Get-AllowedHostnames {     <#     . PREHĽADU         Načíta názvy hostiteľov zo súboru AllowList alebo skupiny AD na cielené zavedenie.Keď je zadaný parameter AllowList, do uvedenia sa zahrnú iba tieto zariadenia.#>     param(         [reťazec]$AllowFilePath,         [reťazec]$ADGroupName     )          $allowed = [System.Collections.Generic.HashSet[reťazec]]::new([StringComparer]::OrdinalIgnoreCase)          # Načítať zo súboru (podporuje .txt alebo .csv)     if ($AllowFilePath -and (Test-Path $AllowFilePath)) {         $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower()                  if ($extension -eq ".csv") {             # FORMÁT CSV: očakáva stĺpec Hostname alebo ComputerName             $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 }                                  ak ($hostCol) {                     foreach ($row in $csvData) {                         ak (![ reťazec]::IsNullOrWhiteSpace($row.$hostCol)) {                             [neplatné]$allowed. Add($row.$hostCol.Trim())                         }                     }                 }             }         } else {             # Obyčajný text: jeden názov hostiteľa na riadok             Get-Content $AllowFilePath | ForEach-Object {                 $line = $_. Orezanie()                 if ($line -and-not $line. StartsWith('#')) {                     [neplatné]$allowed. Add($line)                 }             }         }                  Write-Log "Loaded $($allowed. Počet) názvov hostiteľov zo súboru zoznamu povolených položiek: $AllowFilePath" "INFO"     }          # Načítať zo skupiny zabezpečenia služby AD     ak ($ADGroupName) {         vyskúšať {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Rekurzívna -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }                          foreach ($member in $groupMembers) {                 [neplatné]$allowed. Add($member. Názov)             }                          Write-Log "Loaded $($groupMembers.Count) computers from AD allow group: $ADGroupName" "INFO"         } chytiť {             Write-Log "Nepodarilo sa načítať skupinu AD "$ADGroupName": $_" "WARN"         }     }          vrátiť @($allowed) }

# ============================================================================ # AKTUÁLNOSŤ ÚDAJOV A MONITOROVANIE # ============================================================================

function Get-DataFreshness {     <#     . PREHĽADU         Skontroluje aktuálnu hodnotu údajov zisťovania preskúmaním časových pečiatk súborov JSON.Vráti štatistiku o tom, kedy boli koncové body naposledy nahlásené.#>     param([reťazec]$JsonPath)     $jsonFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue     if ($jsonFiles.Count -eq 0) {         vrátiť @{             TotalFiles = 0             FreshFiles = 0             StaleFiles = 0             NoDataFiles = 0             OldestFile = $null             NewestFile = $null             AvgAgeHours = 0             Warning = "Nenašli sa žiadne súbory JSON – zisťovanie nemusí byť nasadené"         }     }     $now = Get-Date     $freshThresholdHours = 24 # Files aktualizované za posledných 24 hodín sú "čerstvé"     $staleThresholdHours = 72 # Files staršie ako 72 hodín sú "zastarané"     $fresh = 0     $stale = 0     $ages = @()     foreach ($file in $jsonFiles) {         $ageHours = ($now - $file. LastWriteTime). Celkový počet hodin         $ages += $ageHours         if ($ageHours -le $freshThresholdHours) {             $fresh+ +         } elseif ($ageHours -ge $staleThresholdHours) {             $stale++         }     }     $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object –prvý 1     $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descending | Select-Object –prvý 1     $warning = $null     if ($stale -gt ($jsonFiles.Count * 0,5)) {         $warning = "Viac ako 50 % zariadení má zastarané údaje (>72 hodín) – kontrola zisťovania objektu GPO     } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) {         $warning = "Menej ako 30 % nedávno nahlásených zariadení – detekcia nemusí byť spustená"     }     vrátiť @{         TotalFiles = $jsonFiles.Count         FreshFiles = $fresh         StaleFiles = $stale         MediumFiles = $jsonFiles.Count - $fresh - $stale         OldestFile = $oldestFile.LastWriteTime         NewestFile = $newestFile.LastWriteTime         AvgAgeHours = [matematika]::Round(($ages | Measure-Object -Priemer). Priemer, 1)         Warning = $warning     } }                                                 

function Test-DetectionGPODeployed {     <#     . PREHĽADU         Overí, či je zavedená infraštruktúra zisťovania/monitorovania.#>     param([reťazec]$JsonPath)     # Kontrola 1: Cesta JSON existuje     if (-not (Test-Path $JsonPath)) {         vrátiť @{             IsDeployed = $false             Message = "Cesta vstupu JSON neexistuje: $JsonPath"         }     }     # Kontrola 2: Aspoň niektoré súbory JSON existujú     $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Počítať     if ($jsonCount -eq 0) {         vrátiť @{             IsDeployed = $false             Message = "No JSON files in $JsonPath - Detection GPO may not be deployed or devices haven not reported yet"         }     }     # Kontrola 3: Files sú primerane nedávne (aspoň niektoré z minulého týždňa)     $recentFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue |         Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) }     if ($recentFiles.Count -eq 0) {         vrátiť @{             IsDeployed = $false             Message = "No JSON files updated in last 7 days - Detection GPO may be broken or devices offline"         }     }     vrátiť @{         IsDeployed = $true         Message = "Detection appears active: $jsonCount files, $($recentFiles.Count) updated recently"         FileCount = $jsonCount         RecentCount = $recentFiles.Count     } }                         

# ============================================================================ # SLEDOVANIE ZARIADENÍ (PODĽA NÁZVU HOSTITEĽA) # ============================================================================

function Update-DeviceHistory {     <#     . PREHĽADU         Sleduje zariadenia podľa názvu hostiteľa, pretože nemáme jedinečný identifikátor počítača.Poznámka: BucketId je one-to-many (rovnaký hardvér config = rovnaký sektor).Ak sa do kolekcie JSON pridá jedinečný identifikátor, aktualizujte túto funkciu.#>     param(         [pole]$CurrentDevices,         [hashtable]$DeviceHistory     )          foreach ($device in $CurrentDevices) {         $hostname = $device. Hostname         if (-not $hostname) { continue }                  # Sledovať zariadenie podľa názvu hostiteľa         $DeviceHistory[$hostname] = @{             Názov hostiteľa = $hostname             BucketId = $device. BucketId             Výrobca = $device. WMI_Manufacturer             Model = $device. WMI_Model             LastSeen = Get-Date -Format "rrrr-MM-dd HH:mm:ss"             Stav = $device. Aktualizovať stav         }     } }

# ============================================================================ # BLOKOVANÁ DETEKCIA SEKTORA (na základe dostupnosti zariadenia) # ============================================================================

<# . POPIS / KONTROL     Logika blokovania:     - Sektor je blokovaný iba vtedy, ak:       1. Zariadenie bolo zacielené na vlnu       2. MaxWaitHours prešiel od začiatku vlny       3. Zariadenie nie je dostupné (ping zlyhá)          - Ak je zariadenie dostupné, ale zatiaľ neaktualizované, čakáme       (aktualizácia môže čakajúca na reštart – udalosť 1808 sa spustí len po reštarte)          - Nedostupné zariadenie označuje, že sa vyskytla chyba a vyžaduje skúmanie          Odblokovanie:     - Pomocou -ListBlockedBuckets vidieť blokované sektory     - Použitie -UnblockBucket "BucketKey" na odblokovanie konkrétneho sektora     - Použitie funkcie -UnblockAll na odblokovanie všetkých sektorov #>

function Test-DeviceReachable {     param(         [reťazec]$Hostname,         [reťazec]$DataPath # Cesta k súborom JSON zariadenia     )     # Metóda 1: Skontrolujte časovú pečiatku súboru JSON (najrýchlejšie – nie je potrebná analýza súborov)     # Ak sa skript zisťovania spustil nedávno, súbor bol napísaný/aktualizovaný, čím sa preukázalo, že zariadenie je nažive     ak ($DataPath) {         $deviceFile = Get-ChildItem -Path $DataPath -Filter "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object –prvý 1         ak ($deviceFile) {             $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). Celkový počet hodin             if ($hoursSinceWrite -lt 72) { return $true }         }     }     # Metóda 2: Záložné ping (iba v prípade, že JSON je zastaraný alebo chýba)     vyskúšať {         $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue         vrátenie $ping     } chytiť {         vrátenie $false     } }          

function Update-BlockedBuckets {     param(         $RolloutState,         $BlockedBuckets,         $AdminApproved,         [pole]$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 }     # Zhromažďovať zariadenia, ktoré sú po uplynutí doby čakania a stále sa neaktualizovali     foreach ($wave in $RolloutState.WaveHistory) {         ak (-nie $wave. StartedAt) { continue }         $waveStart = [DateTime]::P arse($wave. StartedAt)         $hoursSinceWave = ($now - $waveStart). Celkový počet hodin         if ($hoursSinceWave -lt $MaxWaitHours) {             # Stále v rámci obdobia čakania - ešte nekontrolujte             Pokračovať         }         # Skontrolujte každé zariadenie z tejto vlny         foreach ($deviceInfo in $wave. Zariadenia) {             $hostname = $deviceInfo.Hostname             $bucketKey = $deviceInfo.BucketKey             # Vynechať, ak je sektor už zablokovaný             if ($BlockedBuckets.Contains($bucketKey)) { continue }             # Preskočiť, ak je sektor admin-schválený A vlna začala pred schválením             # (kontrolovať iba zariadenia, ktoré sú zacielené po schválení správcom na opätovné blokovanie)             if ($AdminApproved -and $AdminApproved.Contains($bucketKey)) {                 $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. Schválené)                 ak ($waveStart -lt $approvalTime) {                     # Toto zariadenie bolo zacielené pred schválením správcom – vynechať                     Pokračovať                 }                 # Wave začal po schválení - to je čerstvé zacielenie, môže skontrolovať             }             # Je toto zariadenie stále v zozname NotUpdated?             if ($hostSet.Contains($hostname)) {                 $devicesToCheck += @{                     Názov hostiteľa = $hostname                     BucketKey = $bucketKey                     WaveNumber = $wave. WaveNumber (Číslo vlny)                     HoursSinceWave = [matematika]::Round($hoursSinceWave; 1)                 }             }         }     }     if ($devicesToCheck.Count -eq 0) {         vrátenie $newlyBlocked     }     Write-Log "Checking reachability of $($devicesToCheck.Count) devices past wait period..." (Kontrola dostupnosti zariadení $($devicesToCheck.Count) po uplynutí čakacej doby..." "INFORMÁCIE"     # Sledovať zlyhania na sektor pre rozhodovanie     $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Alive=@() }     # Kontrola dostupnosti každého zariadenia     foreach ($device in $devicesToCheck) {         $hostname = $device. Hostname         $bucketKey = $device. Kľúč sektora         ak ($DryRun) {             Write-Log "[DRYRUN] By skontrolovať $hostname dosah" "INFO"             Pokračovať         }         if (-not $bucketFailures.ContainsKey($bucketKey)) {             $bucketFailures[$bucketKey] = @{ Unreachable = @(); AliveButFailed = @(); WaveNumber = $device. WaveNumber; HoursSinceWave = $device. HoursSinceWave }         }         $isReachable = Test-DeviceReachable -Hostname $hostname -DataPath $AggregationInputPath         ak (-nie $isReachable) {             $bucketFailures[$bucketKey]. Nedosiahnuteľné += $hostname         } else {             # Zariadenie je dostupné, ale zatiaľ neaktualizované - môže to byť dočasné zlyhanie alebo čakanie na reštart             $bucketFailures[$bucketKey]. AliveButFailed += $hostname             $stillWaiting += $hostname         }     }     # Rozhodnutie na sektor: blokovať len v prípade, že zariadenia sú skutočne NEDOSIAHNUTEĽNÉ     # Živé zariadenia so zlyhaniami = dočasné, pokračujte v zavádzaní     foreach ($bucketKey in $bucketFailures.Keys) {         $bf = $bucketFailures[$bucketKey]         $unreachableCount = $bf. Nedosiahnuteľný.Počet         $aliveFailedCount = $bf. AliveButFailed.Count         # Skontrolujte, či tento sektor má nejaké úspechy (z aktualizovaných údajov o zariadeniach)         $bucketHasSuccesses = $stSuccessBuckets -and $stSuccessBuckets.Contains($bucketKey)         if ($unreachableCount -gt 0 -and $aliveFailedCount -eq 0) {             # VŠETKY zlyhávajúce zariadenia sú nedostupné - blokovať sektor             if ($newlyBlocked -notcontains $bucketKey) {                 $BlockedBuckets[$bucketKey] = @{                     BlockedAt = Get-Date -Format "rrrr-MM-dd HH:mm:ss"                     Reason = "All $unreachableCount device(s) unreachable after $($bf. HoursSinceWave) hours"                     FailedDevices = ($bf. Nedosiahnuteľné -join ", ")                     WaveNumber = $bf. WaveNumber (Číslo vlny)                     DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } else { 0 }                 }                 $newlyBlocked += $bucketKey                 Write-Log "BUCKET BLOCKED: $bucketKey ($unreachableCount zariadenia) nedostupné: $($bf. Nedosiahnuteľné -join ', '))" "ZABLOKOVANÉ"             }         } elseif ($aliveFailedCount -gt 0) {             # Zariadenia sú živé, ale neaktualizované - dočasné zlyhanie, NEBLOKOVAŤ             Write-Log "Bucket $($bucketKey.Substring(0, [Matematika]::Min(16, $bucketKey.Length))...: $aliveFailedCount zariadenia nažive, ale čakajúce, $unreachableCount nedosiahnuteľné - NEBLokujúce (dočasné)" "INFO"             if ($unreachableCount -gt 0) {                 Write-Log " Nedosiahnuteľné: $($bf. Nedosiahnuteľné -join ', ')" "WARN"             }             Write-Log " Alive but pending: $($bf. AliveButFailed -join ', ')" "INFO"             # Sledovať počet zlyhaní v stave uvedenia na monitorovanie             if (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} }             $RolloutState.TemporaryFailures[$bucketKey] = @{                 AliveButFailed = $bf. AliveButFailed                 Nedosiahnuteľné = $bf. Nedosiahnuteľný                 LastChecked = Get-Date -Format "rrrr-MM-dd HH:mm:ss"             }         }     }     if ($stillWaiting.Count -gt 0) {         Write-Log "Zariadenia dostupné, ale čakajúce na aktualizáciu (možno bude potrebné reštartovať): $($stillWaiting.Count)" "INFO"     }     vrátenie $newlyBlocked }                                                                                                                                                                                  

# ============================================================================ # AUTO-ODBLOKOVANIE: Odblokovanie sektorov pri úspešnej aktualizácii zariadení # ============================================================================

function Update-AutoUnblockedBuckets {     <#     . POPIS / KONTROL         Skontroluje, či sa zariadenia v blokovaných sektoroch aktualizovali (udalosť 1808).         Automatické odblokovanie, ak sa všetky cieľové zariadenia v sektore aktualizovali.Ak sa aktualizovali len niektoré zariadenia, upozorní správcu, ktorý ho môže manuálne odblokovať.                  Spravovanie môžete manuálne odblokovať pomocou:           .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "cesta" -UnblockBucket "BucketKey"     #>     param(         $BlockedBuckets,         $RolloutState,         [pole]$NotUpdatedDevices,         [reťazec]$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 ($bucketKey in $bucketsToCheck) {         $bucketInfo = $BlockedBuckets[$bucketKey]         # Získajte všetky zariadenia, na ktoré sme sa zamerali z tohto sektora v minulosti         $targetedDevicesInBucket = @()         foreach ($wave in $RolloutState.WaveHistory) {             $targetedDevicesInBucket += @($wave. Zariadenia | Where-Object { $_. BucketKey -eq $bucketKey })         }         if ($targetedDevicesInBucket.Count -eq 0) { continue }         # Skontrolujte, koľko vybraných zariadení je stále v neaktualizovaných a aktualizovaných         $updatedDevices = @()         $stillPendingDevices = @()         foreach ($targetedDevice in $targetedDevicesInBucket) {             if ($hostSet.Contains($targetedDevice.Hostname)) {                 $stillPendingDevices += $targetedDevice.Hostname             } else {                 $updatedDevices += $targetedDevice.Hostname             }         }         if ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -eq 0) {             # VŠETKY cielené zariadenia aktualizovali - auto-odblokovať!             $BlockedBuckets.Remove($bucketKey)             $autoUnblocked += @{                 BucketKey = $bucketKey                 UpdatedDevices = $updatedDevices                 PreviouslyBlockedAt = $bucketInfo.BlockedAt                 Reason = "Všetky cieľové zariadenia ($($updatedDevices.Count) sa úspešne aktualizovali"             }             Write-Log "AUTO-UNBLOCKED: $bucketKey (Všetky zariadenia so zacieleniami na $($updatedDevices.Count) sa úspešne aktualizovali) " "OK"             # Prírastkový počet vĺn OEM pre tento sektor OEM (sledovanie podľa OEM)             $bucketOEM = if ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } else { 'Neznáme' } # Extrahovať OEM z kľúča oddeleného potrubím alebo z predvoleného             if (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             $currentWave = if ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } else { 0 }             $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1             Write-Log počet vĺn OEM "$bucketOEM" sa zvyšuje na $($currentWave + 1) (nasledujúca alokácia: $([int][Matematika]::P ow(2, $currentWave + 1))) " "INFO"         }         elseif ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -gt 0) {             # Niektoré zariadenia sa aktualizovali, ale iné ešte čakajú – upozornite správcu (iba raz)             if (-not $bucketInfo.UnblockCandidate) {                 $bucketInfo.UnblockCandidate = $true                 $bucketInfo.UpdatedDevices = $updatedDevices                 $bucketInfo.PendingDevices = $stillPendingDevices                 $bucketInfo.NotifiedAt = (Get-Date). ToString("rrrr-MM-dd HH:mm:ss")                 Write-Log "" "INFO"                 Write-Log "========== ČIASTOČNÁ AKTUALIZÁCIA V ZABLOKOVANOM SEKTORE ==========" "INFO"                 Write-Log "Sektor: $bucketKey" "INFO"                 $updatedSample = @($updatedDevices | Select-Object –prvé $LogSampleSize)                 $pendingSample = @($stillPendingDevices | Select-Object -First $LogSampleSize)                 $updatedSuffix = if ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count - $LogSampleSize) viac)" } else { "" }                 $pendingSuffix = if ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count - $LogSampleSize) more)" } else { "" }                 Write-Log "Aktualizované zariadenia ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "OK"                 Write-Log "Stále čakajúce ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN"                 Write-Log "" "INFO"                 Write-Log "Ak chcete manuálne odblokovať tento sektor po overení, spustite:" "INFO"                 Write-Log " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'"" "INFO"                 Write-Log "=======================================================" "INFO"                 Write-Log "" "INFO"             }         }     }     vrátenie $autoUnblocked }                                                                                          

# ============================================================================ # WAVE GENERATION (INLINED - vylučuje blokované sektory) # ============================================================================

function New-RolloutWave {     param(         [reťazec]$AggregationPath,         $BlockedBuckets,         $RolloutState,         [int]$MaxDevicesPerWave = 50,         [reťazec[]]$AllowedHostnames = @(),         [reťazec[]]$ExcludedHostnames = @()     )     # Načítať údaje agregácie     $notUptodateCsv = Get-ChildItem -Path $AggregationPath -Filter "*NotUptodate*.csv" |          Where-Object { $_. Názov -notlike "*Sektory*" } |          Sort-Object LastWriteTime -Descending |          Select-Object – prvá 1     ak (-nie $notUptodateCsv) {         Write-Log "Nenašla sa žiadna notuptodate CSV" "CHYBA"         vrátenie $null     }     $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName)     # Normalizovať HostName -> Názov hostiteľa pre konzistenciu (CSV používa HostName, kód používa Hostname)     foreach ($device in $allNotUpdated) {         ak ($device. PSObject.Properties['HostName'] - a -not $device. PSObject.Properties['Hostname']) {             $device | Add-Member -NotePropertyName 'Hostname' -NotePropertyValue $device. HostName -Force         }     }     # Odfiltrovať blokované sektory     $eligibleDevices = @($allNotUpdated | Where-Object {         $bucketKey = Get-BucketKey $_         -not $BlockedBuckets.Contains($bucketKey)     })     # Filter iba povolených zariadení (ak je určená AllowList)     # AllowList = cielené zavádzanie - len tieto zariadenia budú považované za     if ($AllowedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Názov hostiteľa – v $AllowedHostnames         })         $allowedCount = $eligibleDevices.Count         Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO"     }     # Odfiltrovať VIP/vylúčené zariadenia (BlockList)     # BlockList sa použije PO AllowList     if ($ExcludedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Hostname -notin $ExcludedHostnames         })         $excludedCount = $beforeCount - $eligibleDevices.Count         if ($excludedCount -gt 0) {             Write-Log "Excluded $excludedCount VIP/protected devices from rollout" "INFO"         }     }     if ($eligibleDevices.Count -eq 0) {         Write-Log "No eligible devices remaining (all updated or blocked)" "OK"         vrátenie $null     }     # Získať zariadenia už v zavádzanie (z predchádzajúcich vĺn)     $devicesAlreadyInRollout = @()     if ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) {         $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object {             $_. Zariadenia | ForEach-Object { $_. Názov hostiteľa }         } | Where-Object { $_ })     }     Write-Log "Zariadenia, ktoré sa už zavádzajú: $($devicesAlreadyInRollout.Count)" "INFO"     # Oddelené úrovňou spoľahlivosti     $highConfidenceDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -eq "Vysoká spoľahlivosť" - a         $_. Hostname -notin $devicesAlreadyInRollout     })     # Požadovaná akcia zahŕňa:     # - Explicitné "Vyžaduje sa akcia"     # - Empty/null ConfidenceLevel     # - ĽUBOVOĽNÁ neznáma alebo nerozpoznaná hodnota úrovne spoľahlivosti (považuje sa za požadovanú akciu)     $knownSafeCategories = @(         "Vysoká spoľahlivosť",         "Dočasne pozastavené",         "Pod dohľadom",         "Pod dohľadom - viac údajov potrebných",         Nepodporované,         Nepodporované – známe obmedzenie     )     $actionRequiredDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -notin $knownSafeCategories -and         $_. Hostname -notin $devicesAlreadyInRollout     })     Write-Log "High Confidence (not in rollout): $($highConfidenceDevices.Count)" "INFO"     Write-Log "Vyžaduje sa akcia (nie je v uvedenom programe): $($actionRequiredDevices.Count)" "INFO"     # Zostavte zariadenia s vlnami     $waveDevices = @()     # VYSOKÁ SPOĽAHLIVOSŤ: Zahrnúť VŠETKY (bezpečné pre zavedenie)     if ($highConfidenceDevices.Count -gt 0) {         Write-Log "Adding all $($highConfidenceDevices.Count) High Confidence devices" "WAVE"         $waveDevices += $highConfidenceDevices     } # VYŽADUJE SA AKCIA: Postupné zavádzanie (založené na kontajneri s rozšírením OEM pre sektory s nulovým úspechom)     # Stratégia:     # - Sektory s 0 úspechmi: Spread v rámci OEM (1 na OEM -> 2 na OEM -> 4 na OEM)     # - Sektory s úspechom ≥1: Dvojitá voľne bez obmedzenia OEM     if ($actionRequiredDevices.Count -gt 0) {         # Počet úspešných načítania kontajnerov z aktualizovaných zariadení CSV (zariadenia, ktoré sa úspešne aktualizovali)         $updatedCsv = Get-ChildItem -Path $AggregationPath -Filter "*updated_devices*.csv" |             Sort-Object LastWriteTime -Descending | Select-Object –prvý 1         $bucketStats = @{}         ak ($updatedCsv) {             $updatedDevices = Import-Csv $updatedCsv.FullName             Počet úspešných položiek podľa kontajnera BucketId             $updatedDevices | ForEach-Object {                 $key = Get-BucketKey $_                 ak ($key) {                     if (-not $bucketStats.ContainsKey($key)) {                         $bucketStats[$key] = @{ Successes = 0; Čakajúce = 0; Spolu = 0 }                     }                     $bucketStats[$key]. Úspechy+ +                     $bucketStats[$key]. Total++                 }             }             Write-Log "Loaded $($updatedDevices.Count) updated devices across $($bucketStats.Count) buckets" "INFO"         } else {             # Fallback: skúste ActionRequired_Buckets CSV             $bucketsCsv = Get-ChildItem -Path $AggregationPath -Filter "*ActionRequired_Buckets*.csv" |                 Sort-Object LastWriteTime -Descending | Select-Object –prvý 1             ak ($bucketsCsv) {                 Import-Csv $bucketsCsv.FullName | ForEach-Object {                     $key = if ($_. BucketId) { $_. BucketId } else { "$($_. Výrobca)|$($_. Model)|$($_. BIOS)" }                     $bucketStats[$key] = @{                         Úspechy = [int]$_. Úspechy                         Čakajúce = [int]$_. Čakajúce                         Celkový súčet = [int]$_. TotalDevices                     }                 }             }         }         # Zoskupovať zariadenia s neakuplikovanými položkami podľa sektora (výrobca|Model |BIOS)         $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ }         # Samostatné sektory: nula-úspech vs má-úspech         $zeroSuccessBuckets = @()         $hasSuccessBuckets = @()         foreach ($bucket in $buckets) {             $bucketKey = $bucket. Meno             $bucketDevices = @($bucket. Skupina)             $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Názov hostiteľa })             Počet úspešných položiek v tomto sektore             $stats = $bucketStats[$bucketKey]             $successes = if ($stats) { $stats. Successes } else { 0 }             # Nájsť zariadenia nasadené do tohto sektora z histórie vĺn             $deployedToBucket = @()             foreach ($wave in $RolloutState.WaveHistory) {                 foreach ($device in $wave. Zariadenia) {                     ak ($device. BucketKey -eq $bucketKey -a $device. Názov hostiteľa) {                         $deployedToBucket += $device. Hostname                     }                 }             }             $deployedToBucket = @($deployedToBucket | Sort-Object -Unique)             # Skontrolujte, či všetky nasadené zariadenia ohlásili úspech             $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames })             $confirmedSuccess = $deployedToBucket.Count - $stillPending.Count             # Ak čakáte, preskočte tento sektor, kým sa všetky potvrďte             if ($stillPending.Count -gt 0) {                 $parts = $bucketKey -split '\|'                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " Bucket: $displayName - Deployed=$($deployedToBucket.Count), Confirmed=$confirmedSuccess, Pending=$($stillPending.Count) (čakanie)" "INFO"                 Pokračovať             }             Počet zostávajúcich oprávnených zariadení = zariadenia, ktoré ešte nie sú nasadené             $devicesNotYetTargeted = @($bucketDevices | Where-Object {                 $_. Hostname -notin $deployedToBucket             })             if ($devicesNotYetTargeted.Count -eq 0) { continue }             # Kategorizovať podľa počtu úspechov             $bucketInfo = @{                 BucketKey = $bucketKey                 Zariadenia = $devicesNotYetTargeted                 ConfirmedSuccess = $confirmedSuccess                 Successes = $successes                 OEM = if ($bucket. Skupina[0]. WMI_Manufacturer) { $bucket. Skupina[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } else { 'Neznáme' }             }             if ($successes -eq 0) {                 $zeroSuccessBuckets += $bucketInfo             } else {                 $hasSuccessBuckets += $bucketInfo             }         }         # === KONTAJNERY PROCESS HAS-SUCCESS (úspech ≥1) ===         # Dvojnásobný počet úspešných pokusov – ak 14 úspešných, nasaďte ďalších 28         foreach ($bucketInfo in $hasSuccessBuckets) {             $nextBatchSize = $bucketInfo.Successes * 2             $nextBatchSize = [Matematika]::Min($nextBatchSize; $MaxDevicesPerWave)             $nextBatchSize = [Matematika]::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, [Matematika]::Min(12, $bucketInfo.BucketKey.Length))) }                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x potvrdené)" "INFO"             }         }         # === KONTAJNERY PROCESS ZERO-SUCCESS (rozložené v rámci OEM so sledovaním podľa OEM) ===         # Cieľ: Rozloženie rizika v rôznych OEM, nezávislé sledovanie priebehu podľa OEM         # Každý OEM postupuje na základe vlastnej histórie úspechov:         # - OEM s úspechmi: Dostane ďalšie zariadenia ďalšiu vlnu (2^waveCount)         # - OEM bez úspechov: Zostáva na aktuálnej úrovni, kým sa nepotvrdí úspech         if ($zeroSuccessBuckets.Count -gt 0) {             # Inicializovať počet vĺn podľa OEM, ak neexistuje             if (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             # Zoskupenie sektorov s nulovým úspechom podľa OEM             $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OEM }             $totalZeroSuccessAdded = 0             $oemsDeployedTo = @()             foreach ($oemGroup in $oemBuckets) {                 $oemName = $oemGroup.Name                 # Získať tento počet vĺn OEM (začína na 0)                 $oemWaveCount = if ($RolloutState.OEMWaveCounts[$oemName]) {                     $RolloutState.OEMWaveCounts[$oemName]                 } else { 0 }                 # Vypočítať zariadenia pre tento OEM: 2^waveCount (1, 2, 4, 8...)                 $devicesForThisOEM = [int][Matematika]::P ow(2, $oemWaveCount)                 $devicesForThisOEM = [Matematika]::Max(1; $devicesForThisOEM)                 $oemDevicesAdded = 0                 # Vybrať z každého sektora v rámci tohto OEM                 foreach ($bucketInfo in $oemGroup.Group) {                     $remaining = $devicesForThisOEM – $oemDevicesAdded                     if ($remaining -le 0) { break }                     $toTake = [Matematika]::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, [Matematika]::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 - Wave $oemWaveCount, Added $oemDevicesAdded devices" "INFO"                     $oemsDeployedTo += $oemName                 }             }             # Sledujte, do ktorých OEM sme nasadili (na zvýšenie pri ďalšej kontrole úspešnosti)             if ($oemsDeployedTo.Count -gt 0) {                 $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo                 Write-Log "Zero-success deployment: $totalZeroSuccessAdded devices across $($oemsDeployedTo.Count) OEMs" "INFO"             }         }     }     if (@($waveDevices). Počet -eq 0) {         vrátenie $null     }     vrátenie $waveDevices }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

# ============================================================================ # NASADENIE OBJEKTU GPO (INLINED – vytvára objekt GPO, skupinu zabezpečenia, prepojenia) # ============================================================================

function Deploy-GPOForWave {     param(         [reťazec]$GPOName,         [reťazec]$TargetOU,         [reťazec]$SecurityGroupName,         [pole]$WaveHostnames,         [bool]$DryRun = $false     )     # Politika ADMX: SecureBoot.admx – SecureBoot_AvailableUpdatesPolicy     # Cesta k databáze Registry: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot     # Názov hodnoty: AvailableUpdatesPolicy     # Povolená hodnota: 22852 (0x5944) - Aktualizovať všetky kľúče zabezpečeného spustenia + bootmgr     # Zakázaná hodnota: 0     #     # Použitie skupinová politika Preferences (GPP) pre spoľahlivé nasadenie HKLM\SYSTEM path     # GPP vytvorí nastavenia v časti: Konfigurácia počítača > Predvoľby > Nastavenia systému Windows > Registry     $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot"     $RegistryValueName = "AvailableUpdatesPolicy"     $RegistryValue = 22852 # 0x5944 - zodpovedá hodnote ADMX enabledValue     Write-Log "Nasadenie objektu GPO: $GPOName" WAVE     Write-Log "Registry: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X')))" "INFO"     ak ($DryRun) {         Write-Log "[DRYRUN] Vytvorí objekt GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] By vytvoriť skupinu zabezpečenia: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Would add $(@($WaveHostnames). Počet) počítačov na zoskupenie" "INFO"         Write-Log "[DRYRUN] Prepája objekt GPO s: $TargetOU" "INFO"         vrátenie $true     }     vyskúšať {         # Importovanie požadovaných modulov         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } chytiť {         Write-Log "Nepodarilo sa importovať požadované moduly (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         vrátenie $false     }     # Krok 1: Vytvorenie alebo získanie objektu GPO     $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     ak ($existingGPO) {         Write-Log Objekt GPO už existuje: $GPOName INFO         $gpo = $existingGPO     } else {         vyskúšať {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Created GPO: $GPOName" "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť objekt GPO: $($_. Exception.Message)" "ERROR"             vrátenie $false         }     }     # Krok 2: Nastavenie hodnoty databázy Registry pomocou skupinová politika Preferences (GPP)     # GPP je spoľahlivejší pre HKLM\SYSTEM cesty ako Set-GPRegistryValue     vyskúšať {         # Najskôr sa pokúste odstrániť všetky existujúce preferencie pre túto hodnotu (aby sa zabránilo duplicitám)         Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue         # Vytvoriť predvoľbu GPP databázy Registry s akciou Nahradiť         # Nahradiť = Vytvoriť, ak neexistuje, Aktualizovať, ak existuje (najspoľahlivejšie)         # Aktualizovať = Aktualizovať iba v prípade, že existuje (zlyhá, ak hodnota neexistuje)         Set-GPPrefRegistryValue -Name $GPOName '             -Context Computer '             -Action Replace '             -Key $RegistryKey '             -ValueName $RegistryValueName '             -Typ DWord '             -Value $RegistryValue         Write-Log "Configured GPP Registry preference: $RegistryValueName = 0x5944 (Action=Replace)" "OK"     } chytiť {         Write-Log "GPP zlyhalo, skúste Set-GPRegistryValue: $($_. Exception.Message)" "WARN"         # Záložné Set-GPRegistryValue (funguje, ak je nasadený ADMX)         vyskúšať {             Set-GPRegistryValue -Name $GPOName '                 -Key $RegistryKey '                 -ValueName $RegistryValueName '                 -Typ DWord '                 -Hodnota $RegistryValue             Write-Log "Configured Registry via Set-GPRegistryValue: $RegistryValueName = 0x5944" "OK"         } chytiť {             Write-Log "Nepodarilo sa nastaviť hodnotu databázy Registry: $($_. Exception.Message)" "ERROR"             vrátenie $false         }     }     # Krok 3: Vytvorenie alebo získanie skupiny zabezpečenia     $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $existingGroup) {         vyskúšať {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Popis "Počítače zamerané na zavedenie zabezpečeného spustenia - $GPOName" '                 -PassThru             Write-Log "Created security group: $SecurityGroupName" "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť skupinu zabezpečenia: $($_. Exception.Message)" "ERROR"             vrátenie $false         }     } else {         Write-Log "Skupina zabezpečenia existuje: $SecurityGroupName" "INFO"         $group = $existingGroup     }     # Krok 4: Pridanie počítačov do skupiny zabezpečenia     $added = 0     $failed = 0     foreach ($hostname in $WaveHostnames) {         vyskúšať {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } chytiť {             $failed++         }     }     Write-Log "Added $added computers to security group ($failed not found in AD)" "OK"     # Krok 5: Konfigurácia filtrovania zabezpečenia v objekte GPO     vyskúšať {         # Odstrániť predvolené povolenie Použiť overených používateľov (zachovať čítanie)         Set-GPPermission -Name $GPOName -TargetName "Overení používatelia" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         # Pridať povolenie Použiť pre našu skupinu zabezpečenia         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Nakonfigurované filtrovanie zabezpečenia pre: $SecurityGroupName" "OK"     } chytiť {         Write-Log "Nepodarilo sa nakonfigurovať filtrovanie zabezpečenia: $($_. Exception.Message)" "WARN"         Write-Log "OBJEKT GPO sa môže vzťahovať na všetky počítače v prepojenom OU - overenie manuálne" "WARN"     }     # Krok 6: Prepojenie objektu GPO s OU (dôležité pre použitie politiky)     ak ($TargetOU) {         vyskúšať {             $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 "Linked GPO to: $TargetOU" "OK"                 Write-Log "GPO sa použije v ďalšom gpupdate na cieľových počítačoch" "INFO"             } else {                 Write-Log "OBJEKT GPO už prepojený s cieľovou OU" "INFO"             }         } chytiť {             Write-Log "DÔLEŽITÉ: Nepodarilo sa prepojiť objekt GPO s OU: $($_. Exception.Message)" "ERROR"             Write-Log "OBJEKT GPO bol vytvorený, ale NIE PREPOJENÝ - nebude sa vzťahovať na žiadne počítače!" "CHYBA"             Write-Log "Vyžaduje sa manuálna oprava: New-GPLink -Name '$GPOName' -Target '$TargetOU' -LinkEnabled Yes" "ERROR"             vrátenie $false         }     } else {         Write-Log Upozornenie: Nie je zadaný žiadny TargetOU - objekt GPO vytvorený, ale NIE PREPOJENÝ! "CHYBA"         Write-Log "Manuálne prepojenie požadované pre objekt GPO sa prejaví" "ERROR"         Write-Log "Run: New-GPLink -Name '$GPOName' -Target '<Your-Domain-DN>' -LinkEnabled Yes" "ERROR"     }     # Krok 7: Overenie konfigurácie objektu GPO     Write-Log "Overuje sa konfigurácia objektu GPO..." "INFORMÁCIE"     vyskúšať {         $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop         Write-Log "GpO Status: $($gpoReport.GpoStatus)" "INFO"         # Skontrolujte, či je nastavenie databázy Registry nakonfigurované         $regSettings = Get-GPRegistryValue -Name $GPOName -Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue         ak (-nie $regSettings) {             # Skúste kontrolu GPP databázy Registry (iná cesta v objekte GPO)             Write-Log "Checking GPP Registry preferences..." (Kontrola predvolieb GPP databázy Registry... "INFORMÁCIE"         }     } chytiť {         Write-Log "Nepodarilo sa overiť objekt GPO: $($_. Exception.Message)" "WARN"     }     vrátenie $true }                                                                                                

# ============================================================================ # WINCS NASADENIE (Alternatíva k AvailableUpdatesPolicy GPO) # ============================================================================ # Referencia: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe # # Príkazy WinCS (spustené v koncovom bode v kontexte SYSTEM): # Dotaz: WinCsFlags.exe /query --key F33E0C8E002 # Použiť: WinCsFlags.exe /apply --key "F33E0C8E002" # Reset: WinCsFlags.exe /reset --key "F33E0C8E002" # # Táto metóda nasadzuje objekt GPO s naplánovanou úlohou, ktorá sa spustí WinCsFlags.exe /apply # ako SYSTÉM pre cieľové koncové body. Podobne ako pri nasadzovaní detekčného skriptu, # ale beží raz (pri spustení) namiesto denne.

function Deploy-WinCSGPOForWave {     <#     . PREHĽADU         Nasadenie povolenia zabezpečeného spustenia WinCS prostredníctvom naplánovanej úlohy OBJEKTU GPO.. POPIS / KONTROL         Vytvorí objekt GPO, ktorý nasadzuje naplánovanú úlohu na spustenie WinCsFlags.exe /apply         v časti SYSTEM kontext pri spustení počítača. Zameranie na ovládacie prvky skupiny zabezpečenia.. PARAMETER GPOName         Názov objektu GPO.. PARAMETER TargetOU         OU prepojiť objekt GPO.. PARAMETER SecurityGroupName         Skupina zabezpečenia na filtrovanie objektu GPO.. Parametre WaveHostnames         Názvy hostiteľov, ktoré sa majú pridať do skupiny zabezpečenia.. PARAMETER WinCSKey         Použije sa kláves WinCS (predvolené nastavenie: F33E0C8E002).. PARAMETER DryRun         Ak je to pravda, zapíšte do denníka len to, čo sa má urobiť.#>     param(         [Parameter(Povinné = $true)]         [reťazec]$GPOName,                  [Parameter(Povinné = $false)]         [reťazec]$TargetOU,                  [Parameter(Povinné = $true)]         [reťazec]$SecurityGroupName,                  [Parameter(Povinné = $true)]         [pole]$WaveHostnames,                  [Parameter(Povinné = $false)]         [reťazec]$WinCSKey = "F33E0C8E002",                  [Parameter(Povinné = $false)]         [bool]$DryRun = $false     )          # Konfigurácia naplánovanej úlohy pre WinCsFlags.exe     $TaskName = "SecureBoot-WinCS-Apply"     $TaskPath = "\Microsoft\Windows\SecureBoot\"     $TaskDescription = "Použije konfiguráciu zabezpečeného spustenia cez WinCS - kľúč: $WinCSKey"          Write-Log "Nasadenie WinCS GPO: $GPOName" "WAVE"     Write-Log "Úloha sa spustí: WinCsFlags.exe /apply --key ""$WinCSKey"" "INFO"     Write-Log "Trigger: At system startup (runs once as SYSTEM)" "INFO"          ak ($DryRun) {         Write-Log "[DRYRUN] By vytvoriť OBJEKT GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] By vytvoriť skupinu zabezpečenia: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Would add $(@($WaveHostnames). Počet) počítačov na zoskupenie" "INFO"         Write-Log "[DRYRUN] By nasadenie plánovanej úlohy: $TaskName" "INFO"         Write-Log "[DRYRUN] Prepája objekt GPO s: $TargetOU" "INFO"         vrátiť @{             Úspech = $true             GPOCreated = $false             GroupCreated = $false             ComputersAdded = 0         }     }          vyskúšať {         # Importovanie požadovaných modulov         Import-Module GroupPolicy – zastavenie erroraction         Import-Module ActiveDirectory -ErrorAction Stop     } chytiť {         Write-Log "Nepodarilo sa importovať požadované moduly (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Chyba = $_. Exception.Message }     }          # Krok 1: Vytvorenie alebo získanie objektu GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     ak ($gpo) {         Write-Log objekt GPO už existuje: $GPOName INFO     } else {         vyskúšať {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'rrrr-MM-dd HH:mm')"             Write-Log "Created GPO: $GPOName" "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť objekt GPO: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = $_. Exception.Message }         }     }          # Krok 2: Vytvorenie xml naplánovanej úlohy pre nasadenie objektu GPO     # Tým sa vytvorí úloha, ktorá sa spustí WinCsFlags.exe /apply pri spustení     $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">   <RegistrationInfo>     <>$TaskDescription<popisu /> popisu     WinCsFlags.exe1 Author>SYSTEM</Author>   WinCsFlags.exe5 /RegistrationInfo>  >spúšťačov WinCsFlags.exe7     WinCsFlags.exe9 BootTrigger>       <Povolené>true</Enabled>       <</Oneskorenie>PT5M>     </BootTrigger>   </Triggers>  ><principals     <principal id="Author">       <UserId>S-1-5-18</UserId>       <></RunLevel>     </Principal>   </Principals>   <nastavenia>     <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>     <Povolené>pravda</Povolené>     <Skryté></Skryté>     <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 Priority>7</Priority>   WinCsFlags.exe27 /Settings>   WinCsFlags.exe29 Actions Context="Author"WinCsFlags.exe30     WinCsFlags.exe31 Exec>       WinCsFlags.exe33 command>WinCsFlags.exe</Command>       WinCsFlags.exe37 argumenty>/apply --key "$WinCSKey"WinCsFlags.exe39 /Argumenty>     WinCsFlags.exe41 /Exec>   WinCsFlags.exe43 /Actions> WinCsFlags.exe45 /Task> " @

    # Step 3: Deploy scheduled task via GPO Preferences     # Uložiť úlohu XML V SYSVOL pre gpo plánované úlohy okamžité úlohy     vyskúšať {         $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 | Hodnota out-null         }         # Vytvoriť ScheduledTasks.xml pre GPP         $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 'rrrr-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U">       <Task version="1.3">         <RegistrationInfo>           <popis>$TaskDescription</popis>         </RegistrationInfo>         <principals>           <principal id="Author">             <UserId>NT AUTHORITY\System</UserId>             <LogonType>S4U</LogonType>             <</RunLevel>HighestAvailable>           </Principal>         </Principals>        >nastavení <          ><IdleSettings             <Trvanie>PT5M</Trvanie>             <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>           <povolené>true</Enabled>           <Skryté>false</Skryté>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <Priority>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </Settings>        >spúšťačov <           <>TimeTrigger             <StartBoundary>$(Get-Date -Format 'rrrr-MM-dd')T00:00:00</StartBoundary>             <povolené>true</Enabled>           </TimeTrigger>         </Triggers>         <akcie>           <Exec>             <Command>WinCsFlags.exe</Command>             <argumenty>/apply --key "$WinCSKey"</Argumenty>           </Exec>         </Actions>       </Task>    ></Properties  ></ImmediateTaskV2 </ScheduledTasks> "@         $gppTaskXml | Out-File -FilePath (Join-Path $sysvolPath "ScheduledTasks.xml") -kódovanie UTF8 -Force         Write-Log "Deployed scheduled task to GPO: $TaskName" "OK"     } chytiť {         Write-Log "Nepodarilo sa nasadiť súbor XML naplánovanej úlohy: $($_. Exception.Message)" "WARN"         Write-Log "Falling back to registry-based WinCS deployment" "INFO"         # Záložná: Použitie prístupu databázy Registry WinCS v prípade zlyhania naplánovanej úlohy GPP         # WinCS možno spustiť aj prostredníctvom kľúča databázy Registry         # (Implementácia závisí od rozhrania API databázy Registry WinCS, ak je k dispozícii)     }     # Krok 4: Vytvorenie alebo získanie skupiny zabezpečenia     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     ak (-nie $group) {         vyskúšať {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Popis "Počítače zamerané na spustenie Secure Boot WinCS zavádzanie - $GPOName" '                 -PassThru             Write-Log "Created security group: $SecurityGroupName" (Vytvorená skupina zabezpečenia: $SecurityGroupName) "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť skupinu zabezpečenia: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = $_. Exception.Message }         }     } else {         Write-Log "Skupina zabezpečenia existuje: $SecurityGroupName" "INFO"     }     # Krok 5: Pridanie počítačov do skupiny zabezpečenia     $added = 0     $failed = 0     foreach ($hostname in $WaveHostnames) {         vyskúšať {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } chytiť {             $failed+ +         }     }     Write-Log "Added $added computers to security group ($failed not found in AD)" "OK"     # Krok 6: Konfigurácia filtrovania zabezpečenia v objekte GPO     vyskúšať {         Set-GPPermission -Name $GPOName -TargetName "Overení používatelia" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Nakonfigurované filtrovanie zabezpečenia pre: $SecurityGroupName" "OK"     } chytiť {         Write-Log "Nepodarilo sa nakonfigurovať filtrovanie zabezpečenia: $($_. Exception.Message)" "WARN"     }     # Krok 7: Prepojenie objektu GPO s OU     ak ($TargetOU) {         vyskúšať {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             ak (-nie $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Linked GPO to: $TargetOU" "OK"             } else {                 Write-Log "GPO už prepojené s cieľovou OU" "INFO"             }         } chytiť {             Write-Log "DÔLEŽITÉ: Nepodarilo sa prepojiť objekt GPO s OU: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = "Prepojenie OBJEKTU GPO zlyhalo: $($_. Exception.Message)" }         }     }     Write-Log "WinCS GPO nasadenie dokončené" "OK"     Write-Log "Stroje sa spustia WinCsFlags.exe pri ďalšom obnovení gpo + reštart / spustenie" "INFO"     vrátiť @{         Success = $true         GPOCreated = $true         GroupCreated = $true         ComputersAdded = $added         ComputersFailed = $failed     } }                                                                                        

# Wrapper function to maintain compatibility with main loop Deploy-WinCSForWave funkcie {     param(         [Parameter(Povinné = $true)]         [pole]$WaveHostnames,         [Parameter(Povinné = $false)]         [reťazec]$WinCSKey = "F33E0C8E002",         [Parameter(Povinné = $false)]         [reťazec]$WavePrefix = "SecureBoot-Rollout",         [Parameter(Povinné = $false)]         [int]$WaveNumber = 1,         [Parameter(Povinné = $false)]         [reťazec]$TargetOU,         [Parameter(Povinné = $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     # Konvertovať na očakávaný formát návratu     vrátiť @{         Success = $result. Úspech         Použité = $result. ComputersAdded (PočítačePridané)         Vynechané = 0         Zlyhalo = if ($result. ComputersFailed) { $result. ComputersFailed } else { 0 }         Results = @()     } }                                                            

# ============================================================================ # POVOLIŤ NASADENIE ÚLOH # ============================================================================ # Nasadiť Enable-SecureBootUpdateTask.ps1 do zariadení s vypnutou naplánovanou úlohou.# Používa objekt GPO s okamžitou plánovanou úlohou, ktorá sa spustí raz.

function Deploy-EnableTaskGPO {     <#     . PREHĽADU         Nasadenie Enable-SecureBootUpdateTask.ps1 prostredníctvom naplánovanej úlohy objektu GPO.. POPIS / KONTROL         Vytvorí objekt GPO, ktorý nasadzuje jednorazovo naplánovanú úlohu na povolenie         Naplánovaná úloha secure-boot-update v cieľových zariadeniach.. PARAMETER TargetOU         OU prepojiť objekt GPO.. PARAMETER TargetHostnames         Názvy hostiteľov zariadení so zakázanou úlohou (zo zostavy agregácie).. PARAMETER DryRun         Ak je to pravda, zapíšte do denníka len to, čo sa má urobiť.#>     param(         [Parameter(Povinné = $false)]         [reťazec]$TargetOU,                  [Parameter(Povinné = $true)]         [pole]$TargetHostnames,                  [Parameter(Povinné = $false)]         [bool]$DryRun = $false     )          $GPOName = SecureBoot-EnableTask-Remediation     $SecurityGroupName = "SecureBoot-EnableTask-Devices"     $TaskName = SecureBoot-EnableTask-OneTime     $TaskDescription = "Jednorazová úloha na povolenie naplánovanej úlohy secure-boot-update"          Write-Log "=" * 70 "INFO"     Write-Log "DEPLOYING ENABLE TASK REMEDIATION" "INFO"     Write-Log "=" * 70 "INFO"     Write-Log "Cieľové zariadenia: $($TargetHostnames.Count)" "INFO"     Write-Log "GPO: $GPOName" "INFO"     Write-Log "Skupina zabezpečenia: $SecurityGroupName" "INFO"          ak ($DryRun) {         Write-Log "[DRYRUN] Vytvorí objekt GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] By vytvoriť skupinu zabezpečenia: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Would add $($TargetHostnames.Count) computers to group" "INFO"         Write-Log "[DRYRUN] by nasadiť jednorazovo naplánovanú úlohu na povolenie Secure-Boot-Update" "INFO"         Write-Log "[DRYRUN] Would link GPO to: $TargetOU" "INFO"         vrátiť @{             Success = $true             ComputersAdded = 0             DryRun = $true         }     }          vyskúšať {         # Importovanie požadovaných modulov         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } chytiť {         Write-Log "Nepodarilo sa importovať požadované moduly: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Chyba = $_. Exception.Message }     }          # Krok 1: Vytvorenie alebo získanie objektu GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     ak ($gpo) {         Write-Log objekt GPO už existuje: $GPOName INFO     } else {         vyskúšať {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Task Enable Remediation - Created $(Get-Date -Format 'rrrr-MM-dd HH:mm')"             Write-Log "Created GPO: $GPOName" "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť objekt GPO: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = $_. Exception.Message }         }     }          # Krok 2: Nasadenie xml naplánovanej úlohy gpo SYSVOL     # Úloha spustí príkaz prostredia PowerShell na povolenie úlohy Secure-Boot-Update     vyskúšať {         $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks"                  if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Directory -Path $sysvolPath -Force | Hodnota out-null         }                  # Príkaz Prostredia PowerShell na povolenie úlohy Secure-Boot-Update         $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 }'                  # Kódovať príkaz pre bezpečné vkladanie XML         $encodedCommand = [Convert]::ToBase64String([Text.Kódovanie]::Unicode.GetBytes($enableCommand))                  $taskGuid = [guid]::NewGuid(). ToString("B"). ToUpper()                  # XML naplánovanej úlohy GPP – okamžitá úloha, ktorá sa spustí raz         $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">       <Task version="1.3">         <RegistrationInfo>          >popisu>$TaskDescription</popisu <         </RegistrationInfo>         <principals>           <principal id="Author">             <UserId>S-1-5-18</UserId>             <></RunLevel>           </Principal>         </Principals>        >nastavenia <          ><IdleSettings             <Trvanie>PT5M</Trvanie>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd>false</StopOnIdleEnd>            ></RestartOnIdle> <RestartOnIdle>false           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <StartWhenAvailable>true</StartWhenAvailable>           <AllowStartOnDemand>true</AllowStartOnDemand>           <povolené>true</Enabled>           <Skryté></Skryté>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <Priority>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </Settings>         <akcie>           <Exec>             <command>powershell.exe</Command>             <Argumenty>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Argumenty>           </Exec>         </Actions>       </Task>    ></Properties   </ImmediateTaskV2> </ScheduledTasks> "@                  $gppTaskXml | Out-File -FilePath (Join-Path $sysvolPath "ScheduledTasks.xml") -kódovanie UTF8 -Force         Write-Log "Deployed one-time scheduled task to GPO: $TaskName" "OK"              } chytiť {         Write-Log "Nepodarilo sa nasadiť súbor XML naplánovanej úlohy: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Chyba = $_. Exception.Message }     }          # Krok 3: Vytvorenie alebo získanie skupiny zabezpečenia     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     ak (-nie $group) {         vyskúšať {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Popis "Počítače s vypnutým Secure-Boot-Update úlohu - zamerané na nápravu" '                 -PassThru             Write-Log "Created security group: $SecurityGroupName" (Vytvorená skupina zabezpečenia: $SecurityGroupName) "OK"         } chytiť {             Write-Log "Nepodarilo sa vytvoriť skupinu zabezpečenia: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = $_. Exception.Message }         }     } else {         Write-Log "Skupina zabezpečenia existuje: $SecurityGroupName" "INFO"     }          # Krok 4: Pridanie počítačov do skupiny zabezpečenia     $added = 0     $failed = 0     foreach ($hostname in $TargetHostnames) {         vyskúšať {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added + +         } chytiť {             $failed + +             Write-Log "Počítač sa nenašiel v službe AD: $hostname" "WARN"         }     }     Write-Log "Added $added computers to security group ($failed not found in AD)" "OK"          # Krok 5: Konfigurácia filtrovania zabezpečenia v objekte GPO     vyskúšať {         Set-GPPermission -Name $GPOName -TargetName "Overení používatelia" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Nakonfigurované filtrovanie zabezpečenia pre: $SecurityGroupName" "OK"     } chytiť {         Write-Log "Nepodarilo sa nakonfigurovať filtrovanie zabezpečenia: $($_. Exception.Message)" "WARN"     }          # Krok 6: Prepojenie objektu GPO s OU     ak ($TargetOU) {         vyskúšať {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }                          ak (-nie $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Linked GPO to: $TargetOU" "OK"             } else {                 Write-Log "OBJEKT GPO už prepojený s cieľovou OU" "INFO"             }         } chytiť {             Write-Log "Nepodarilo sa prepojiť objekt GPO s OU: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Chyba = "Prepojenie OBJEKTU GPO zlyhalo: $($_. Exception.Message)" }         }     } else {         Write-Log "Nie je zadaný žiadny TargetOU – objekt GPO bude potrebné manuálne prepojiť" "WARN"     }          Write-Log "" "INFO"     Write-Log "ENABLE TASK DEPLOYMENT COMPLETE" (POVOLIŤ DOKONČENIE NASADENIA ÚLOH) "OK"     Write-Log "Zariadenia spustia úlohu povoliť pri ďalšom obnovení objektu GPO (gpupdate)" "INFO"     Write-Log "Úloha sa spustí raz ako SYSTÉM a povolí secure-boot-update" "INFO"     Write-Log "" "INFO"          vrátiť @{         Success = $true         ComputersAdded = $added         ComputersFailed = $failed         GPOName = $GPOName         SecurityGroup = $SecurityGroupName     } }

# ============================================================================ # POVOLIŤ ÚLOHU V ZAKÁZANÝCH ZARIADENIACH # ============================================================================ ak ($EnableTaskOnDisabled) {     Write-Host ""     Write-Host ("=" * 70) -Farba popredia žltá     Write-Host " ENABLE TASK REMEDIATION - Fixing Disabled Scheduled Tasks" -ForegroundColor Yellow     Write-Host ("=" * 70) -Farba popredia žltá     Write-Host ""     # Vyhľadať zariadenia so zakázanou úlohou z agregačných údajov     ak (-nie $AggregationInputPath) {         Write-Host "ERROR: -AggregationInputPath sa vyžaduje na identifikáciu zariadení so zakázanou úlohou" -ForegroundColor Red         Write-Host "Usage: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <cesta> -ReportBasePath <cesta>" -ForegroundColor Gray         ukončiť 1     }     Write-Host "Skenovanie zariadení s vypnutou úlohou Secure-Boot-Update..." -ForegroundColor Cyan     # Načítať súbory JSON a vyhľadať zariadenia so zakázanou úlohou     $jsonFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue |                  Where-Object { $_. Názov -notmatch "ScanHistory|Zavádza saŠtát |RolloutPlan" }     $disabledTaskDevices = @()     foreach ($file in $jsonFiles) {         vyskúšať {             $device = Get-Content $file. FullName -Raw | Konvertovať Z-Json             ak ($device. SecureBootTaskEnabled -eq $false - alebo                 $device. SecureBootTaskStatus -eq 'Disabled' -or                 $device. SecureBootTaskStatus -eq 'NotFound') {                 # Zahrnúť iba zariadenia, ktoré sa ešte neaktualizovali (žiadna udalosť 1808)                 if ([int]$device. Event1808Count -eq 0) {                     $disabledTaskDevices += $device. Hostname                 }             }         } chytiť {             # Vynechať neplatné súbory         }     }     $disabledTaskDevices = $disabledTaskDevices | Select-Object – jedinečné     if ($disabledTaskDevices.Count -eq 0) {         Write-Host ""         Write-Host "Nenašli sa žiadne zariadenia so zakázanou úlohou secure-boot-update". -Farba popredia – zelená         Write-Host "Všetky zariadenia majú buď povolenú úlohu, alebo už boli aktualizované." -ForegroundColor Gray         ukončiť 0     }     Write-Host ""     Write-Host "Found $($disabledTaskDevices.Count) devices with disabled task:" -ForegroundColor Yellow     $disabledTaskDevices | Select-Object -Prvých 20 | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray }     if ($disabledTaskDevices.Count -gt 20) {         Write-Host " ... a $($disabledTaskDevices.Count - 20) more" -ForegroundColor Gray     }     Write-Host ""     # Nasadiť objekt Enable Task GPO     $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun     ak ($result. Úspech) {         Write-Host ""         Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green         Write-Host " Počítače pridané do skupiny zabezpečenia: $($result. ComputersAdded)" -ForegroundColor Azúrová         ak ($result. ComputersFailed -gt 0) {             Write-Host " Počítače sa nenašli v službe AD: $($result. ComputersFailed)" -ForegroundColor Yellow         }         Write-Host ""         Write-Host "ĎALŠIE KROKY:" -Farba popredia Biela         Write-Host " 1.                                              Zariadenia dostanú objekt GPO pri ďalšom obnovení (gpupdate /force)" -ForegroundColor Gray         Write-Host " 2. Jednorazová úloha povolí secure-boot-update" -ForegroundColor Gray         Write-Host " 3. Opätovné spustenie agregácie na overenie, či je úloha povolená" -ForegroundColor Gray     } else {         Write-Host ""         Write-Host "FAILED: Could not deploy Enable Task GPO" -ForegroundColor Red         Write-Host "Chyba: $($result. Error)" -ForegroundColor Red     }          ukončiť 0 }

# ============================================================================ # HLAVNÁ SLUČKA ORCHESTRATION # ============================================================================

Write-Host "" Write-Host ("=" * 80) -Azúrová farba popredia Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - CONTINUOUS DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 80) -Azúrová farba popredia Write-Host ""

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

if ($UseWinCS) {     Write-Host "[WinCS MODE]" -ForegroundColor Yellow     Write-Host "Using WinCsFlags.exe instead of GPO/AvailableUpdatesPolicy" -ForegroundColor Yellow     Write-Host "WinCS Key: $WinCSKey" -ForegroundColor Gray     Write-Host "" }

Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Vstupná cesta: $AggregationInputPath" "INFO" Write-Log "Cesta k zostave: $ReportBasePath" "INFO" ak ($UseWinCS) {     Write-Log "Metóda nasadenia: WinCS (WinCsFlags.exe /apply --key ""$WinCSKey")" "INFO" } else {     Write-Log "Metóda nasadenia: GPO (AvailableUpdatesPolicy)" "INFO" }

# Resolve TargetOU - default to domain root for domain-wide coverage # Vyžaduje sa len pre metódu nasadenia objektu GPO (WinCS nevyžaduje AD/GPO) if (-not $UseWinCS -and-not $TargetOU) {     vyskúšať {         # Skúste získať doménu DN viacerými spôsobmi         $domainDN = $null         # Metóda 1: Get-ADDomain (vyžaduje RSAT-AD-PowerShell)         vyskúšať {             Import-Module ActiveDirectory -ErrorAction Stop             $domainDN = (Get-ADDomain -ErrorAction Stop). DistinguishedName         } chytiť {             Write-Log "Get-ADDomain failed: $($_. Exception.Message)" "WARN"         }         # Metóda 2: Použitie RootDSE cez ADSI         ak (-nie $domainDN) {             vyskúšať {                 $rootDSE = [ADSI]"LDAP://RootDSE"                 $domainDN = $rootDSE.defaultNamingContext.ToString()             } chytiť {                 Write-Log ADSI RootDSE failed: $($_. Exception.Message)" "WARN"             }         }         # Metóda 3: Analyzovať z počítača domény členstvo         ak (-nie $domainDN) {             vyskúšať {                 $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()                 $domainDN = "DC=" + ($domain. Názov -replace '\.', ',DC=')             } chytiť {                 Write-Log GetComputerDomain zlyhal: $($_. Exception.Message)" "WARN"             }         }         ak ($domainDN) {             $TargetOU = $domainDN             Write-Log "Target: Domain Root ($domainDN) - GPO will apply domain-wide via security group filtering" "INFO"         } else {             Write-Log "Nepodarilo sa určiť doménu DN - OBJEKT GPO sa vytvorí, ale NIE JE PREPOJENÝ!" "CHYBA"             Write-Log "Zadajte parameter -TargetOU alebo prepojenie objektu GPO manuálne po vytvorení" "ERROR"             $TargetOU = $null         }     } chytiť {         Write-Log "Nepodarilo sa získať doménu DN – objekt GPO sa vytvorí, ale nebude prepojený.                                     Prepojenie manuálne v prípade potreby." "VAROVAŤ"         Write-Log "Chyba: $($_. Exception.Message)" "WARN"         $TargetOU = $null     } } else {     Write-Log "Target OU: $TargetOU" "INFO" }

Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Interval ankety: $PollIntervalMinutes minút" "INFO" ak ($LargeScaleMode) {     Write-Log "LargeScaleMode enabled (batch size: $ProcessingBatchSize, log sample: $DeviceLogSampleSize)" "INFO" }

# ============================================================================ # NEVYHNUTNÁ KONTROLA: Overenie nasadenia a fungovania zisťovania # ============================================================================

Write-Host "" Write-Log "Kontrola predpokladov..." "INFORMÁCIE"

$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath if (-not $detectionCheck.IsDeployed) {     Write-Log $detectionCheck.Message "ERROR"     Write-Host ""     Write-Host POVINNÉ: Najprv nasadiť infraštruktúru zisťovania:" -Farba popredia – žltá     Write-Host " 1. Spustiť: Deploy-GPO-SecureBootCollection.ps1 -OUPath 'OU=...' -OutputPath '\\server\SecureBootLogs$'" -ForegroundColor Cyan     Write-Host " 2. Počkajte, kým zariadenia nahlásia (12-24 hodín)" -ForegroundColor Cyan     Write-Host " 3. Re-run this orchestrator" -ForegroundColor Cyan     Write-Host ""     ak (-nie $DryRun) {         Vrátiť     } } else {     Write-Log $detectionCheck.Message "OK" }

# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Aktuálnosť údajov: $($freshness. TotalFiles) files, $($freshness. FreshFiles) čerstvé (<24h), $($freshness. StaleFiles) stale (>72h)" "INFO" ak ($freshness. Upozornenie) {     Write-Log $freshness. Upozornenie "WARN" }

# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() if ($AllowListPath -or $AllowADGroup) {     $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup     if ($allowedHostnames.Count -gt 0) {         Write-Log "AllowList: ONLY $($allowedHostnames.Count) devices will be considered for rollout" "INFO"     } else {         Write-Log "AllowList specified but no devices found - this will block all rollouts! "VAROVAŤ"     } }

# Load VIP/exclusion list (BlockList) $excludedHostnames = @() if ($ExclusionListPath -or $ExcludeADGroup) {     $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup     if ($excludedHostnames.Count -gt 0) {         Write-Log "VIP Exclusion: $($excludedHostnames.Count) devices will be skipped from rollout" "INFO"     } }

# 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 "rrrr-MM-dd HH:mm:ss"     Write-Log "Starting new rollout" "WAVE" }

Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Blokované sektory: $($blockedBuckets.Count)" "INFO"

# Main loop - runs until all eligible devices are updated $iterationCount = 0 zatiaľ čo ($true) {     $iterationCount + +     Write-Host ""     Write-Host ("=" * 80) -Farba popredia biela     Write-Log "=== ITERATION $iterationCount ===" "WAVE"     Write-Host ("=" * 80) -Farba popredia biela     # Krok 1: Spustenie agregácie     Write-Log "Krok 1: Spustená agregácia..." "INFORMÁCIE"     # Orchestrator vždy opakovane jeden priečinok (LargeScaleMode), aby sa zabránilo bloat disku     # Správcovia, ktorí spúšťajú agregátor, manuálne získajú priečinky s časovou pečiatkou pre snímky point-in-time     $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current"     # Kontrola aktuálnosti údajov pred agregáciou     $freshness = Get-DataFreshness -JsonPath $AggregationInputPath     Write-Log "Aktuálnosť údajov: $($freshness. FreshFiles)/$($freshness. TotalFiles) zariadenia hlásené za posledných 24h" "INFO"     ak ($freshness. Upozornenie) {         Write-Log $freshness. Upozornenie "WARN"     }     $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1"     $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json"     $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     if (test-path $aggregateScript) {         ak (-nie $DryRun) {             # Orchestrator vždy používa streaming + prírastkové pre účinnosť             # Agregátor auto-zvyšuje na PS7, ak je k dispozícii pre najlepší výkon             $aggregateParams = @{                 InputPath = $AggregationInputPath                 OutputPath = $aggregationPath                 StreamingMode = $true                 IncrementalMode = $true                 SkipReportIfUnchanged = $true                 ParallelThreads = 8             }             # Odovzdať súhrn zavádzania, ak existuje (pre údaje o rýchlosti/projekcii)             if (Test-Path $rolloutSummaryPath) {                 $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath             }             & $aggregateScript @aggregateParams             # Zobraziť príkaz na generovanie úplnej tabule HTML s tabuľkami zariadení             Write-Host ""             Write-Host "Ak chcete generovať úplnú tabuľu HTML s tabuľkami výrobcov/modelov, spustite:" -ForegroundColor Yellow             Write-Host " $aggregateScript -InputPath '"$AggregationInputPath'" -OutputPath '"$aggregationPath'"" -Farba popredia žltá             Write-Host ""         } else {             Write-Log "[DRYRUN] By spustiť agregáciu" "INFO"             # V DryRune použiť existujúce údaje agregácie priamo z ReportBasePath             $aggregationPath = $ReportBasePath         }     }     $rolloutState.LastAggregation = Get-Date -Format "rrrr-MM-dd HH:mm:ss"     # Krok 2: Načítanie aktuálneho stavu zariadenia     Write-Log "Krok 2: Načítava sa stav zariadenia..." "INFORMÁCIE"     $notUptodateCsv = Get-ChildItem -Path $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue |          Where-Object { $_. Názov -notlike "*Sektory*" } |          Sort-Object LastWriteTime -Descending |          Select-Object – prvá 1     if (-not $notUptodateCsv -and-not $DryRun) {         Write-Log "Nenašli sa žiadne údaje agregácie.                                            Čaká sa..." "VAROVAŤ"         Start-Sleep - sekundy ($PollIntervalMinutes * 60)         Pokračovať     }     $notUpdatedDevices = if ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } else { @() }     Write-Log "Zariadenia sa neaktualizovali: $($notUpdatedDevices.Count)" "INFO"     $notUpdatedIndexes = Get-NotUpdatedIndexes – zariadenia $notUpdatedDevices     # Krok 3: Aktualizácia histórie zariadenia (sledovanie podľa názvu hostiteľa)     Write-Log "Krok 3: Aktualizácia histórie zariadenia..." "INFORMÁCIE"     Update-DeviceHistory -CurrentDevices $notUpdatedDevices -DeviceHistory $deviceHistory     Save-DeviceHistory – história $deviceHistory     # Krok 4: Kontrola blokovaných sektorov (nedostupné zariadenia)     $existingBlockedCount = $blockedBuckets.Count     Write-Log "Krok 4: Kontrola blokovaných sektorov (ping zariadení po uplynutí čakacej doby)..." "INFORMÁCIE"     if ($existingBlockedCount -gt 0) {         Write-Log "Aktuálne blokované sektory z predchádzajúcich spustení: $existingBlockedCount" "INFO"     }     if ($adminApproved.Count -gt 0) {         Write-Log "Spravovanie schválené sektory (nebudú znovu blokované): $($adminApproved.Count)" "INFO"     }     $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState -BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun     if ($newlyBlocked.Count -gt 0) {         Save-BlockedBuckets -Blokované $blockedBuckets         Write-Log "Newly blocked buckets (this iteration): $($newlyBlocked.Count)" "BLOCKED"     }     # Krok 4b: Automatické odblokovanie sektorov, kde sa zariadenia aktualizovali     $autoUnblocked = Update-AutoUnblockedBuckets -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize     if ($autoUnblocked.Count -gt 0) {         Save-BlockedBuckets -Blokované $blockedBuckets         Write-Log "Automaticky odblokované sektory (aktualizované zariadenia): $($autoUnblocked.Count)" "OK"     }     # Krok 5: Výpočet zostávajúcich oprávnených zariadení     $eligibleCount = 0     foreach ($device in $notUpdatedDevices) {         $bucketKey = Get-BucketKey $device         if (-not $blockedBuckets.Contains($bucketKey)) {             $eligibleCount + +         }     }     Write-Log "Oprávnené zariadenia zostávajú: $eligibleCount" "INFO"     Write-Log "Blokované sektory: $($blockedBuckets.Count)" "INFO"     # Krok 6: Kontrola dokončenia     if ($eligibleCount -eq 0) {         Write-Log "ROLLOUT COMPLETE - All eligible devices updated!" (Zavádzanie dokončené – aktualizované všetky oprávnené zariadenia) "OK"         $rolloutState.Status = "Dokončené"         $rolloutState.CompletedAt = Get-Date -Format "rrrr-MM-dd HH:mm:ss"         Save-RolloutState -State $rolloutState         Prestávke     }     # Krok 6: Generovanie a nasadenie ďalšej vlny     Write-Log "Krok 6: Generovanie vlny zavádzania..." "INFORMÁCIE"     $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames     # Skontrolujte, či máme zariadenia na nasadenie ($waveDevices môžu byť $null, prázdne alebo so skutočnými zariadeniami)     $hasDevices = $waveDevices -and @($waveDevices | Where-Object { $_ }). Počet -gt 0     ak ($hasDevices) {         # Len prírastkové číslo vlny, keď skutočne máme zariadenia na nasadenie         $rolloutState.CurrentWave++         Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Počet) zariadení" "WAVE"         # Nasadenie objektu GPO pomocou inlinovanej funkcie         $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $hostnames = @($waveDevices | ForEach-Object {             ak ($_. Názov hostiteľa) { $_. Hostname } elseif ($_. Názov hostiteľa) { $_. HostName } else { $null }         } | Where-Object { $_ })         # Uložiť súbor hostnames pre referenciu/audit         $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt"         $hostnames | Out-File $hostnamesFile kódovanie UTF8         # Overte, či máme názvy hostiteľov na nasadenie         ak ($hostnames. Počet -eq 0) {             Write-Log "Nenašli sa žiadne platné názvy hostiteľov vo vlnách $($rolloutState.CurrentWave) – zariadeniam môže chýbať vlastnosť Hostname" "WARN"             Write-Log "Preskakovanie nasadenia pre túto vlnu - kontrola údajov zariadenia" "WARN"             # Stále čakať pred ďalšou iteráciou             ak (-nie $DryRun) {                 Write-Log "Sleeping for $PollIntervalMinutes minutes before retry..." "INFORMÁCIE"                 Start-Sleep –sekundy ($PollIntervalMinutes * 60)             }             Pokračovať         }         Write-Log "Deploying to $($hostnames. Count) hostnames in Wave $($rolloutState.CurrentWave)" "INFO"         # Nasadenie pomocou metódy WinCS alebo GPO na základe parametra -UseWinCS         ak ($UseWinCS) {             # WinCS Metóda: Vytvoriť objekt GPO s plánovanou úlohou spustiť WinCsFlags.exe ako systém v každom koncovom bode             Write-Log "Using WinCS deployment method (Key: $WinCSKey)" "WAVE"             $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames '                 -WinCSKey $WinCSKey '                 -WavePrefix $WavePrefix '                 -WaveNumber $rolloutState.CurrentWave '                 -TargetOU $TargetOU '                 -DryRun:$DryRun             if (-not $wincsResult.Success) {                 Write-Log Nasadenie WinCS zlyhalo – použité: $($wincsResult.Applied), Failed: $($wincsResult.Failed)" "WARN"             } else {                 Write-Log Úspešné nasadenie WinCS - Použité: $($wincsResult.Applied), Skipped: $($wincsResult.Skipped)" "OK"             }             # Uložiť výsledky WinCS pre audit             $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json"             $wincsResult | ConvertTo-Json -Hĺbka 5 | Out-File $wincsResultFile kódovanie UTF8         } else {             # METÓDA GPO: Vytvorenie objektu GPO s nastavením databázy Registry AvailableUpdatesPolicy             $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun             ak (-nie $gpoResult) {                 Write-Log "Nasadenie objektu GPO zlyhalo - zopakuje pokus o ďalšiu iteráciu" "CHYBA"             }         }         # Zaznamenať vlnu v stave         $waveRecord = @{             WaveNumber = $rolloutState.CurrentWave             StartedAt = Get-Date -Format "rrrr-MM-dd HH:mm:ss"             DeviceCount = @($waveDevices). Počítať             Devices = @($waveDevices | ForEach-Object {                 @{                     Názov hostiteľa = if ($_. Názov hostiteľa) { $_. Hostname } elseif ($_. Názov hostiteľa) { $_. HostName } else { $null }                     BucketKey = Get-BucketKey $_                 }             })         }         # Uistite sa, že WaveHistory je vždy pole pred pripojením (zabraňuje problémom s hashtable zlúčenie)         $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord)         $rolloutState.TotalDevicesTargeted += @($waveDevices). Počítať         Save-RolloutState – $rolloutState štátov         Write-Log nasadená verzia Wave $($rolloutState.CurrentWave).                                                                                                                                                                                        Čaká sa $PollIntervalMinutes minút..." "OK"     } else {         # Zobraziť stav nasadených zariadení čakajúcich na aktualizácie         Write-Log "" "INFO"         Write-Log "========== VŠETKY NASADENÉ ZARIADENIA – ČAKÁ SA NA ========== STAVU" "INFO"                  # Získať všetky nasadené zariadenia z histórie vĺn         $allDeployedLookup = @{}         foreach ($wave in $rolloutState.WaveHistory) {             foreach ($device in $wave. Zariadenia) {                 ak ($device. Názov hostiteľa) {                     $allDeployedLookup[$device. Názov hostiteľa] = @{                         Názov hostiteľa = $device. Hostname                         BucketKey = $device. Kľúč sektora                         DeployedAt = $wave. StartedAt (Začiatok)                         WaveNumber = $wave. WaveNumber (Číslo vlny)                     }                 }             }         }         $allDeployedDevices = @($allDeployedLookup.Hodnoty)                  if ($allDeployedDevices.Count -gt 0) {             # Zistite, ktoré nasadené zariadenia ešte čakajú (v zozname Neakplikované)             $stillPendingCount = 0             $noLongerPendingCount = 0             $pendingSample = @()             foreach ($deployed in $allDeployedDevices) {                 if ($notUpdatedIndexes.HostSet.Contains($deployed. Názov hostiteľa)) {                     $stillPendingCount + +                     if ($pendingSample.Count -lt $DeviceLogSampleSize) {                         $pendingSample += $deployed. Hostname                     }                 } else {                     $noLongerPendingCount+ +                 }             }                          # Získať skutočné aktualizované počty z agregácie - odlíšiť Udalosť 1808 vs UEFICA2023Status             $summaryCsv = Get-ChildItem -Path $aggregationPath -Filter "*Summary*.csv" |                  Sort-Object LastWriteTime -Descending | Select-Object –prvý 1             $actualUpdated = 0             $totalDevicesFromSummary = 0             $event 1808Count = 0             $uefiStatusUpdated = 0             $needsRebootSample = @()                          ak ($summaryCsv) {                 $summary = Import-Csv $summaryCsv.FullName | Select-Object –prvý 1                 ak ($summary. Aktualizované) { $actualUpdated = [int]$summary. Aktualizované }                 ak ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices }             }                          # Výpočet rýchlosti z histórie vĺn (zariadenia aktualizované za deň)             $devicesPerDay = 0             if ($rolloutState.StartedAt -and $actualUpdated -gt 0) {                 $startDate = [datetime]::P arse($rolloutState.StartedAt)                 $daysElapsed = ((Get-Date) - $startDate). Celkový počet dní                 if ($daysElapsed -gt 0) {                     $devicesPerDay = $actualUpdated/$daysElapsed                 }             }                          # Uložiť súhrn uvedenia s projekciami na víkend             # Použitie agregátora NotUptodate počet (vylučuje SB OFF zariadenia) pre konzistenciu             $notUpdatedCount = if ($summary -and $summary. NotUptodate) { [int]$summary. NotUptodate } else { $totalDevicesFromSummary - $actualUpdated }             Save-RolloutSummary -State $rolloutState '                 -TotalDevices $totalDevicesFromSummary '                 -UpdatedDevices $actualUpdated '                 -NotUpdatedDevices $notUpdatedCount '                 -DevicesPerDay $devicesPerDay                          # Skontrolujte nespracované údaje pre zariadenia s UEFICA2023Status=Aktualizované, ale žiadna udalosť 1808 (vyžaduje reštart)             $dataFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue             $totalDataFiles = @($dataFiles). Počítať             $batchSize = [Matematika]::Max(500; $ProcessingBatchSize)             ak ($LargeScaleMode) {                 $batchSize = [Matematika]::Max(2000; $ProcessingBatchSize)             }

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

                    foreach ($file in $batchFiles) {                         vyskúšať {                             $deviceData = Get-Content $file. FullName -Raw | Konvertovať Z-Json                             $hostname = $deviceData.Hostname                             if (-not $hostname) { continue }                             $has 1808 = [int]$deviceData.Event1808Count -gt 0                             $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Aktualizované"                             ak ($has 1808) {                                 $event 1808Count++                             } elseif ($hasUefiUpdated) {                                 $uefiStatusUpdated + +                                 if ($needsRebootSample.Count -lt $DeviceLogSampleSize) {                                     $needsRebootSample += $hostname                                 }                             }                         } chytiť { }                     }                                                          

                    Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{                         Event1808Count = $event 1808Count                         UEFIUpdatedAwaitingReboot = $uefiStatusUpdated                     }                 }             }             Write-Log "Total deployed: $($allDeployedDevices.Count)" "INFO"             Write-Log "Aktualizované (udalosť 1808 potvrdená): $event 1808Count" "OK"             if ($uefiStatusUpdated -gt 0) {                 Write-Log "Aktualizované (UEFICA2023Status=Aktualizované, čaká sa na reštart): $uefiStatusUpdated" "OK"                 $rebootSuffix = if ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated - $DeviceLogSampleSize) viac)" } else { "" }                 Write-Log " Zariadenia, ktoré potrebujú reštartovať udalosť 1808 (ukážka): $($needsRebootSample -join ', ')$rebootSuffix" "INFO"                 Write-Log " Tieto zariadenia nahlásia udalosť 1808 po ďalšom reštarte" "INFO"             }             Write-Log "Už nevybavené: $noLongerPendingCount (vrátane secureboot off, chýbajúce zariadenia)" "INFO"             Write-Log "Čaká sa na stav: $stillPendingCount" "INFO"             if ($stillPendingCount -gt 0) {                 $pendingSuffix = if ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount - $DeviceLogSampleSize) viac)" } else { "" }                 Write-Log "Čakajúce zariadenia (vzorka): $($pendingSample -join ', ')$pendingSuffix" "WARN"             }         } else {             Write-Log "Zatiaľ neboli nasadené žiadne zariadenia" "INFO"         }         Write-Log "================================================================" "INFO"         Write-Log "" "INFO"     }     # Počkajte pred ďalšou iteráciou     ak (-nie $DryRun) {         Write-Log "Sleeping for $PollIntervalMinutes minutes..." (Minúty spánku... "INFORMÁCIE"         Start-Sleep –sekundy ($PollIntervalMinutes * 60)     } else {         Write-Log "[DRYRUN] Would wait $PollIntervalMinutes minutes" "INFO"         break # Exit after one iteration in dry run     } }                               

# ============================================================================ # KONEČNÝ SÚHRN # ============================================================================

Write-Host "" Write-Host ("=" * 80) -Farba popredia zelená Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -ForegroundColor Green Write-Host ("=" * 80) -Farba popredia zelená Write-Host ""

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

Write-Host "Status:              $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Total Waves: $($finalState.CurrentWave)" Write-Host "Vybraté zariadenia: $($finalState.TotalDevicesTargeted)" Write-Host "Blokované sektory: $($finalBlocked.Count)" -ForegroundColor $(if ($finalBlocked.Count -gt 0) { "Red" } else { "Green" }) Write-Host "Sledované zariadenia: $($deviceHistory.Count)" -ForegroundColor Gray Write-Host ""

if ($finalBlocked.Count -gt 0) {     Write-Host "BLOCKED BUCKETS (require manual review):" -ForegroundColor Red     foreach ($key in $finalBlocked.Keys) {         $info = $finalBlocked[$key]         Write-Host " - $key" -Farba popredia červená         Write-Host " Dôvod: $($info. Reason)" -ForegroundColor Gray     }     Write-Host ""     Write-Host "Blocked buckets file: $blockedBucketsPath" -ForegroundColor Yellow }

Write-Host "" Write-Host "State files:" -ForegroundColor Cyan Write-Host stav uvedenia: $rolloutStatePath Write-Host "Blokované sektory: $blockedBucketsPath" Write-Host " História zariadenia: $deviceHistoryPath" Write-Host ""  

​​​​​​​

Potrebujete ďalšiu pomoc?

Chcete ďalšie možnosti?

Môžete preskúmať výhody predplatného, prehľadávať školiace kurzy, naučiť sa zabezpečiť svoje zariadenie a ešte oveľa viac.