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 ""