Nukopijuokite ir įklijuokite šį scenarijaus pavyzdį ir modifikuokite, kiek reikia jūsų aplinkai:

<# . ANOTACIJA     Nepertraukiamas saugiosios įkrovos diegimo valdymo modulis, kuris paleidžiamas, kol diegimas bus baigtas.

.DESCRIPTION     Šis scenarijus suteikia visapusį automatizavimą saugiosios įkrovos sertifikato išleidimą:     1.      Generuoja diegimo bangas pagal agregavimo duomenis     2. Kiekvienai bangai sukuria AD grupes ir GPO.     3. Monitoriai, skirti įrenginio naujinimams (1808 įvykis)     4. Aptinka užblokuotas talpyklas (nepasiekiamus įrenginius)     5. Automatiškai progresuoja į paskesnę bangą     6. Veikia, kol atnaujinsITE      VISUS reikalavimus atitinkančius įrenginius     Baigimo kriterijai:     - Nėra likusių įrenginių: Būtinas veiksmas, didelis pasitikėjimas, stebėjimas, laikinai pristabdyta     - Nepatenka į aprėptį (pagal dizainą): nepalaikoma, saugi įkrova išjungta     - Veikia nuolat, kol bus baigtas - be savavališko bangų limito          Diegimo strategija:     - DIDELIS PASITIKĖJIMAS: visi įrenginiai pirmos bangos (saugus)     - BŪTINA IMTIS VEIKSMŲ: Progresinis dvigubas (1→2→4→8...)          Blocking Logic:     - Po MaxWaitHours, orkestras pings įrenginius, kurie nebuvo atnaujinti     - Jei įrenginys yra NEPASIEKIAMAS (ryšio užklausa nepavyksta), → talpykla užblokuota tyrimui     - Jei įrenginys yra REACHABLE, bet neatnaujintas → laukti (gali tekti perkrauti)     - Užblokuotos talpyklos neįtraukiamos, kol administratorius juos      atblokuoja     Automatinis atblokavimas:     - Jei vėliau užblokuotoje talpykloje esantis įrenginys rodomas kaip atnaujintas (1808 įvykis),       segmentas automatiškai atblokuojamas ir vykdomas diegimas     - Tai apdoroja įrenginius, kurie buvo laikinai neprisijungę, bet grįžo          Įrenginio sekimas:     - Seka įrenginius pagal pagrindinio kompiuterio vardą (daro prielaidą, kad pavadinimai diegimo metu nesikeičia)     - Pastaba: JSON rinkinyje nėra unikalaus kompiuterio ID; įtraukite, kad sektumėte geriau

.PARAMETER AggregationInputPath     Kelias į neapdorotus JSON įrenginio duomenis (nuo scenarijaus aptikimo)

.PARAMETER ReportBasePath     Pagrindinis agregavimo ataskaitų kelias

.PARAMETER TargetOU     Distinguished Name of the OU to link GPO.Pasirinktinai – jei nenurodyta, GPO yra susietas su domeno šaknimi, kad apimtų visą domeną.Saugos grupės filtravimas užtikrina, kad strategija būtų taikoma tik tiksliniams įrenginiams.

.PARAMETER MaxWaitHours     Valandos laukti, kol įrenginiai bus atnaujinti prieš tikrinant pasiekiamumą.Po šio laiko įrenginiai, kurie nėra atnaujinti, pateikiami ryšio užklausas.Nepatikimi įrenginiai blokuoja talpyklą.Numatytoji reikšmė: 72 (3 dienos)

.PARAMETER PollIntervalMinutes     Minučių skaičius tarp būsenos patikrų. Numatytoji reikšmė: 1 440 (1 diena)

.PARAMETER AllowListPath     Failo, kuriame yra pagrindinių kompiuterių vardai, kelias į ALLOW, skirtą įdiegti (tikslinis diegimas).Palaiko .txt (vienas pagrindinio kompiuterio vardas vienoje eilutėje) arba .csv (su stulpeliu Hostname/ComputerName/Name).Kai nurodyta, tik šie įrenginiai bus įtraukti į diegimą.BlockList vis dar taikomas po AllowList.

.PARAMETER AllowADGroup     AD saugos grupės, kurioje yra ALLOW kompiuterio paskyrų, pavadinimas.Pvz.: "SecureBoot-Pilot-Computers" arba "Wave1-Devices"     Kai nurodyta, tik šios grupės įrenginiai bus įtraukti į diegimą.Suderinkite su "AllowListPath" ir failui, ir AD pagrįstam taikomam taikymui.

.PARAMETER ExclusionListPath     Failo, kuriame yra pagrindinio kompiuterio vardai, kelias į EXCLUDE iš "Rollout" (VIP / vadovų įrenginiai).Palaiko .txt (vienas pagrindinio kompiuterio vardas vienoje eilutėje) arba .csv (su stulpeliu Hostname/ComputerName/Name).Šie įrenginiai niekada nebus įtraukti į jokias diegimo bangas.BlockList taikomas PO AllowList filtravimo.     . PARAMETER ExcludeADGroup     AD saugos grupės, kurioje yra kompiuterio paskyrų, kurių nenorite įtraukti, pavadinimas.Pvz.: VIP kompiuteriai arba vadovų įrenginiai     Sujunkite su ExclusionListPath ir failams, ir AD pagrįstoms išimtims.

.PARAMETER UseWinCS     Naudokite "WinCS" ("Windows" konfigūravimo sistemą), o ne GPO/AvailableUpdatesPolicy."WinCS" diegia saugiosios įkrovos įgalinimą paleisdama WinCsFlags.exe tiesiogiai kiekviename galiniame punkte.WinCsFlags.exe vykdomas sistemos kontekste naudojant suplanuotą užduotį.Šis metodas naudingas:     - Greitesni išėmimai (tiesioginis efektas ir laukiama GPO apdorojimo)     - Prie domeno neprijungti įrenginiai     - Aplinkos be AD / GPO infrastruktūros     Nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe

.PARAMETER WinCSKey     "WinCS" raktas, naudojamas įgalinus saugiąją įkrovą.Numatytoji reikšmė: F33E0C8E002     Šis raktas atitinka saugiosios įkrovos diegimo konfigūraciją.     . PARAMETER DryRun     Rodyti, ką reikėtų daryti neatlikont pakeitimų

.PARAMETER ListBlockedBuckets     Rodyti visas šiuo metu užblokuotas talpyklas ir išeiti

.PARAMETER UnblockBucket     Atblokuokite konkretų talpyklą klavišu ir išeikite

.PARAMETER UnblockAll     Atblokuokite visas talpyklas ir išeikite

.PARAMETER EnableTaskOnDisabled     Visuotinai diegti Enable-SecureBootUpdateTask.ps1 visuose įrenginiuose, kuriuose išjungta suplanuota užduotis.Sukuria GPO su vienkartine suplanuota užduotimi, kuri paleidžia parinktį Įgalinti scenarijų su -Quiet.Tai naudinga norint pataisyti įrenginius, kuriuose išjungta užduotis Saugiosios įkrovos naujinimas.

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

.EXAMPLE     # Sąrašas užblokuotų talpyklų     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets

.EXAMPLE     # Atblokuoti konkretų segmentą     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"

.EXAMPLE     # Neįtraukti VIP įrenginių iš naujo naudojant teksto failą     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath E:\SecureBootReports '         -ExclusionListPath C:\Admin\VIP-Devices.txt

.EXAMPLE     # Neįtraukti įrenginių AD saugos grupėje (pvz., vadovų nešiojamieji kompiuteriai)     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath E:\SecureBootReports '         -ExcludeADGroup VIP kompiuteriai

.EXAMPLE     # Naudokite "WinCS" ("Windows" konfigūravimo sistemą), o ne GPO/AvailableUpdatesPolicy     # WinCsFlags.exe veikia sistemos kontekste kiekviename pabaigos taško per suplanuotą užduotį     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath E:\SecureBootReports '         -UseWinCS '         -WinCSKey "F33E0C8E002" #>

[CmdletBinding()] param(     [Parametras(Privalomas = $false)]     [eilutė]$AggregationInputPath,     [Parametras(Privalomas = $false)]     [eilutė]$ReportBasePath,     [Parametras(Privalomas = $false)]     [eilutė]$TargetOU,     [Parametras(Privalomas = $false)]     [eilutė]$WavePrefix = "SecureBoot-Rollout",     [Parametras(Privalomas = $false)]     [sveikasis skaičius]$MaxWaitHours = 72,     [Parametras(Privalomas = $false)]     [sveikasis skaičius]$PollIntervalMinutes = 1440,                         

    [Parameter(Mandatory = $false)]     [sveikasis skaičius]$ProcessingBatchSize = 5000,

    [Parameter(Mandatory = $false)]     [sveikasis skaičius]$DeviceLogSampleSize = 25,

    [Parameter(Mandatory = $false)]     [jungiklis]$LargeScaleMode,     #============================================================================     # AllowList / BlockList parametrai     # ============================================================================     # AllowList = įtraukti tik šiuos įrenginius (tikslinis diegimas)     # BlockList = neįtraukti šių įrenginių (jie niekada nebus pateikti)     # Apdorojimo tvarka: AllowList pirmiausia (jei nurodyta), tada BlockList     [Parametras(Privalomas = $false)]     [eilutė]$AllowListPath,     [Parametras(Privalomas = $false)]     [eilutė]$AllowADGroup,     [Parametras(Privalomas = $false)]     [eilutė]$ExclusionListPath,     [Parametras(Privalomas = $false)]     [eilutė]$ExcludeADGroup,     #============================================================================     # WinCS ("Windows" konfigūravimo sistemos) parametrai     #============================================================================     # WinCS yra "AvailableUpdatesPolicy" GPO diegimo alternatyva.                              # Jis naudoja WinCsFlags.exe kiekviename galiniame punkte, kad įgalintų saugiosios įkrovos diegimą.# WinCsFlags.exe veikia galinio punkto SISTEMOS kontekste.Nr. nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe          [Parametras(Privalomas = $false)]     [jungiklis]$UseWinCS,          [Parametras(Privalomas = $false)]     [eilutė]$WinCSKey = "F33E0C8E002",          [Parametras(Privalomas = $false)]     [jungiklis]$DryRun,          [Parametras(Privalomas = $false)]     [jungiklis]$ListBlockedBuckets,          [Parametras(Privalomas = $false)]     [eilutė]$UnblockBucket,          [Parametras(Privalomas = $false)]     [jungiklis]$UnblockAll,          [Parametras(Privalomas = $false)]     [jungiklis]$EnableTaskOnDisabled )

$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Diegimo ir stebėjimo pavyzdžiai"

# ============================================================================ # PRIKLAUSOMYBĖS TIKRINIMAS # ============================================================================

function Test-ScriptDependencies {     param(         [Parametras(Privalomas = $true)]         [eilutė]$ScriptDirectory,         [Parametras(Privalomas = $true)]         [eilutė[]$RequiredScripts     )     $missingScripts = @()     foreach ($script in $RequiredScripts) {         $scriptPath = Join-Path $ScriptDirectory $script         jei (-not (testo kelias $scriptPath)) {             $missingScripts += $script         }     }     jei ($missingScripts.Count -gt 0) {         Write-Host ""         Write-Host (=" * 70) - priekinio planopalva raudona         Write-Host " MISSING DEPENDENCIES" -ForegroundColor Red         Write-Host (=" * 70) - priekinio planopalva raudona         Write-Host "         Write-Host "Nerasta šių būtinų scenarijų:" -Priekinio planopalva Geltona         foreach ($script in $missingScripts) {             Write-Host " - $script" - Priekinio planopalva balta         }         Write-Host         Write-Host "Atsisiųskite naujausius scenarijus iš:" -ForegroundColor Cyan         Write-Host " URL: $DownloadUrl" - Priekinio planopalva balta         Write-Host " Pereiti į: "$DownloadSubPage" - priekinio planopalva balta         Write-Host         Write-Host "Išskleisti visus scenarijus į tą patį katalogą ir paleisti iš naujo." -Priekinio planopalva Geltona         Write-Host         grąžinimo $false     }     grąžinimo $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)) {     išeiti iš 1 }

# ============================================================================ # PARAMETRO TIKRINIMAS # ============================================================================

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

if (-not $ReportBasePath) {     Write-Host "KLAIDA: Reikia "-ReportBasePath". -Priekinio planopalva raudona     išeiti iš 1 }

if (-not $isAdminCommand -and -not $AggregationInputPath) {     Write-Host "KLAIDA: -AggregationInputPath būtinas diegiant (nereikia -ListBlockedBuckets, -UnblockBucket, -UnblockAll)" -ForegroundColor Red     išeiti iš 1 }

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

if (-not $isAdminCommand -and -not $DryRun) {     $CollectionGPOName = "SecureBoot-EventCollection"     # Patikrinkite, ar yra "GroupPolicy" modulis     jei (Get-Module -ListAvailable -Name GroupPolicy) {         Import-Module GroupPolicy - ErrorAction SilentlyContinue         Write-Host "Tikrinama, ar yra aptikimo GPO..." - Geltona priekinio plano spalva         išbandykite {             # Patikrinkite, ar GPO yra             $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue             jei ($existingGpo) {                 Write-Host " Rasta aptikimo GPO: $CollectionGPOName" -Priekinio planopalva žalia             } dar {                 Write-Host                 Write-Host ("=" * 70) - priekinio planopalva geltona                 Write-Host " ĮSPĖJIMAS: APTIKIMO GPO NERASTAS" - priekinio planopalva geltona                 Write-Host ("=" * 70) – geltona priekinio plano spalva                 Write-Host                 Write-Host "Aptikimo GPO "$CollectionGPOName" nerastas." -Priekinio planopalva geltona                 Write-Host "Be šio GPO nebus renkami jokie įrenginio duomenys." -Priekinio planopalva geltona                 Write-Host "                 Write-Host "To deploy the Detection GPO, run:" -ForegroundColor Cyan                 Write-Host ".\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domain> -AutoDetectOU" -ForegroundColor White                 Write-Host                 Write-Host "Vis tiek tęsti?                                     (Y/N)" - priekinio planopalva geltona                 $response = skaitymo pagrindinis kompiuteris                 if ($response -notmatch '^[Yy]') {                     Write-Host "Nutraukiama. Pirmiausia įdiekite aptikimo GPO." -ForegroundColor Red                     išeiti iš 1                 }             }         } sugauti {             Write-Host " Nepavyksta patikrinti GPO: $($_. Exception.Message)" -ForegroundColor Yellow         }     } dar {         Write-Host " GroupPolicy modulis nepasiekiamas – GPO patikros praleidimas" -Priekinio planopalva pilka     }     Write-Host }

# ============================================================================ # BŪSENOS FAILŲ KELIAI # ============================================================================

$stateDir = Join-Path $ReportBasePath "RolloutState" jei (-not (testo kelias $stateDir)) {     New-Item -ItemType katalogas -kelias $stateDir -Force | Nu neapibrėžta reikšmė (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 SUDERINAMUMAS: ConvertTo-Maišos lentelė #============================================================================ # ConvertFrom-Json -AsHashtable yra tik PS7+. Tai suteikia suderinamumą.

function ConvertTo-Hashtable {     param(         [Parameter(ValueFromPipeline = $true)]         $InputObject     )     procesas {         jei ($null -eq $InputObject) { grąžinti @{} }         jei ($InputObject -is [System.Collections.IDictionary]) { return $InputObject }         jei ($InputObject - yra [PSCustomObject]) {             # Naudokite [užsakyta] nuosekliam raktų užsakymui ir saugiam pasikartojančiam apdorojimui             $hash = [užsakyta]@{}             foreach ($prop in $InputObject.PSObject.Properties) {                 # Indexed assignment safely handles duplicates by overwriting                 $hash[$prop. Pavadinimas] = ConvertTo-Hashtable $prop. Vertė             }             grąžinimo $hash         }         jei ($InputObject - yra [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {             grįžti @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ })         }         grąžinimo $InputObject     } }

# ============================================================================ # ADMINISTRAVIMO KOMANDOS: Sąrašas / atblokuoti talpyklas # ============================================================================

if ($ListBlockedBuckets) {     Write-Host "     Write-Host ("=" * 80) – geltona priekinio plano spalva     Write-Host " BLOCKED BUCKETS" -Priekinio planopalva Geltona     Write-Host ("=" * 80) – priekinio planopalva geltona     Write-Host     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę         jei ($blocked. Skaičius -eq 0) {             Write-Host "Nėra užblokuotų talpyklų". -Priekinio planopalva Žalia         } dar {             Write-Host "Total blocked: $($blocked. Count)" -Priekinio planopalva raudona             Write-Host             ($key $blocked. Klavišai) {                 $info = $blocked[$key]                 Write-Host "Bucket: $key" -Priekinio planopalva raudona                 Write-Host " užblokuota: $($info. BlockedAt)" -Priekinio planopalva pilka                 Write-Host " Priežastis: $($info. Reason)" -Priekinio planopalva pilka                 Write-Host " Nepavykęs įrenginys: $($info. FailedDevice)" -Priekinio planopalva pilka                 Write-Host " Paskutinį kartą pranešta: $($info. LastReported)" -Priekinio planopalva pilka                 Write-Host " Banga: $($info. WaveNumber)" -Priekinio planopalva pilka                 Write-Host " Įrenginiai talpykloje: $($info. DevicesInBucket)" -ForegroundColor Gray                 Write-Host             }             Write-Host "Norėdami atblokuoti talpyklą:"             Write-Host .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY' -ForegroundColor Cyan             Write-Host             Write-Host "Norėdami atblokuoti viską:"             Write-Host ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockAll" -ForegroundColor Cyan         }     } dar {         Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Žalia     }     Write-Host     išeiti iš 0 }     

if ($UnblockBucket) {     Write-Host     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Neapdorota | ConvertFrom-Json | Konvertuoti į maišos lentelę         jei ($blocked. Yra($UnblockBucket)) {             $blocked. Šalinti($UnblockBucket)             $blocked | ConvertTo-Json gylis 10 | Out-File $blockedBucketsPath - Kodavimas UTF8 -Force             # Įtraukti į administratoriaus patvirtintą sąrašą, kad nebūtų blokuojamas iš naujo             $adminApproved = @{}             if (Test-Path $adminApprovedPath) {                 $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę             }             $adminApproved[$UnblockBucket] = @{                 ApprovedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"                 ApprovedBy = $env:USERNAME             }             $adminApproved | ConvertTo-Json gylis 10 | Out-File $adminApprovedPath - Kodavimas UTF8 -Force             Write-Host "Atblokuota talpykla: $UnblockBucket" – priekinio planopalva žalia             Write-Host "Įtraukta į administratoriaus patvirtintą sąrašą (nebus automatiškai blokuojama iš naujo)" -ForegroundColor Cyan         } dar {             Write-Host "Talpykla nerasta: $UnblockBucket" - Priekinio planopalva Geltona             Write-Host "Available buckets:" -ForegroundColor Gray             $blocked. Raktai | ForEach-Object { Write-Host " $_" -Priekinio planopalva Pilka }         }     } dar {         Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Geltona     }     Write-Host     išeiti iš 0 }                          

if ($UnblockAll) {     Write-Host     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Neapdorota | ConvertFrom-Json | Konvertuoti į maišos lentelę         $count = $blocked. Skaičius         @{} | ConvertTo-Json | Out-File $blockedBucketsPath - Kodavimas UTF8 -Force         Write-Host "Atblokuota visos $count talpyklos". - Priekinio planocolor žalia     } dar {         Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Geltona     }     Write-Host     išeiti iš 0 }

# ============================================================================ # PAGALBININKAS FUNKCIJOS # ============================================================================

function Get-RolloutState {     if (Test-Path $rolloutStatePath) {         išbandykite {             $loaded = Get-Content $rolloutStatePath -Žalias | ConvertFrom-Json | Konvertuoti į maišos lentelę             # Patikrinti būtinas ypatybes yra             jeigu ($null -eq $loaded. CurrentWave) {                 throw "Neleistinas būsenos failas – trūksta CurrentWave"             }             # Įsitikinkite, kad WaveHistory visada yra masyvas (išsprendžia PS5.1 JSON išdėstymo eilutėmis panaikinimas)             jeigu ($null -eq $loaded. WaveHistory) {                 $loaded. WaveHistory = @()             } elseif ($loaded. WaveHistory -isnot [masyvas]) {                 $loaded. WaveHistory = @($loaded. "WaveHistory")             }             grąžinimo $loaded         } sugauti {             Write-Log "Aptikta sugadintų RolloutState.json: $($_. Exception.Message)" "WARN"             Write-Log "Atsarginės sugadinto failo kopijos kūrimas ir naujo paleidimo" WARN             $backupPath = "$rolloutStatePath.corrupted.$(Get-Date -Format 'yyyyMMdd-HHmmss')"             Move-Item $rolloutStatePath $backupPath –Force - ErrorAction SilentlyContinue         }     }     grąžinti @{         CurrentWave = 0         StartedAt = $null         LastAggregation = $null         TotalDevicesTargeted = 0         TotalDevicesUpdated = 0         Būsena = "NotStarted"         WaveHistory = @()     } }

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

function Get-WeekdayProjection {     <#     . ANOTACIJA         Apskaičiuokite numatomas baigimo datas savaitgaliais (nėra eigos š/s)     #>     param(         [sveikasis skaičius]$RemainingDevices,         [double]$DevicesPerDay,         [datetime]$StartDate = (Get-Date)     )     jei ($DevicesPerDay -le 0 arba $RemainingDevices -le 0) {         grąžinti @{             ProjectedDate = $null             WorkingDaysNeeded = 0             CalendarDaysNeeded = 0         }     }     # Apskaičiuokite reikiamas darbo dienas (išskyrus savaitgalius)     $workingDaysNeeded = [matematika]::Ceiling($RemainingDevices / $DevicesPerDay)     # Darbo dienų konvertavimas į kalendorines dienas (įtraukti savaitgalius)     $currentDate = $StartDate.Date     $daysAdded = 0     $workingDaysAdded = 0     o ($workingDaysAdded -lt $workingDaysNeeded) {         $currentDate = $currentDate.AddDays(1)         $daysAdded++         # Skaičiuoti tik darbo dienas         jei ($currentDate.DayOfWeek -ne [DayOfWeek]::Šeštadienis -ir             $currentDate.DayOfWeek -ne [DayOfWeek]::Sunday) {             $workingDaysAdded++         }     }     grąžinti @{         ProjectedDate = $currentDate.ToString("yyyy-MM-dd")         WorkingDaysNeeded = $workingDaysNeeded         CalendarDaysNeeded = $daysAdded     } }                                  

function Save-RolloutSummary {     <#     . ANOTACIJA         Įrašyti diegimo suvestinę su ataskaitų srities rodymo projekcijos informacija     #>     param(         [maišos lentelė]$State,         [sveikasis skaičius]$TotalDevices,         [sveikasis skaičius]$UpdatedDevices,         [sveikasis skaičius]$NotUpdatedDevices,         [dvigubas]$DevicesPerDay     )     $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     # Apskaičiuokite savaitgalio projekciją     $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay     $summary = @{         GeneratedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")         RolloutStartDate = $State.StartedAt         LastAggregation = $State.LastAggregation         CurrentWave = $State.CurrentWave         Būsena = $State.Status         # Įrenginių skaičius         TotalDevices = $TotalDevices         UpdatedDevices = $UpdatedDevices         NotUpdatedDevices = $NotUpdatedDevices         PercentUpdated = jei ($TotalDevices -gt 0) { [matematika]::Round(($UpdatedDevices / $TotalDevices) * 100, 1) } dar { 0 }         # greičio metrika         DevicesPerDay = [matematika]::Round($DevicesPerDay, 1)         TotalDevicesTargeted = $State.TotalDevicesTargeted         TotalWaves = $State.CurrentWave         # Savaitgalio projekcija         ProjectedCompletionDate = $projection. Projekto data         WorkingDaysRemaining = $projection. WorkingDaysNeeded         CalendarDaysRemaining = $projection. CalendarDaysNeeded         # Pastaba apie savaitgalio išimtį         ProjectionNote = "Projected completion excludes weekends (Sat/Sun)"     }     $summary | ConvertTo-Json gylis 5 | Out-File $summaryPath - Kodavimas UTF8 -Force     Write-Log "Rollout summary saved: $summaryPath" "INFO"     grąžinimo $summary }                                                             

function Get-BlockedBuckets {     if (Test-Path $blockedBucketsPath) {         return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę     }     grąžinti @{} }

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

function Get-AdminApproved {     if (Test-Path $adminApprovedPath) {         return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę     }     grąžinti @{} }

function Get-DeviceHistory {     if (Test-Path $deviceHistoryPath) {         return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę     }     grąžinti @{} }

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

function Save-ProcessingCheckpoint {     param(         [eilutė]$Stage,         [sveikasis skaičius]$Processed,         [sveikasis skaičius]$Total,         [maišos lentelė]$Metrics = @{}     )

    $checkpoint = @{         Etapas = $Stage         UpdatedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"         Apdorota = $Processed         Iš viso = $Total         Procentas = jei ($Total -gt 0) { [matematika]::Round(($Processed / $Total) * 100, 2) } dar { 0 }         Metrika = $Metrics     }

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

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

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

    foreach ($device in $Devices) {         $hostname = jei ($device. Pagrindinio kompiuterio vardas) { $device. Hostname } elseif ($device. HostName) { $device. HostName } dar { $null }         jei ($hostname) {             [negalioja]$hostSet.Add($hostname)         }

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

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

function Write-Log {     param([eilutė]$Message, [eilutė]$Level = "INFO")     $timestamp = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"     $color = jungiklis ($Level) {         "OK" { "Green" }         "WARN" { "Yellow" }         "ERROR" { "Red" }         "BLOCKED" { "DarkRed" }         "WAVE" { "Cyan" }         numatytoji reikšmė { "Baltas" }     }     Write-Host $color "[$timestamp] [$Level] $Message"     # Taip pat įeiti į failą     $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMMdd').log"     "[$timestamp] [$Level] $Message" | Out-File $logFile – pridėti - kodavimas UTF8 }               

function Get-BucketKey {     param($Device)     # Naudokite BucketId iš įrenginio JSON, jei yra (SHA256 maiša iš aptikimo scenarijaus)     if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { return "$($Device.BucketId)" }     # Atsarginė kopija: gamintojo konstrukcija|modelis|bios     $mfr = jei ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } dar { $Device.Manufacturer }     $model = jei ($Device.WMI_Model) { $Device.WMI_Model } dar { $Device.Model }     $bios = jei ($Device.BIOSDescription) { $Device.BIOSDescription } dar { $Device.BIOS }     pateikti $mfr|$model|$bios" }

# ============================================================================ # VIP / IŠIMČIŲ SĄRAŠO ĮKĖLIMAS # ============================================================================

function Get-ExcludedHostnames {     param(         [eilutė]$ExclusionFilePath,         [eilutė]$ADGroupName     )     $excluded = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)     # Įkelti iš failo (palaiko .txt arba .csv)     jei ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) {         $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower()         jei ($extension -eq ".csv") {             # CSV formatas: tikisi stulpelio "Hostname" arba "ComputerName"             $csvData = Import-Csv $ExclusionFilePath             $hostCol = jei ($csvData[0]. PSObject.Properties.Name - yra "Hostname") { "Hostname" }                        elseif ($csvData[0]. PSObject.Properties.Name – yra "ComputerName") { "ComputerName" }                        elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                        else { $null }             jei ($hostCol) {                 foreach ($row in $csvData) {                     jei (![ eilutė]::IsNullOrWhiteSpace($row.$hostCol)) {                         [negalioja]$excluded. Add($row.$hostCol.Trim())                     }                 }             }         } dar {             # Paprastasis tekstas: vienas pagrindinio kompiuterio vardas vienoje eilutėje             Get-Content $ExclusionFilePath | ForEach-Object {                 $line = $_. Trim()                 jei ($line -and -not $line. StartsWith('#')) {                     [negalioja]$excluded. Add($line)                 }             }         }         Write-Log "Įkelta $($excluded. Skaičius) pagrindinio kompiuterio vardai iš išimčių failo: $ExclusionFilePath" "INFO"     }     # Įkelti iš AD saugos grupės     jei ($ADGroupName) {         išbandykite {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }             foreach ($member in $groupMembers) {                 [negalioja]$excluded. Add($member. Vardas ir pavardė)             }             Write-Log "Įkelti $($groupMembers.Count) kompiuteriai iš AD grupės: $ADGroupName" "INFO"         } sugauti {             Write-Log "Nepavyko įkelti AD grupės "$ADGroupName": $_" "WARN"         }     }     grįžti @($excluded) }                                                                             

# ============================================================================ # ALLOW SĄRAŠO ĮKĖLIMAS (tikslinis diegimas) # ============================================================================

function Get-AllowedHostnames {     <#     . ANOTACIJA         Įkelia pagrindinio kompiuterio vardus iš AllowList failo ir (arba) AD grupės tiksliniam išleidimą.Kai nurodytas AllowList, TIK šie įrenginiai bus įtraukti į diegimą.#>     param(         [eilutė]$AllowFilePath,         [eilutė]$ADGroupName     )          $allowed = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)          # Įkelti iš failo (palaiko .txt arba .csv)     jei ($AllowFilePath -and (Test-Path $AllowFilePath)) {         $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower()                  jei ($extension -eq ".csv") {             # CSV formatas: tikisi stulpelio "Hostname" arba "ComputerName"             $csvData = Import-Csv $AllowFilePath             jei ($csvData.Count -gt 0) {                 $hostCol = jei ($csvData[0]. PSObject.Properties.Name -contains "Hostname") { "Hostname" }                            elseif ($csvData[0]. PSObject.Properties.Name – yra "ComputerName") { "ComputerName" }                            elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                            else { $null }                                  jei ($hostCol) {                     foreach ($row in $csvData) {                         jei (![ eilutė]::IsNullOrWhiteSpace($row.$hostCol)) {                             [negalioja]$allowed. Add($row.$hostCol.Trim())                         }                     }                 }             }         } dar {             # Paprastasis tekstas: vienas pagrindinio kompiuterio vardas vienoje eilutėje             Get-Content $AllowFilePath | ForEach-Object {                 $line = $_. Trim()                 jei ($line -and -not $line. StartsWith('#')) {                     [negalioja]$allowed. Add($line)                 }             }         }                  Write-Log "Įkelta $($allowed. Skaičius) pagrindinių kompiuterių vardai iš leidžiamų sąrašo failų: $AllowFilePath" "INFO"     }          # Įkelti iš AD saugos grupės     jei ($ADGroupName) {         išbandykite {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }                          foreach ($member in $groupMembers) {                 [negalioja]$allowed. Add($member. Vardas ir pavardė)             }                          Write-Log "Įkelti $($groupMembers.Count) kompiuteriai iš AD allow grupės: $ADGroupName" "INFO"         } sugauti {             Write-Log "Nepavyko įkelti AD grupės "$ADGroupName": $_" "WARN"         }     }          grįžti @($allowed) }

# ============================================================================ # DUOMENŲ NAUJUMAS IR STEBĖJIMAS # ============================================================================

function Get-DataFreshness {     <#     . ANOTACIJA         Tikrina, ar aptikimo duomenys yra nauji, patikrindama JSON failo laiko žymas.Grąžina statistiką apie tai, kada paskutinį kartą buvo pateikti pabaigos taškai.#>     param([eilutė]$JsonPath)     $jsonFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue     jei ($jsonFiles.Count -eq 0) {         grąžinti @{             TotalFiles = 0             FreshFiles = 0             StaleFiles = 0             NoDataFiles = 0             OldestFile = $null             NewestFile = $null             AvgAgeHours = 0             Įspėjimas = "Nerasta JSON failų – aptikimas gali būti neįdiegtas"         }     }     $now = Gavimo data     $freshThresholdHours = 24 # Files atnaujinta per paskutines 24 valandas yra "fresh"     $staleThresholdHours = 72 # Files senesni nei 72 valandos yra "pasenusi"     $fresh = 0     $stale = 0     $ages = @()     foreach ($file in $jsonFiles) {         $ageHours = ($now – $file. "LastWriteTime"). Bendra valandų suma         $ages += $ageHours         jei ($ageHours -le $freshThresholdHours) {             $fresh++         } elseif ($ageHours -ge $staleThresholdHours) {             $stale++         }     }     $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object – pirmas 1     $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1     $warning = $null     jei ($stale -gt ($jsonFiles.Count * 0.5)) {         $warning = "Daugiau nei 50 % įrenginių turi pasenusius duomenis (>72 val.) - patikrinti aptikimo GPO"     } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) {         $warning = "Mažiau nei 30 % neseniai praneštų įrenginių – aptikimas gali neveikti"     }     grąžinti @{         TotalFiles = $jsonFiles.Count         FreshFiles = $fresh         StaleFiles = $stale         MediumFiles = $jsonFiles.Count - $fresh - $stale         OldestFile = $oldestFile.LastWriteTime         NewestFile = $newestFile.LastWriteTime         AvgAgeHours = [matematika]:Round(($ages | Measure-Object -Average). Vidurkis, 1)         Įspėjimas = $warning     } }                                                 

function Test-DetectionGPODeployed {     <#     . ANOTACIJA         patikrina, ar veikia aptikimo ir (arba) stebėjimo infrastruktūra.#>     param([eilutė]$JsonPath)     # 1 patikrinimas: JSON kelias yra     jei (-not (testo kelias $JsonPath)) {         grąžinti @{             IsDeployed = $false             Message = "JSON įvesties kelio nėra: $JsonPath"         }     }     # 2 patikrinimas: yra bent kai kurie JSON failai     $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Skaičius     jei ($jsonCount -eq 0) {         grąžinti @{             IsDeployed = $false             Message = "No JSON files in $JsonPath - Detection GPO may not be deployed or devices haven't reported yet"         }     }     # Patikrinkite 3: Files yra gana neseniai (bent jau praėjusią savaitę)     $recentFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue |         Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) }     jei ($recentFiles.Count -eq 0) {         grąžinti @{             IsDeployed = $false             Message = "No JSON files updated in last 7 days - Detection GPO may be broken or devices offline"         }     }     grąžinti @{         IsDeployed = $true         Message = "Detection appears active: $jsonCount files, $($recentFiles.Count) updated recently"         FileCount = $jsonCount         RecentCount = $recentFiles.Count     } }                         

# ============================================================================ # ĮRENGINIO SEKIMAS (PAGAL PAGRINDINIO KOMPIUTERIO VARDĄ) # ============================================================================

function Update-DeviceHistory {     <#     . ANOTACIJA         Seka įrenginius pagal pagrindinio kompiuterio vardą, nes neturime unikalaus kompiuterio identifikatoriaus.Pastaba: BucketId yra "vienas su daugeliu" (ta pati aparatūros konfigūracija = ta pati talpykla).Jei Į JSON rinkinį įtraukiamas unikalus identifikatorius, atnaujinkite šią funkciją.#>     param(         [masyvas]$CurrentDevices,         [maišos lentelė]$DeviceHistory     )          foreach ($device in $CurrentDevices) {         $hostname = $device. Hostname         jei (-not $hostname) { continue }                  # Sekti įrenginį pagal pagrindinio kompiuterio vardą         $DeviceHistory[$hostname] = @{             Pagrindinio kompiuterio vardas = $hostname             BucketId = $device. "BucketId"             Gamintojas = $device. WMI_Manufacturer             Modelis = $device. WMI_Model             LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             Būsena = $device. UpdateStatus         }     } }

# ============================================================================ # UŽBLOKUOTAS TALPYKLOS APTIKIMAS (atsižvelgiant į įrenginio pasiekiamumą) # ============================================================================

<# . APRAŠYMAS / KONTROLĖ     Blocking Logic:     - Talpykla blokuojama, TIK jei:       1. Įrenginys buvo nukreiptas į bangą       2. MaxWaitHours praėjo nuo bangos pradžios       3. Įrenginys NEPASIEKIAMAS (ryšio užklausa nepavyksta)          - Jei įrenginys pasiekiamas, bet dar neatnaujintas, vis dar laukiame       (Naujinimas gali būti laukia paleidimo iš naujo – įvykis 1808 sužadinama tik paleidus iš naujo)          - Nepasiekiamas įrenginys rodo, kad kažkas nutiko ir reikia atlikti tyrimą          Atblokavimo:     - Naudokite -ListBlockedBuckets norėdami pamatyti užblokuotas talpyklas     - Norėdami atblokuoti konkretų segmentą, naudokite -UnblockBucket "BucketKey"     - Naudokite -UnblockAll, kad atblokuotumėte visus talpyklas #>

function Test-DeviceReachable {     param(         [eilutė]$Hostname,         [eilutė]$DataPath # Kelias į įrenginio JSON failus     )     # 1 metodas: Patikrinkite JSON failo laiko žymą (greičiausia – nereikia analizuoti failo)     # Jei aptikimo scenarijus vyko neseniai, failas buvo parašytas / atnaujintas, įrodant, kad įrenginys yra gyvas     jei ($DataPath) {         $deviceFile = Get-ChildItem -Path $DataPath -Filter "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object – pirmas 1         jei ($deviceFile) {             $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). Bendra valandų suma             jei ($hoursSinceWrite -lt 72) { grąžinti $true }         }     }     # 2 metodas: Atsarginė ryšio užklausa (tik jei JSON yra pasenusi arba jos nėra)     išbandykite {         $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue         grąžinimo $ping     } sugauti {         grąžinimo $false     } }          

function Update-BlockedBuckets {     param(         $RolloutState,         $BlockedBuckets,         $AdminApproved,         [masyvas]$NotUpdatedDevices,         [maišos lentelė]$NotUpdatedIndexes,         [sveikasis skaičius]$MaxWaitHours,         [Bulio logika]$DryRun = $false     )     $now = Gavimo data     $newlyBlocked = @()     $stillWaiting = @()     $devicesToCheck = @()     $hostSet = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     $bucketCounts = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts} dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). BucketCounts }     # Rinkti įrenginius, kurie yra praėjusio laukimo laikotarpio ir vis dar neatnaujinti     foreach ($wave in $RolloutState.WaveHistory) {         jei (ne $wave. StartedAt) { continue }         $waveStart = [DateTime]::P arse($wave. Darbo pradžia)         $hoursSinceWave = ($now – $waveStart). Bendra valandų suma         jei ($hoursSinceWave -lt $MaxWaitHours) {             # Vis dar laukimo laikotarpiu – dar netikrinę             Toliau         }         # Patikrinkite kiekvieną įrenginį iš šios bangos         ($deviceInfo programoje "$wave". Įrenginiai) {             $hostname = $deviceInfo.Hostname             $bucketKey = $deviceInfo.BucketKey             # Praleisti, jei talpykla jau užblokuota             jei ($BlockedBuckets.Contains($bucketKey)) { continue }             # Praleisti, jei talpykla yra administratoriaus patvirtinta IR banga pradėta PRIEŠ patvirtinimą             # (patikrinkite tik tuos įrenginius, kuriems taikomas po administratoriaus patvirtinimo dėl pakartotinio blokavimo)             jei ($AdminApproved ir $AdminApproved.Contains($bucketKey)) {                 $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. PatvirtintaAt)                 jei ($waveStart -lt $approvalTime) {                     # Šis įrenginys buvo nukreiptas prieš administratoriaus patvirtinimą – praleisti                     Toliau                 }                 # Banga pradėta po patvirtinimo – tai yra naujas taikymas, galite patikrinti             }             # Ar šis įrenginys vis dar yra NotUpdated sąraše?             jei ($hostSet.Contains($hostname)) {                 $devicesToCheck += @{                     Hostname = $hostname                     BucketKey = $bucketKey                     WaveNumber = $wave. "WaveNumber"                     HoursSinceWave = [math]::Round($hoursSinceWave, 1)                 }             }         }     }     jei ($devicesToCheck.Count -eq 0) {         grąžinimo $newlyBlocked     }     Write-Log "$($devicesToCheck.Count) įrenginių pasiekiamumo tikrinimas praėjusio laukimo laikotarpio..." "INFO"     # Track failures per bucket for decision-making     $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Gyvas = @() }     # Patikrinkite kiekvieno įrenginio pasiekiamumą     foreach ($device in $devicesToCheck) {         $hostname = $device. Hostname         $bucketKey = $device. "BucketKey"         jei ($DryRun) {             Write-Log "[DRYRUN] Patikrins $hostname pasiekiamumą" "INFO"             Toliau         }         jei (-not $bucketFailures.ContainsKey($bucketKey)) {             $bucketFailures[$bucketKey] = @{ Nepasiekiamas = @(); AliveButFailed = @(); WaveNumber = $device. "WaveNumber"; HoursSinceWave = $device. HoursSinceWave }         }         $isReachable = Test-DeviceReachable -Hostname $hostname -DataPath $AggregationInputPath         jei (-not $isReachable) {             $bucketFailures[$bucketKey]. Nepasiekiamas += $hostname         } dar {             # Įrenginys YRA pasiekiamas, bet dar neatnaujintas – gali būti laikina triktis arba laukiama perkrovimo             $bucketFailures[$bucketKey]. AliveButFailed += $hostname             $stillWaiting += $hostname         }     }     # Sprendimas už talpyklą: blokuoti tik jei įrenginiai tikrai UNREACHABLE     # Alive devices with failures = temporary, continue rollout     foreach ($bucketKey in $bucketFailures.Keys) {         $bf = $bucketFailures[$bucketKey]         $unreachableCount = $bf. Nepasiekiamas.skaičius         $aliveFailedCount = $bf. AliveButFailed.Count         # Patikrinkite, ar šioje talpykloje yra sėkmingų įvykių (iš atnaujintų įrenginių duomenų)         $bucketHasSuccesses = $stSuccessBuckets ir $stSuccessBuckets.Contains($bucketKey)         jei ($unreachableCount -gt 0 -ir $aliveFailedCount -eq 0) {             # VISI įrenginiai su klaida nepasiekiami – užblokuokite talpyklą             jei ($newlyBlocked -notcontains $bucketKey) {                 $BlockedBuckets[$bucketKey] = @{                     BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                     Reason = "All $unreachableCount device(s) unreachable after $($bf. HoursSinceWave) valandos"                     FailedDevices = ($bf. Nepasiekiama -join ", ")                     WaveNumber = $bf. "WaveNumber"                     DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } dar { 0 }                 }                 $newlyBlocked += $bucketKey                 Write-Log "BUCKET BLOCKED: $bucketKey ($unreachableCount device(s) unreachable: $($bf. Nepasiekiamas -join ', '))" "BLOCKED"             }         } elseif ($aliveFailedCount -gt 0) {             # Įrenginiai yra gyvi, bet neatnaujinami – laikina triktis, NEBLOKUOKITE             Write-Log "Bucket $($bucketKey.Substring(0, [Math]::Min(16, $bucketKey.Length)))...: $aliveFailedCount įrenginys (-iai) gyvas (-i), bet laukia, $unreachableCount nepasiekiamas – NOT blocking (temporary)" "INFO"             jei ($unreachableCount -gt 0) {                 Write-Log " Nepasiekiama: $($bf. Nepasiekiamas -join ', ')" "WARN"             }             Write-Log " Gyvas, bet laukia: $($bf. AliveButFailed - join ', ')" "INFO"             # Track failure count in rollout state for monitoring             jei (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} }             $RolloutState.TemporaryFailures[$bucketKey] = @{                 AliveButFailed = $bf. AliveButFailed                 Nepasiekiama = $bf. Nepasiekiamas                 LastChecked = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"             }         }     }     jei ($stillWaiting.Count -gt 0) {         Write-Log "Įrenginiai pasiekiami, bet laukia naujinimo (gali reikėti perkrauti): $($stillWaiting.Count)" "INFO"     }     grąžinimo $newlyBlocked }                                                                                                                                                                                  

# ============================================================================ # AUTO-UNBLOCK: atblokuoti talpyklas, kai įrenginiai sėkmingai atnaujinami # ============================================================================

function Update-AutoUnblockedBuckets {     <#     . APRAŠYMAS / KONTROLĖ         Tikrina, ar atnaujinti įrenginiai užblokuotose talpyklose (1808 įvykis).         Automatiškai atblokuoja, jei atnaujinti VISI tiksliniai įrenginiai talpykloje.Jei tik atnaujinti KAI KURIE įrenginiai, praneša administratoriui, kuris gali atblokuoti rankiniu būdu.                  Administratorius gali atblokuoti rankiniu būdu, naudodami:           .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "kelias" -UnblockBucket "BucketKey"     #>     param(         $BlockedBuckets,         $RolloutState,         [masyvas]$NotUpdatedDevices,         [eilutė]$ReportBasePath,         [maišos lentelė]$NotUpdatedIndexes,         [sveikasis skaičius]$LogSampleSize = 25     )     $autoUnblocked = @()     $bucketsToCheck = @($BlockedBuckets.Keys)     $hostSet = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     foreach ($bucketKey in $bucketsToCheck) {         $bucketInfo = $BlockedBuckets[$bucketKey]         # Gaukite visus mūsų pritaikytus įrenginius iš šios talpyklos istoriškai         $targetedDevicesInBucket = @()         foreach ($wave in $RolloutState.WaveHistory) {             $targetedDevicesInBucket += @($wave. Įrenginiai | Where-Object { $_. BucketKey -eq $bucketKey })         }         jei ($targetedDevicesInBucket.Count -eq 0) { continue }         # Patikrinkite, kiek tikslinių įrenginių vis dar yra "NotUpdated" ir naujinama         $updatedDevices = @()         $stillPendingDevices = @()         foreach ($targetedDevice in $targetedDevicesInBucket) {             jei ($hostSet.Contains($targetedDevice.Hostname)) {                 $stillPendingDevices += $targetedDevice.Hostname             } dar {                 $updatedDevices += $targetedDevice.Hostname             }         }         jei ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -eq 0) {             # VISI tiksliniai įrenginiai atnaujinti - automatiškai atblokuoti!             $BlockedBuckets.Remove($bucketKey)             $autoUnblocked += @{                 BucketKey = $bucketKey                 UpdatedDevices = $updatedDevices                 PreviouslyBlockedAt = $bucketInfo.BlockedAt                 Reason = "All $($updatedDevices.Count) targeted device(s) successfully updated"             }             Write-Log "AUTO-UNBLOCKED: $bucketKey (All $($updatedDevices.Count) targeted device(s) updated successfully)" "OK"             # Papildančiojo OĮG bangų skaičius šios talpyklos OĮG (OĮG sekimas)             $bucketOEM = jei ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } dar { 'Unknown' } # Extract OEM from pipe-delimited key or default             jei (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             $currentWave = jei ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } dar { 0 }             $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1             Write-Log " OEM '$bucketOEM' wave count incremented to $($currentWave + 1) (next allocation: $([int][Math]::P ow(2, $currentWave + 1)) devices)" "INFO"         }         elseif ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -gt 0) {             # KAI kurie įrenginiai atnaujinti, bet kiti vis dar laukia – praneškite administratoriui (tik vieną kartą)             jei (-not $bucketInfo.UnblockCandidate) {                 $bucketInfo.UnblockCandidate = $true                 $bucketInfo.UpdatedDevices = $updatedDevices                 $bucketInfo.PendingDevices = $stillPendingDevices                 $bucketInfo.NotifiedAt = (Gavimo data). ToString("mmmm-MM-dd HH:mm:ss")                 Write-Log "INFO"                 Write-Log "========== DALINIS NAUJINIMAS UŽBLOKUOTOJE TALPYKLOJE ==========" "INFO"                 Write-Log "Bucket: $bucketKey" "INFO"                 $updatedSample = @($updatedDevices | Select-Object -First $LogSampleSize)                 $pendingSample = @($stillPendingDevices | Select-Object -Pirmasis $LogSampleSize)                 $updatedSuffix = if ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count – $LogSampleSize) daugiau)" } dar { "" }                 $pendingSuffix = if ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count – $LogSampleSize) daugiau)" } dar { "" }                 Write-Log "Atnaujinti įrenginiai ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "OK"                 Write-Log "Still pending ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN"                 Write-Log "INFO"                 Write-Log "To manually unblock this bucket after verification, run:" "INFO"                 Write-Log ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'" "INFO"                 Write-Log "=======================================================" "INFO"                 Write-Log "INFO"             }         }     }     grąžinimo $autoUnblocked }                                                                                          

# ============================================================================ # WAVE GENERATION (INLINED - neapima užblokuotų talpyklų) # ============================================================================

function New-RolloutWave {     param(         [eilutė]$AggregationPath,         $BlockedBuckets,         $RolloutState,         [sveikasis skaičius]$MaxDevicesPerWave = 50,         [string[]]$AllowedHostnames = @(),         [eilutė[]$ExcludedHostnames = @()     )     # Įkelti agregavimo duomenis     $notUptodateCsv = Get-ChildItem -Path $AggregationPath -Filter "*NotUptodate*.csv" |          Where-Object { $_. Pavadinimas -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object – pirmas 1     jei (-not $notUptodateCsv) {         Write-Log "NotUptodate CSV nerasta" "KLAIDA"         grąžinimo $null     }     $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName)     # Normalize HostName -> Hostname for consistency (CSV naudoja HostName, kodas naudoja Hostname)     foreach ($device in $allNotUpdated) {         jei ($device. PSObject.Properties['HostName'], o ne $device. PSObject.Properties['Hostname']) {             $device | Add-Member -NotePropertyName Hostname -NotePropertyValue $device. HostName – priverstinai         }     }     # Išfiltruokite užblokuotas talpyklas     $eligibleDevices = @($allNotUpdated | Where-Object {         $bucketKey = Get-BucketKey $_         -not $BlockedBuckets.Contains($bucketKey)     })     # Filtruoti tik leidžiamiems įrenginiams (jei nurodyta AllowList)     # AllowList = tikslinis diegimas – bus svarstomi tik šie įrenginiai     jei ($AllowedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Pagrindinio kompiuterio vardas $AllowedHostnames         })         $allowedCount = $eligibleDevices.Count         Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO"     }     # Filtruoti VIP / neįtrauktus įrenginius (BlockList)     # BlockList taikomas AFTER AllowList     jei ($ExcludedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Pagrindinio kompiuterio vardas –notin $ExcludedHostnames         })         $excludedCount = $beforeCount – $eligibleDevices.count         jei ($excludedCount -gt 0) {             Write-Log "Excluded $excludedCount VIP/protected devices from rollout" "INFO"         }     }     jei ($eligibleDevices.Count -eq 0) {         Write-Log "Nėra likusių tinkamų įrenginių (visi atnaujinti arba užblokuoti)" "Gerai"         grąžinimo $null     }     # Gauti įrenginius, kurie jau įdiegti (iš ankstesnių bangų)     $devicesAlreadyInRollout = @()     jei ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) {         $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object {             $_. Įrenginiai | ForEach-Object { $_. Pagrindinio kompiuterio vardas }         } | Where-Object { $_ })     }     Write-Log "Įrenginiai, kurie jau įdiegti: $($devicesAlreadyInRollout.Count)" "INFO"     # Atskirti patikimumo lygiu     $highConfidenceDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -eq "Didelis pasitikėjimas" - ir         $_. Pagrindinio kompiuterio vardas –notin $devicesAlreadyInRollout     })     # Būtinas veiksmas apima:     # - Aiškus "Būtinas veiksmas"     # – tuščias / nulinis ConfidenceLevel     # – BET kuri nežinoma / neatpažinta ConfidenceLevel reikšmė (laikoma būtinuoju veiksmu)     $knownSafeCategories = @(         "Didelis pasitikėjimas",         "Laikinai pristabdyta",         "Stebima",         "Stebima – reikia daugiau duomenų",         "Nepalaikoma",         "Not Supported - Known Limitation"     )     $actionRequiredDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -notin $knownSafeCategories -and         $_. Hostname -notin $devicesAlreadyInRollout     })     Write-Log "High Confidence (not in rollout): $($highConfidenceDevices.Count)" "INFO"     Write-Log "Būtinas veiksmas (neįdiegtas): $($actionRequiredDevices.Count)" "INFO"     # Komponavimo versijos bangų įrenginiai     $waveDevices = @()     # DIDELIS PASITIKĖJIMAS: įtraukti VISKĄ (saugu įdiegti)     jei ($highConfidenceDevices.Count -gt 0) {         Write-Log "Adding all $($highConfidenceDevices.Count) High Confidence devices" "WAVE"         $waveDevices += $highConfidenceDevices     } # BŪTINA IMTIS VEIKSMŲ: progresyvus diegimas (segmentas pagrįstas OĮG skaidiniu, skirtu nulinio sėkmės talpykloms)     # Strategija:     # - Talpyklos su 0 sėkmingų įvykių: OĮG (po 1 kiekvienam OĮG –> 2 kiekvienam OĮG –> 4 kiekvienam OĮG)     # - Talpyklos su ≥1 sėkmingu: dukart nemokamai be OĮG apribojimų     jei ($actionRequiredDevices.Count -gt 0) {         # Įkėlimo talpyklos sėkmės skaičius iš atnaujintų įrenginių CSV (sėkmingai atnaujinti įrenginiai)         $updatedCsv = Get-ChildItem -Path $AggregationPath -Filter "*updated_devices*.csv" |             Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1         $bucketStats = @{}         jei ($updatedCsv) {             $updatedDevices = Import-Csv $updatedCsv.FullName             # Skaičiuoti sėkmingus pasiekimus per BucketId             $updatedDevices | ForEach-Object {                 $key = Get-BucketKey $_                 jei ($key) {                     jei (-not $bucketStats.ContainsKey($key)) {                         $bucketStats[$key] = @{ Successes = 0; Laukiama = 0; Iš viso = 0 }                     }                     $bucketStats[$key]. Successes++                     $bucketStats[$key]. Total++                 }             }             Write-Log "Įkelti $($updatedDevices.Count) atnaujinti įrenginiai $($bucketStats.Count) talpyklose" "INFO"         } dar {             # Atsarginė priemonė: išbandykite ActionRequired_Buckets CSV             $bucketsCsv = Get-ChildItem -Path $AggregationPath -Filter "*ActionRequired_Buckets*.csv" |                 Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1             jei ($bucketsCsv) {                 Import-Csv $bucketsCsv.FullName | ForEach-Object {                     $key = jei ($_. BucketId) { $_. BucketId } dar { "$($_. Gamintojas)|$($_. Modelis)|$($_. BIOS)" }                     $bucketStats[$key] = @{                         Sėkmės = [int]$_. Sėkmės                         Laukiama = [int]$_. Laukiama                         Iš viso = [sveikasis skaičius]$_. TotalDevices                     }                 }             }         }         # Grupė NotUpdated įrenginiai pagal talpyklą (Gamintojas|Modelis |BIOS)         $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ }         # Atskirkite talpyklas: nulinis sėkmingas ir sėkmingas         $zeroSuccessBuckets = @()         $hasSuccessBuckets = @()         foreach ($bucket in $buckets) {             $bucketKey = $bucket. Vardas, pavadinimas             $bucketDevices = @($bucket. grupė)             $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Pagrindinio kompiuterio vardas })             # Skaičiuoti sėkmingus pasiekimus šioje talpykloje             $stats = $bucketStats[$bucketKey]             $successes = jei ($stats) { $stats. Sėkmingos veiklos } dar { 0 }             # Rasti įrenginius, įdiegtus į šią talpyklą iš bangų istorijos             $deployedToBucket = @()             foreach ($wave in $RolloutState.WaveHistory) {                 ($device $wave. Įrenginiai) {                     jei ($device. BucketKey -eq $bucketKey ir $device. Pagrindinio kompiuterio vardas) {                         $deployedToBucket += $device. Hostname                     }                 }             }             $deployedToBucket = @($deployedToBucket | Sort-Object -Unique)             # Patikrinkite, ar VISI įdiegti įrenginiai pranešė apie sėkmingą sėkmę             $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames })             $confirmedSuccess = $deployedToBucket.Count – $stillPending.Count             # Jei laukiama, praleiskite šią talpyklą, kol visi patvirtins             jei ($stillPending.Count -gt 0) {                 $parts = $bucketKey -split '\|'                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " segmentas: $displayName – deployed=$($deployedToBucket.Count), confirmed=$confirmedSuccess, pending=$($stillPending.Count) (waiting)" "INFO"                 Toliau             }             # Likę tinkami = įrenginiai dar neįdiegti             $devicesNotYetTargeted = @($bucketDevices | Where-Object {                 $_. Hostname -notin $deployedToBucket             })             jei ($devicesNotYetTargeted.Count -eq 0) { continue }             # Skirstyti pagal sėkmingų įvykių skaičių             $bucketInfo = @{                 BucketKey = $bucketKey                 Įrenginiai = $devicesNotYetTargeted                 ConfirmedSuccess = $confirmedSuccess                 Successes = $successes                 OEM = jei ($bucket. Grupė[0]. WMI_Manufacturer) { $bucket. Grupė[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } dar { 'Nežinoma' }             }             jei ($successes -eq 0) {                 $zeroSuccessBuckets += $bucketInfo             } dar {                 $hasSuccessBuckets += $bucketInfo             }         }         # === SĖKMINGO PROCESO TALPYKLOS (≥1 pavyko) ===         # Dvigubas sėkmingų įvykių skaičius – jei pavyko 14, įdiekite 28 kitą         foreach ($bucketInfo in $hasSuccessBuckets) {             $nextBatchSize = $bucketInfo.Successes * 2             $nextBatchSize = [Matematika]:Min($nextBatchSize, $MaxDevicesPerWave)             $nextBatchSize = [Matematika]:Min($nextBatchSize, $bucketInfo.Devices.Count)             jei ($nextBatchSize -gt 0) {                 $selectedDevices = @($bucketInfo.Devices | Select-Object -First $nextBatchSize)                 $waveDevices += $selectedDevices                 $parts = jei ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length))) }                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x confirmed)" "INFO"             }         }         # === APDOROTI NULINIO PASISEKIMO TALPYKLAS (paskirstyti tarp OĮG su OĮG sekimu) ===         # Tikslas: rizikos skleidimas skirtinguose OĮG, atskirai stebėti OĮG eigą         # Kiekvienas OĮG vykdomas atsižvelgiant į jo sėkmingų įvykių istoriją:         # - OĮG su sėkmės: gauna daugiau įrenginių kitą bangą (2^waveCount)         # – OĮG be sėkmingų įvykių: likite dabartiniame lygyje, kol pasiseks         jei ($zeroSuccessBuckets.Count -gt 0) {             # Inicijuoti OĮG bangų skaičių, jei toks nėra             jei (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             # Grupės nepavykę segmentai pagal OĮG             $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OĮG }             $totalZeroSuccessAdded = 0             $oemsDeployedTo = @()             foreach ($oemGroup in $oemBuckets) {                 $oemName = $oemGroup.Name                 # Gauti šio OĮG bangų skaičių (prasideda nuo 0)                 $oemWaveCount = jei ($RolloutState.OEMWaveCounts[$oemName]) {                     $RolloutState.OEMWaveCounts[$oemName]                 Dar } { 0 }                 # Skaičiuoti šio OĮG įrenginius: 2^waveCount (1, 2, 4, 8...)                 $devicesForThisOEM = [int][Matematika]::P ow(2, $oemWaveCount)                 $devicesForThisOEM = [Matematika]::Max(1, $devicesForThisOEM)                 $oemDevicesAdded = 0                 # Pasirinkite iš kiekvienos talpyklos šiame OĮG                 foreach ($bucketInfo in $oemGroup.Group) {                     $remaining = $devicesForThisOEM - $oemDevicesAdded                     jei ($remaining -le 0) { break }                     $toTake = [Matematika]:Min($remaining, $bucketInfo.Devices.Count)                     jei ($toTake -gt 0) {                         $selectedDevices = @($bucketInfo.Devices | Select-Object -First $toTake)                         $waveDevices += $selectedDevices                         $oemDevicesAdded += $toTake                         $totalZeroSuccessAdded += $toTake                         $parts = jei ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length))) }                         $displayName = "$($parts[0]) - $($parts[1])"                         Write-Log " [ZERO-SUCCESS] $displayName - Deploying=$toTake (OEM wave $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN"                     }                 }                 jei ($oemDevicesAdded -gt 0) {                     Write-Log " OEM: $oemName - Wave $oemWaveCount, Added $oemDevicesAdded devices" "INFO"                     $oemsDeployedTo += $oemName                 }             }             # Sekti, į kuriuos OĮG įdiegėme (norint padidinti kitą sėkmingą patikrą)             jei ($oemsDeployedTo.Count -gt 0) {                 $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo                 Write-Log "Zero-success deployment: $totalZeroSuccessAdded devices across $($oemsDeployedTo.Count) OĮG" "INFO"             }         }     }     jei (@($waveDevices). Skaičius -eq 0) {         grąžinimo $null     }     grąžinimo $waveDevices }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

# ============================================================================ # GPO DEPLOYMENT (INLINED - sukuria GPO, saugos grupę, saitus) # ============================================================================

function Deploy-GPOForWave {     param(         [eilutė]$GPOName,         [eilutė]$TargetOU,         [eilutė]$SecurityGroupName,         [masyvas]$WaveHostnames,         [bulio logika]$DryRun = $false     )     # ADMX strategija: SecureBoot.admx - SecureBoot_AvailableUpdatesPolicy     # Registro kelias: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot     # reikšmės pavadinimas: AvailableUpdatesPolicy     # Enabled Value: 22852 (0x5944) - Atnaujinti visus saugiosios įkrovos kodus + bootmgr     # Išjungta reikšmė: 0     2010     # Using Grupės strategija Preferences (GPP) for reliable HKLM\SYSTEM path deployment     # GPP sukuria parametrus dalyje: Computer Configuration > Preferences > Windows Settings > Registry     $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot"     $RegistryValueName = "AvailableUpdatesPolicy"     $RegistryValue = 22852 # 0x5944 – atitinka ADMX enabledValue     Write-Log "GPO diegimas: $GPOName" WAVE"     Write-Log "Registras: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X')))" "INFO"     jei ($DryRun) {         Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Pridės $(@($WaveHostnames). Skaičius) kompiuterių grupavimas "INFO"         Write-Log "[DRYRUN] susies GPO su: $TargetOU" "INFO"         grąžinimo $true     }     išbandykite {         # Importuoti reikiamus modulius         Import-Module GroupPolicy - ErrorAction Stop         Import-Module ActiveDirectory - ErrorAction Stop     } sugauti {         Write-Log "Nepavyko importuoti būtinų modulių (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         grąžinimo $false     }     # 1 veiksmas: sukurkite arba gaukite GPO     $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     jei ($existingGPO) {         Write-Log "GPO jau yra: $GPOName" "INFO"         $gpo = $existingGPO     } dar {         išbandykite {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Sukurtas GPO: $GPOName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR"             grąžinimo $false         }     }     # 2 veiksmas: nustatykite registro reikšmę naudodami Grupės strategija Preferences" (GPP)     # GPP yra patikimesnis HKLM\SYSTEM keliams nei Set-GPRegistryValue     išbandykite {         # Pirmiausia pabandykite pašalinti bet kurią esamą šios reikšmės nuostatą (kad išvengtumėte dublikatų)         Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue         # Sukurti GPP registro nuostatą su veiksmu "Pakeisti"         # Pakeisti = Kurti, jei nėra, naujinti, jei toks yra (patikimiausias)         # Naujinimas = tik naujinimas, jei toks yra (nepavyksta, jei reikšmės nėra)         Set-GPPrefRegistryValue pavadinimas $GPOName '             -Konteksto kompiuteris '             -Veiksmo keitimas '             - Rakto $RegistryKey '             -ValueName $RegistryValueName '             -Įveskite DWord '             -Reikšmės $RegistryValue         Write-Log "Configured GPP registry preference: $RegistryValueName = 0x5944 (Action=Replace)" "OK"     } sugauti {         Write-Log "GPP nepavyko, bandoma Set-GPRegistryValue: $($_. Exception.Message)" "WARN"         # Grįžti į Set-GPRegistryValue (veikia, jei įdiegtas ADMX)         išbandykite {             Set-GPRegistryValue - pavadinimas $GPOName '                 -Rakto $RegistryKey '                 -ValueName $RegistryValueName '                 -Įveskite DWord '                 Reikšmės $RegistryValue             Write-Log "Sukonfigūruotas registras naudojant Set-GPRegistryValue: $RegistryValueName = 0x5944" "Gerai"         } sugauti {             Write-Log "Nepavyko nustatyti registro reikšmės: $($_. Exception.Message)" "ERROR"             grąžinimo $false         }     }     # 3 veiksmas: saugos grupės kūrimas arba gavimas     $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     jei (-not $existingGroup) {         išbandykite {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Sauga '                 -GroupScope DomainLocal '                 -Aprašas "Kompiuteriai, skirti saugiosios įkrovos išleidimą - $GPOName" '                 -PassThru             Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR"             grąžinimo $false         }     } dar {         Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO"         $group = $existingGroup     }     # 4 veiksmas: kompiuterių įtraukimas į saugos grupę     $added = 0     $failed = 0     foreach ($hostname in $WaveHostnames) {         išbandykite {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue             $added++         } sugauti {             $failed++         }     }     Write-Log "Įtraukti $added kompiuteriai į saugos grupę ($failed nerasta AD)" "Gerai"     # 5 veiksmas: konfigūruokite GPO saugos filtravimą     išbandykite {         # Pašalinti numatytąją parinktį "Autentifikuoti vartotojai" Taikyti teises (toliau skaityti)         Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         # Įtraukti teisę taikyti mūsų saugos grupei         Set-GPPermission – pavadinimas $GPOName – TargetName $SecurityGroupName – TargetType grupė – PermissionLevel GpoApply – ErrorAction stabdyti         Write-Log "Konfigūruotas saugos filtravimas: $SecurityGroupName"     } sugauti {         Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN"         Write-Log "GPO gali būti taikomas visiems kompiuteriams, susieti OU - patikrinti rankiniu būdu" "WARN"     }     # 6 veiksmas: susieti GPO su OU (LABAI SVARBU, kad strategija būtų taikoma)     jei ($TargetOU) {         išbandykite {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             jei (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop                 Write-Log "Susietas GPO su: $TargetOU" "Gerai"                 Write-Log "GPO bus taikomas kitame gpupdate tiksliniuose kompiuteriuose" "INFO"             } dar {                 Write-Log "GPO jau susietas su paskirties OU" "INFO"             }         } sugauti {             Write-Log "KRITINIS: nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR"             Write-Log "GPO buvo sukurtas, bet NE SUSIETAS – jis NEBUS taikomas jokiems kompiuteriams!" "KLAIDA"             Write-Log "Būtina taisyti rankiniu būdu: New-GPLink – pavadinimas "$GPOName" – tikslinė "$TargetOU" - "LinkEnabled" taip" "KLAIDA"             grąžinimo $false         }     } dar {         Write-Log "ĮSPĖJIMAS: Nenurodytas TargetOU – GPO sukurtas, bet NE SUSIETAS!" "KLAIDA"         Write-Log "Rankinis susiejimas būtinas, kad GPO įsigaliotų" "KLAIDA"         Write-Log "Run: New-GPLink -Name '$GPOName' -Target '<Your-Domain-DN>' -LinkEnabled Yes" "ERROR"     }     # 7 veiksmas: patikrinkite GPO konfigūraciją     Write-Log "Tikrinama GPO konfigūracija..." "INFO"     išbandykite {         $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop         Write-Log "GPO būsena: $($gpoReport.GpoStatus)" "INFO"         # Patikrinkite, ar sukonfigūruotas registro parametras         $regSettings = Get-GPRegistryValue - Name $GPOName - Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue         jei (-not $regSettings) {             # Išbandykite GPP registro patikrą (kitas kelias GPO)             Write-Log "GPP registro nuostatų tikrinimas..." "INFO"         }     } sugauti {         Write-Log "Nepavyko patikrinti GPO: $($_. Exception.Message)" "WARN"     }     grąžinimo $true }                                                                                                

# ============================================================================ # WINCS DEPLOYMENT ("AvailableUpdatesPolicy GPO" alternatyva) #============================================================================ Nr. nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe 2010 # WinCS komandos (vykdyti galinio punkto sistemos kontekste): # užklausa: WinCsFlags.exe /query – rakto F33E0C8E002 # Taikyti: WinCsFlags.exe /apply --key "F33E0C8E002" # Reset: WinCsFlags.exe /reset --key "F33E0C8E002" 2010 # Šis metodas įdiegia GPO su suplanuota užduotimi, kuri vykdo WinCsFlags.exe /apply # kaip SYSTEM tiksliniuose pabaigos taškuose. Panašiai kaip diegiamas aptikimo scenarijus, #, bet paleidžiama vieną kartą (paleisties metu), o ne kasdien.

function Deploy-WinCSGPOForWave {     <#     . ANOTACIJA         Įdiekite "WinCS" saugiosios įkrovos įgalinimą per GPO suplanuotą užduotį.. APRAŠYMAS / KONTROLĖ         Sukuria GPO, kuris įdiegia suplanuotą užduotį vykdyti WinCsFlags.exe /apply         sistemos kontekste kompiuterio paleisties metu. Taikoma saugos grupės valdikliams.. PARAMETRO GPOName         GPO pavadinimas.. PARAMETER TargetOU         OU, kad susietumėte SU GPO.. PARAMETER SecurityGroupName         GPO filtravimo saugos grupė.. PARAMETER WaveHostnames         Pagrindinio kompiuterio vardai, įtraukti į saugos grupę.. PARAMETER WinCSKey         Taikomas "WinCS" raktas (numatytasis: F33E0C8E002).. PARAMETER DryRun         Jei teisinga, rašykite tik tai, ką reikėtų padaryti.#>     param(         [Parametras(Privalomas = $true)]         [eilutė]$GPOName,                  [Parametras(Privalomas = $false)]         [eilutė]$TargetOU,                  [Parametras(Privalomas = $true)]         [eilutė]$SecurityGroupName,                  [Parametras(Privalomas = $true)]         [masyvas]$WaveHostnames,                  [Parametras(Privalomas = $false)]         [eilutė]$WinCSKey = "F33E0C8E002",                  [Parametras(Privalomas = $false)]         [bulio logika]$DryRun = $false     )          # suplanuotos užduoties konfigūracija, skirta WinCsFlags.exe     $TaskName = "SecureBoot-WinCS-Apply"     $TaskPath = "\Microsoft\Windows\SecureBoot\"     $TaskDescription = "Taiko saugiosios įkrovos konfigūraciją per WinCS – raktas: $WinCSKey"          Write-Log "WinCS GPO diegimas: $GPOName" WAVE"     Write-Log "Užduotis bus vykdoma: WinCsFlags.exe /apply --key ""$WinCSKey"" "INFO"     Write-Log "Trigger: At system startup (runs once as SYSTEM)" "INFO"          jei ($DryRun) {         Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] pridės $(@($WaveHostnames). Skaičius) kompiuterių grupavimas "INFO"         Write-Log [DRYRUN] Įdiegtų suplanuotą užduotį: $TaskName" "INFO"         Write-Log "[DRYRUN] susies GPO su: $TargetOU" "INFO"         grąžinti @{             Success = $true             GPOCreated = $false             GroupCreated = $false             KompiuteriaiĮtraukti = 0         }     }          išbandykite {         # Importuoti reikiamus modulius         Import-Module GroupPolicy - ErrorAction Stop         Import-Module ActiveDirectory - ErrorAction Stop     } sugauti {         Write-Log "Nepavyko importuoti būtinų modulių (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Klaida = $_. Exception.Message }     }          # 1 veiksmas: sukurkite arba gaukite GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     jei ($gpo) {         Write-Log "GPO jau yra: $GPOName" "INFO"     } dar {         išbandykite {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Sukurtas GPO: $GPOName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = $_. Exception.Message }         }     }          # 2 veiksmas: GPO diegimo suplanuotos užduoties XML kūrimas     # Tai sukuria užduotį, kuri paleidžia WinCsFlags.exe /apply paleisties metu     $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">   <RegistrationInfo>     <aprašo>$TaskDescription</aprašo>     WinCsFlags.exe1 author>SYSTEM</Author>   WinCsFlags.exe5 /RegistrationInfo>   WinCsFlags.exe7 paleidikliai>     WinCsFlags.exe9 BootTrigger>       <Įgalintas>teisingas</ įgalintas>       <delsos>PT5M</ delsos>     </BootTrigger>  >< / paleidikliai   <principals>     <Principal id="Author">       <UserId>S-1-5-18</UserId>       <RunLevel>HighestAvailable</RunLevel>     </Principal>   </Principals>   parametrų><     <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>     <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>     <StopIfGoingOnBatteries>klaidingas</StopIfGoingOnBatteries>     <AllowHardTerminate>true</AllowHardTerminate>     <PradėtiKasgalima>true</StartWhenAvailable>     <RunOnlyIfNetworkAvailable>klaidingas</RunOnlyIfNetworkAvailable>     <IdleSettings>       <StopOnIdleEnd>false</StopOnIdleEnd>       <RestartOnIdle>false</RestartOnIdle>     </IdleSettings>     <AllowStartOnDemand>true</AllowStartOnDemand>     <Įgalintas>teisingas</ įgalintas>     <paslėptas>klaidingas</paslėptas>     <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 7 prioriteto></ prioriteto>   WinCsFlags.exe27 /Settings>   WinCsFlags.exe29 Actions Context="Author"WinCsFlags.exe30     WinCsFlags.exe31 Exec>       WinCsFlags.exe33 Command>WinCsFlags.exe</Command>       WinCsFlags.exe37 argumentai>/apply --key "$WinCSKey"WinCsFlags.exe39 /Arguments>     WinCsFlags.exe41 /Exec>   WinCsFlags.exe43 /Actions> WinCsFlags.exe45 / užduočių> " @

    # Step 3: Deploy scheduled task via GPO Preferences     # Saugoti užduoties XML SYSVOL GPO suplanuotų užduočių tiesioginėms užduotims     išbandykite {         $gpoId = $gpo. Id.ToString()         $sysvolPath = "\\$((Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Policies\{$gpoId}\Machine\Preferences\ScheduledTasks"         jei (-not (testo kelias $sysvolPath)) {             New-Item -ItemType katalogas -kelias $sysvolPath -Force | Nu neapibrėžta reikšmė (Out-Null)         }         # Sukurkite GPP ScheduledTasks.xml         $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">   <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName "image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U">       <Task version="1.3">         <RegistrationInfo>           <aprašo>$TaskDescription</aprašo>         </RegistrationInfo>         <principals>           <Principal id="Author">             <UserId>NT AUTHORITY\System</UserId>             <LogonType>S4U</LogonType>             <RunLevel>HighestAvailable</RunLevel>           </ pagrindinis>         </Principals>         parametrų><           <IdleSettings>             <trukmė>PT5M</Duration>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle">klaidingas</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <PradėtiKasgalima>true</StartWhenAvailable>           <AllowStartOnDemand>true</AllowStartOnDemand>           <Įgalintas>true</Enabled>           <paslėptas>klaidingas</paslėptas>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <Priority>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </ parametrų>         <paleidiklių>           <TimeTrigger>             <StartBoundary>$(Get-Date -Format 'yyyy-MM-dd')T00:00:00</StartBoundary>             <Įgalintas>teisingas</ įgalintas>           </TimeTrigger>         </Triggers>         veiksmų><           <Exec>             <Command>WinCsFlags.exe</Command>             <argumentai>/apply --key "$WinCSKey"</Arguments>           </Exec>         </Actions>       </ užduočių>     <apgyvendinimo įstaigos>   </ImmediateTaskV2> </ScheduledTasks> "@         $gppTaskXml | Out-File -FilePath (sujungimo kelio $sysvolPath "ScheduledTasks.xml") - Utf8 kodavimas -Force         Write-Log "Deployed scheduled task to GPO: $TaskName" "OK"     } sugauti {         Write-Log "Nepavyko įdiegti suplanuotos užduoties XML: $($_. Exception.Message)" "WARN"         Write-Log "Grįžimas prie registru pagrįsto "WinCS" diegimo" "INFO"         # Atsarginė: Naudoti WinCS registro metodas, jei GPP suplanuota užduotis nepavyksta         # WinCS taip pat gali būti paleistas per registro raktą         # (Diegimas priklauso nuo "WinCS" registro API, jei yra)     }     # 4 veiksmas: saugos grupės kūrimas arba gavimas     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     jei (-not $group) {         išbandykite {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Sauga '                 -GroupScope DomainLocal '                 -Aprašymas "Kompiuteriai, skirti saugiosios įkrovos WinCS rollout - $GPOName" '                 -PassThru             Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = $_. Exception.Message }         }     } dar {         Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO"     }     # 5 veiksmas: kompiuterių įtraukimas į saugos grupę     $added = 0     $failed = 0     foreach ($hostname in $WaveHostnames) {         išbandykite {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue             $added++         } sugauti {             $failed++         }     }     Write-Log "Įtraukti $added kompiuteriai į saugos grupę ($failed nerasta AD)" "Gerai"     # 6 veiksmas: konfigūruokite GPO saugos filtravimą     išbandykite {         Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission – pavadinimas $GPOName – TargetName $SecurityGroupName – TargetType grupė – PermissionLevel GpoApply – ErrorAction – sustabdymas         Write-Log "Konfigūruotas saugos filtravimas: $SecurityGroupName"     } sugauti {         Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN"     }     # 7 veiksmas: susieti GPO su OU     jei ($TargetOU) {         išbandykite {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             jei (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop                 Write-Log "Susietas GPO su: $TargetOU" "Gerai"             } dar {                 Write-Log "GPO jau susietas su paskirties OU" "INFO"             }         } sugauti {             Write-Log "KRITINIS: nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = "GPO saitas nepavyko: $($_. Exception.Message)" }         }     }     Write-Log "WinCS GPO diegimas baigtas" "Gerai"     Write-Log "Machines will run WinCsFlags.exe at next GPO refresh + reboot/startup" "INFO"     grąžinti @{         Success = $true         GPOCreated = $true         GroupCreated = $true         ComputersAdded = $added         ComputersFailed = $failed     } }                                                                                        

# Wrapper function to maintain compatibility with main loop funkcija Deploy-WinCSForWave {     param(         [Parametras(Privalomas = $true)]         [masyvas]$WaveHostnames,         [Parametras(Privalomas = $false)]         [eilutė]$WinCSKey = "F33E0C8E002",         [Parametras(Privalomas = $false)]         [eilutė]$WavePrefix = "SecureBoot-Rollout",         [Parametras(Privalomas = $false)]         [sveikasis skaičius]$WaveNumber = 1,         [Parametras(Privalomas = $false)]         [eilutė]$TargetOU,         [Parametras(Privalomas = $false)]         [bulio logika]$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     # Konvertuoti į numatomą grąžinimo formatą     grąžinti @{         Success = $result. Sėkmės         Pritaikė = $result. Įtraukti kompiuteriai         Praleista = 0         Nepavyko = jei ($result. Kompiuteriai nepavyko) { $result. Kompiuteriai nepavyko } dar { 0 }         Rezultatai = @()     } }                                                            

# ============================================================================ # ĮGALINTI UŽDUOTIES DIEGIMĄ #============================================================================ # Visuotinai diegti Enable-SecureBootUpdateTask.ps1 įrenginiuose, kuriuose išjungta suplanuota užduotis.# Naudoja GPO su nedelsiant vykdoma suplanuota užduotimi, kuri vykdoma vieną kartą.

function Deploy-EnableTaskGPO {     <#     . ANOTACIJA         Diegti Enable-SecureBootUpdateTask.ps1 naudojant GPO suplanuotą užduotį.. APRAŠYMAS / KONTROLĖ         Sukuria GPO, kuris įdiegia vienkartinę suplanuotą užduotį, kad įjungtų         Saugiosios įkrovos naujinimo suplanuota užduotis tiksliniuose įrenginiuose.. PARAMETER TargetOU         OU, kad susietumėte SU GPO.. PARAMETER TargetHostnames         Įrenginių su išjungta užduotimi pagrindinio kompiuterio vardai (iš agregavimo ataskaitos).. PARAMETER DryRun         Jei teisinga, rašykite tik tai, ką reikėtų padaryti.#>     param(         [Parametras(Privalomas = $false)]         [eilutė]$TargetOU,                  [Parametras(Privalomas = $true)]         [masyvas]$TargetHostnames,                  [Parametras(Privalomas = $false)]         [bulio logika]$DryRun = $false     )          $GPOName = "SecureBoot-EnableTask-Remediation"     $SecurityGroupName = "SecureBoot-EnableTask-Devices"     $TaskName = "SecureBoot-EnableTask-OneTime"     $TaskDescription = "Vienkartinė užduotis įgalinti saugiosios įkrovos naujinimo suplanuotą užduotį"          Write-Log "=" * 70 "INFO"     Write-Log "DEPLOYING ENABLE TASK REMEDIATION" "INFO"     Write-Log "=" * 70 "INFO"     Write-Log "Paskirties įrenginiai: $($TargetHostnames.Count)" "INFO"     Write-Log "GPO: $GPOName" "INFO"     Write-Log "Saugos grupė: $SecurityGroupName" "INFO"          jei ($DryRun) {         Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Pridės $($TargetHostnames.Count) kompiuterius prie grupės "INFO"         Write-Log "[DRYRUN] Įdiegtų vienkartinę suplanuotą užduotį, kad įgalintų saugiosios įkrovos naujinimą" "INFO"         Write-Log "[DRYRUN] Susies GPO su: $TargetOU" "INFO"         grąžinti @{             Success = $true             KompiuteriaiĮtraukti = 0             DryRun = $true         }     }          išbandykite {         # Importuoti reikiamus modulius         Import-Module GroupPolicy - ErrorAction Stop         Import-Module ActiveDirectory - ErrorAction Stop     } sugauti {         Write-Log "Nepavyko importuoti būtinų modulių: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Klaida = $_. Exception.Message }     }          # 1 veiksmas: sukurkite arba gaukite GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     jei ($gpo) {         Write-Log "GPO jau yra: $GPOName" "INFO"     } dar {         išbandykite {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Task Enable Remediation - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "Sukurtas GPO: $GPOName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = $_. Exception.Message }         }     }          # 2 veiksmas: įdiekite suplanuotos užduoties XML GPO SYSVOL     # Užduotis vykdo "PowerShell" komandą, kad įgalintų saugiosios įkrovos naujinimo užduotį     išbandykite {         $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks"                  jei (-not (testo kelias $sysvolPath)) {             New-Item -ItemType katalogas -Kelias $sysvolPath -Force | Nu neapibrėžta reikšmė (Out-Null)         }                  # PowerShell command to enable the Secure-Boot-Update task         $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 }'                  #Encode command for safe XML embedding         $encodedCommand = [Konvertuoti]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableCommand))                  $taskGuid = [guid]::NewGuid(). ToString("B"). Priseku()                  # GPP suplanuotos užduoties XML – tiesioginė užduotis, vykdoma vieną kartą         $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=""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>           <aprašo>$TaskDescription</aprašo>         </RegistrationInfo>         <principals>           <Principal id="Author">             <UserId>S-1-5-18</UserId>             <RunLevel>HighestAvailable</RunLevel>           </Principal>         </Principals>         parametrų><           <IdleSettings>             <trukmė>PT5M</ trukmės>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle>false</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries>klaidingas</StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <PradėtiKokausti>true</StartWhenAvailable>           <AllowStartOnDemand>true</AllowStartOnDemand>           <Įgalintas>teisingas</ įgalintas>           <paslėptas>klaidingas</paslėptas>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <priority>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </ parametrų>         veiksmų><           <Exec>             <Command>powershell.exe</Command>             <argumentai>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Arguments>           </Exec>         </Actions>       </ užduočių>     <apgyvendinimo įstaigos>   </ImmediateTaskV2> </ScheduledTasks> "@                  $gppTaskXml | Out-File -FilePath (prisijungimo kelio $sysvolPath "ScheduledTasks.xml") - Utf8 kodavimas -Force         Write-Log "Įdiegta vienkartinė suplanuota užduotis GPO: $TaskName" "Gerai"              } sugauti {         Write-Log "Nepavyko įdiegti suplanuotos užduoties XML: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Klaida = $_. Exception.Message }     }          # 3 veiksmas: saugos grupės kūrimas arba gavimas     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     jei (-not $group) {         išbandykite {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Sauga '                 -GroupScope DomainLocal '                 -Aprašas "Kompiuteriai su išjungta saugiosios įkrovos naujinimo užduotimi – tikslinis taisymas" "                 -PassThru             Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai"         } sugauti {             Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = $_. Exception.Message }         }     } dar {         Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO"     }          # 4 veiksmas: kompiuterių įtraukimas į saugos grupę     $added = 0     $failed = 0     foreach ($hostname in $TargetHostnames) {         išbandykite {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue             $added++         } sugauti {             $failed++             Write-Log "Computer not found in AD: $hostname" "WARN"         }     }     Write-Log "$added kompiuteriai įtraukti į saugos grupę ($failed nerasta AD)" "Gerai"          # 5 veiksmas: konfigūruokite GPO saugos filtravimą     išbandykite {         Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Konfigūruotas saugos filtravimas, skirtas: $SecurityGroupName" "Gerai"     } sugauti {         Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN"     }          # 6 veiksmas: susieti GPO su OU     jei ($TargetOU) {         išbandykite {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }                          jei (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop                 Write-Log "Susietas GPO su: $TargetOU" "Gerai"             } dar {                 Write-Log "GPO jau susietas su paskirties OU" "INFO"             }         } sugauti {             Write-Log "Nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Klaida = "GPO saitas nepavyko: $($_. Exception.Message)" }         }     } dar {         Write-Log "No TargetOU specified - GPO will be be manually linked" "WARN"     }          Write-Log "INFO"     Write-Log "ENABLE TASK DEPLOYMENT COMPLETE" (ĮGALINTI UŽDUOTIES DIEGIMĄ BAIGTAS) "OK"     Write-Log "Įrenginiai paleis įgalintą užduotį kito GPO atnaujinimo metu (gpupdate)" "INFO"     Write-Log "Užduotis vykdoma vieną kartą kaip SISTEMA ir įgalina saugiosios įkrovos naujinimą" "INFO"     Write-Log "INFO"          grąžinti @{         Success = $true         ComputersAdded = $added         ComputersFailed = $failed         GPOName = $GPOName         SecurityGroup = $SecurityGroupName     } }

# ============================================================================ # ĮGALINTI UŽDUOTĮ IŠJUNGTOSE ĮRENGINIUOSE #============================================================================ jei ($EnableTaskOnDisabled) {     Write-Host     Write-Host ("=" * 70) – priekinio planopalva geltona     Write-Host " ENABLE TASK REMEDIATION - Fixing Disabled Scheduled Tasks" -ForegroundColor Yellow     Write-Host ("=" * 70) – geltona priekinio plano spalva     Write-Host     # Rasti įrenginius, kuriuose išjungta užduotis iš agregavimo duomenų     jei (-not $AggregationInputPath) {         Write-Host "KLAIDA: -AggregationInputPath reikia identifikuoti įrenginius su išjungta užduotimi" -ForegroundColor Red         Write-Host "Naudojimas: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <kelias> -ReportBasePath <kelias>" -ForegroundColor Gray         išeiti iš 1     }     Write-Host "Nuskaitomi įrenginiai, kuriuose išjungta saugiosios įkrovos naujinimo užduotis..." -ForegroundColor Cyan     # Įkelti JSON failus ir rasti įrenginius su išjungta užduotimi     $jsonFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue |                  Where-Object { $_. Pavadinimas -notmatch "ScanHistory|RolloutState|RolloutPlan" }     $disabledTaskDevices = @()     foreach ($file in $jsonFiles) {         išbandykite {             $device = Get-Content $file. FullName – žalias | KonvertuotiFrom-Json             jei ($device. SecureBootTaskEnabled - eq $false arba                 $device. SecureBootTaskStatus - eq išjungtas arba                 $device. SecureBootTaskStatus -eq 'NotFound') {                 # Įtraukti tik tuos įrenginius, kurie dar neatnaujinti (nėra 1808 įvykio)                 jei ([int]$device. Event1808Count -eq 0) {                     $disabledTaskDevices += $device. Hostname                 }             }         } sugauti {             # Praleisti neleistinus failus         }     }     $disabledTaskDevices = $disabledTaskDevices | Select-Object unikalus     jei ($disabledTaskDevices.Count -eq 0) {         Write-Host         Write-Host "Nerasta įrenginių, kuriuose išjungta saugiosios įkrovos naujinimo užduotis." -Priekinio planopalva Žalia         Write-Host "Visi įrenginiai turi įgalintą arba jau atnaujintą užduotį". -ForegroundColor Gray         išeiti iš 0     }     Write-Host     Write-Host "Rasta $($disabledTaskDevices.Count) įrenginių su išjungta užduotimi:" -Priekinio planopalva geltona     $disabledTaskDevices | Select-Object pirmosios 20 | ForEach-Object { Write-Host " - $_" -Priekinio planopalva Pilka }     jei ($disabledTaskDevices.Count -gt 20) {         Write-Host " ... ir $($disabledTaskDevices.Count – 20) daugiau" -Priekinio plano spalva Pilka     }     Write-Host     # Visuotinis užduoties įgalinimas GPO     $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun     jei ($result. Pavyko) {         Write-Host         Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green         Write-Host " Kompiuteriai, įtraukti į saugos grupę: $($result. ComputersAdded)" -ForegroundColor Cyan         jei ($result. Kompiuteriai nepavyko -gt 0) {             Write-Host " Ad kompiuterių nerasta: $($result. ComputersFailed)" -ForegroundColor Yellow         }         Write-Host         Write-Host "KITI VEIKSMAI:" - Priekinio planopalva balta         Write-Host " 1.                                              Įrenginiai gaus GPO kitą kartą atnaujinus (gpupdate /force)" -ForegroundColor Gray         Write-Host " 2. Vienkartinė užduotis įgalins saugiosios įkrovos naujinimą "-Priekinio planopalva pilka         Write-Host " 3. Iš naujo paleiskite agregavimą, kad patikrintumėte, ar užduotis dabar įgalinta" -ForegroundColor Gray     } dar {         Write-Host         Write-Host "NEPAVYKO: nepavyko įdiegti įgalinti užduoties GPO" -Priekinio planopalva raudona         Write-Host "Klaida: $($result. Klaida)" -Priekinio planopalva raudona     }          išeiti iš 0 }

# ============================================================================ # MAIN ORCHESTRATION LOOP # ============================================================================

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

if ($DryRun) {     Write-Host "[SAUSO VYKDYMO REŽIMAS]" - Priekinio planocolor purpurinė }

if ($UseWinCS) {     Write-Host "[WinCS REŽIMAS]" - priekinio planopalva geltona     Write-Host "Using WinCsFlags.exe instead of GPO/AvailableUpdatesPolicy" -ForegroundColor Yellow     Write-Host "WinCS raktas: $WinCSKey" - Priekinio planopalva pilka     Write-Host }

Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Įvesties kelias: $AggregationInputPath" "INFO" Write-Log "Ataskaitos kelias: $ReportBasePath" "INFO" jei ($UseWinCS) {     Write-Log "Diegimo metodas: WinCS (WinCsFlags.exe /apply --key ""$WinCSKey")" "INFO" } dar {     Write-Log "Diegimo metodas: GPO (AvailableUpdatesPolicy)" "INFO" }

# Resolve TargetOU - default to domain root for domain-wide coverage # Reikia tik GPO diegimo metodui ("WinCS" nereikia AD / GPO) jei (-not $UseWinCS -and -not $TargetOU) {     išbandykite {         # Išbandykite kelis būdus, kad gautumėte domeno DN         $domainDN = $null         # 1 metodas: Get-ADDomain (būtina RSAT-AD-PowerShell)         išbandykite {             Import-Module ActiveDirectory - ErrorAction Stop             $domainDN = (Get-ADDomain -ErrorAction Stop). Išskirtinis vardas         } sugauti {             Write-Log "Get-ADDomain nepavyko: $($_. Exception.Message)" "WARN"         }         # 2 metodas: Naudokite RootDSE per ADSI         jei (-not $domainDN) {             išbandykite {                 $rootDSE = [ADSI]"LDAP://RootDSE"                 $domainDN = $rootDSE.defaultNamingContext.ToString()             } sugauti {                 Write-Log "ADSI RootDSE nepavyko: $($_. Exception.Message)" "WARN"             }         }         # 3 būdas: išanalizuoti iš kompiuterio domeno narystės         jei (-not $domainDN) {             išbandykite {                 $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()                 $domainDN = "DC=" + ($domain. Name -replace '\.', ',DC=')             } sugauti {                 Write-Log "GetComputerDomain nepavyko: $($_. Exception.Message)" "WARN"             }         }         jei ($domainDN) {             $TargetOU = $domainDN             Write-Log "Target: Domain Root ($domainDN) - GPO will apply domain-wide via security group filtering" "INFO"         } dar {             Write-Log "Nepavyko nustatyti domeno DN – GPO bus sukurtas, bet NESUSIETAS!" "KLAIDA"             Write-Log "Nurodykite -TargetOU parametrą arba susiekite GPO rankiniu būdu po sukūrimo" "KLAIDA"             $TargetOU = $null         }     } sugauti {         Write-Log "Nepavyko gauti domeno DN – GPO bus sukurtas, bet nesusietas.                                     Saitas rankiniu būdu, jei reikia." "WARN"         Write-Log "Klaida: $($_. Exception.Message)" "WARN"         $TargetOU = $null     } } dar {     Write-Log "Target OU: $TargetOU" "INFO" }

Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Apklausos intervalas: $PollIntervalMinutes minutės" "INFO" jei ($LargeScaleMode) {     Write-Log "LargeScaleMode enabled (paketo dydis: $ProcessingBatchSize, žurnalo pavyzdys: $DeviceLogSampleSize)" "INFO" }

# ============================================================================ # BŪTINŲJŲ SĄLYGŲ PATIKRA: patikrinkite, ar aptikimas įdiegtas ir veikia # ============================================================================

Write-Host "" Write-Log "Tikrinamos būtinosios sąlygos..." "INFO"

$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath jei (-not $detectionCheck.IsDeployed) {     Write-Log $detectionCheck.Message "ERROR"     Write-Host     Write-Host "REQUIRED: Deploy detection infrastructure first:" -ForegroundColor Yellow     Write-Host " 1. Vykdyti: Deploy-GPO-SecureBootCollection.ps1 -OUPath OU=...' -OutputPath \\server\SecureBootLogs$" -ForegroundColor Cyan     Write-Host " 2. Palaukite, kol įrenginiai praneš (12–24 valandos)" -ForegroundColor Cyan     Write-Host " 3. Iš naujo paleiskite šį orkestrą" -ForegroundColor Cyan     Write-Host     jei (-not $DryRun) {         Grįžti     } } dar {     Write-Log $detectionCheck.Pranešimas "Gerai" }

# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Duomenų naujumas: $($freshness. TotalFiles) failai, $($freshness. FreshFiles) fresh (<24h), $($freshness. StaleFiles) pasenusi (>72h)" "INFO" jei ($freshness. Įspėjimas) {     Write-Log $freshness. Įspėjimas "WARN" }

# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() jei ($AllowListPath arba $AllowADGroup) {     $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup     jei ($allowedHostnames.Count -gt 0) {         Write-Log "AllowList: ONLY $($allowedHostnames.Count) įrenginiai bus laikomi diegiamais į "INFO"     } dar {         Write-Log "AllowList specified but no devices found - tai blokuos visus keitimus!" "WARN"     } }

# Load VIP/exclusion list (BlockList) $excludedHostnames = @() jei ($ExclusionListPath arba $ExcludeADGroup) {     $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup     jei ($excludedHostnames.Count -gt 0) {         Write-Log "VIP išimtis: $($excludedHostnames.Count) įrenginiai bus praleisti iš "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 "yyyy-MM-dd HH:mm:ss"     Write-Log "Pradedamas naujas diegimas" WAVE" }

Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Užblokuotos talpyklos: $($blockedBuckets.Count)" "INFO"

# Main loop - runs until all eligible devices are updated $iterationCount = 0 o ($true) {     $iterationCount++     Write-Host "     Write-Host (=" * 80) - Priekinio planopalva balta     Write-Log "=== ITERATION $iterationCount ==" "WAVE"     Write-Host ("=" * 80) – priekinio planopalva balta     # 1 veiksmas: agregavimas     Write-Log "1 veiksmas: vykdomas agregavimas..." "INFO"     # Orchestrator visada pakartotinai naudoja vieną aplanką (LargeScaleMode), kad būtų išvengta disko išsipūtimo     # Administratoriai, naudojantys rinkiklį, rankiniu būdu gauna laiko žyma pažymėtus aplankus momentinių taškų     $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current"     # Prieš agreguodami patikrinkite duomenų naujuumą     $freshness = Get-DataFreshness -JsonPath $AggregationInputPath     Write-Log "Duomenų naujumas: $($freshness. FreshFiles)/$($freshness. TotalFiles) įrenginiai, apie kuriuos pranešta paskutinį 24 val. "INFO"     jei ($freshness. Įspėjimas) {         Write-Log $freshness. Įspėjimas "WARN"     }     $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1"     $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json"     $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     if (Test-Path $aggregateScript) {         jei (-not $DryRun) {             # Orchestrator visada naudoja srautinio + papildantysis efektyvumo             # Aggregator auto-elevates to PS7 if available for best performance             $aggregateParams = @{                 InputPath = $AggregationInputPath                 OutputPath = $aggregationPath                 StreamingMode = $true                 IncrementalMode = $true                 SkipReportIfUnchanged = $true                 ParallelThreads = 8             }             # Pass rollout summary if it exists (for velocity/projection data)             if (Test-Path $rolloutSummaryPath) {                 $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath             }             & $aggregateScript @aggregateParams             # Show command to generate full HTML dashboard with device tables             Write-Host             Write-Host "Norėdami sugeneruoti visą HTML ataskaitų sritį su gamintojo / modelio lentelėmis, vykdyti:" -Priekinio planopalva Geltona             Write-Host $aggregateScript -InputPath $AggregationInputPath -OutputPath '"$aggregationPath'" - Priekinio planopalva geltona             Write-Host         } dar {             Write-Log "[DRYRUN] Vykdytų agregavimą" "INFO"             # In DryRun, tiesiogiai naudokite esamus agregavimo duomenis iš ReportBasePath             $aggregationPath = $ReportBasePath         }     }     $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     # 2 veiksmas: įkelti dabartinę įrenginio būseną     Write-Log "2 veiksmas: įkeliamas įrenginio būsena..." "INFO"     $notUptodateCsv = Get-ChildItem -Path $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue |          Where-Object { $_. Pavadinimas -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object – pirmas 1     jei (-not $notUptodateCsv -and -not $DryRun) {         Write-Log "Agregavimo duomenų nerasta.                                            Laukiama..." "WARN"         Start-Sleep –sekundės ($PollIntervalMinutes * 60)         Toliau     }     $notUpdatedDevices = jei ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } dar { @() }     Write-Log "Įrenginiai neatnaujinami: $($notUpdatedDevices.Count)" "INFO"     $notUpdatedIndexes = Get-NotUpdatedIndexes - Įrenginiai $notUpdatedDevices     # 3 veiksmas: atnaujinkite įrenginio istoriją (sekimas pagal pagrindinio kompiuterio vardą)     Write-Log "3 veiksmas: įrenginio retrospektyvos naujinimas..." "INFO"     Update-DeviceHistory – CurrentDevices $notUpdatedDevices – DeviceHistory $deviceHistory     Save-DeviceHistory – retrospektyvos $deviceHistory     # 4 veiksmas: patikrinkite, ar nėra užblokuotų talpyklų (nepasiekiami įrenginiai)     $existingBlockedCount = $blockedBuckets.Count     Write-Log "4 veiksmas: tikrinama, ar yra užblokuotų talpyklų (įrenginių tikrinimas praėjus laukimo laikotarpiui)..." "INFO"     jei ($existingBlockedCount -gt 0) {         Write-Log "Šiuo metu užblokuotos talpyklos iš ankstesnių paleidimų: $existingBlockedCount" "INFO"     }     jei ($adminApproved.Count -gt 0) {         Write-Log "Administratorius patvirtintos talpyklos (nebus iš naujo užblokuotos): $($adminApproved.Count)" "INFO"     }     $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState - BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun     jei ($newlyBlocked.Count -gt 0) {         Save-BlockedBuckets – užblokuotas $blockedBuckets         Write-Log "Naujai užblokuotos talpyklos (ši iteracija): $($newlyBlocked.Count)" "UŽBLOKUOTA"     }     # 4b veiksmas: automatiškai atblokuokite talpyklas, kuriose atnaujinti įrenginiai     $autoUnblocked = Update-AutoUnblockedBuckets - BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize     jei ($autoUnblocked.Count -gt 0) {         Save-BlockedBuckets – užblokuotas $blockedBuckets         Write-Log "Auto-unblocked buckets (devices updated): $($autoUnblocked.Count)" "OK"     }     # 5 veiksmas: apskaičiuokite likusius reikalavimus atitinkančius įrenginius     $eligibleCount = 0     foreach ($device in $notUpdatedDevices) {         $bucketKey = Get-BucketKey $device         jei (-not $blockedBuckets.Contains($bucketKey)) {             $eligibleCount++         }     }     Write-Log "Liko reikalavimus atitinkančių įrenginių: $eligibleCount" "INFO"     Write-Log "Užblokuotos talpyklos: $($blockedBuckets.Count)" "INFO"     # 6 veiksmas: patikrinimas baigtas     jei ($eligibleCount -eq 0) {         Write-Log "ROLLOUT COMPLETE – atnaujinti visi reikalavimus atitinkantys įrenginiai!" "GERAI"         $rolloutState.Status = "Completed"         $rolloutState.CompletedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"         Save-RolloutState – būsenos $rolloutState         Pertraukos     }     # 6 veiksmas: sukurkite ir įdiekite kitą bangą     Write-Log "Step 6: Generating rollout wave..." "INFO"     $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames     # Patikrinkite, ar turime diegtinų įrenginių ($waveDevices gali būti $null, tuščias arba su faktiniais įrenginiais)     $hasDevices = $waveDevices ir @($waveDevices | Where-Object { $_ }). Skaičiavimas -gt 0     jei ($hasDevices) {         # Tik didėjančių bangų skaičius, kai iš tikrųjų turime diegtini įrenginiai         $rolloutState.CurrentWave++         Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Count) devices" "WAVE"         # Visuotinai diegti GPO naudojant funkciją Inlined         $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $hostnames = @($waveDevices | ForEach-Object {             jei ($_. Pagrindinio kompiuterio vardas) { $_. Hostname } elseif ($_. HostName) { $_. HostName } else { $null }         } | Where-Object { $_ })         # Pagrindinio kompiuterio vardų failo įrašymas nuorodai / auditui         $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt"         $hostnames | Out-File $hostnamesFile – utf8 kodavimas         # Tikrinimas, ar turime pagrindinių kompiuterių, kuriuos reikia diegti         jei ($hostnames. Skaičius -eq 0) {             Write-Log "Nėra leistinų pagrindinio kompiuterio vardų bangoje $($rolloutState.CurrentWave) – įrenginiuose gali trūkti hostname ypatybės" WARN             Write-Log "Šios bangos diegimas praleidžiamas – patikrinkite įrenginio duomenis" WARN             # Vis dar palaukite prieš kitą iteraciją             jei (-not $DryRun) {                 Write-Log "Miega $PollIntervalMinutes min. prieš pakartodami..." "INFO"                 Start-Sleep – sekundės ($PollIntervalMinutes * 60)             }             Toliau         }         Write-Log "Diegimas į $($hostnames. Count) hostnames in Wave $($rolloutState.CurrentWave)" "INFO"         # Diegimas naudojant "WinCS" arba GPO metodą, pagrįstą parametru "UseWinCS"         jei ($UseWinCS) {             # WinCS metodas: Sukurti GPO su suplanuota užduotimi paleisti WinCsFlags.exe kaip SYSTEM kiekviename galinį punktą             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             jei (-not $wincsResult.Success) {                 Write-Log "WinCS" įdiegti nepavyko – pritaikyta: $($wincsResult.Applied), nepavyko: $($wincsResult.Failed)" "WARN"             } dar {                 Write-Log "WinCS" diegimas sėkmingas – taikoma: $($wincsResult.Applied), praleista: $($wincsResult.Skipped)" "Gerai"             }             # Įrašyti "WinCS" rezultatus auditui             $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json"             $wincsResult | ConvertTo-Json gylis 5 | Out-File $wincsResultFile – utf8 kodavimas         } dar {             # GPO metodas: sukurkite GPO naudodami "AvailableUpdatesPolicy" registro parametrą             $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun             jei (-not $gpoResult) {                 Write-Log "GPO diegimas nepavyko – bandys pakartoti paskesnę iteraciją" "KLAIDA"             }         }         # Įrašų banga būsenoje         $waveRecord = @{             WaveNumber = $rolloutState.CurrentWave             StartedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss"             DeviceCount = @($waveDevices). Skaičius             Įrenginiai = @($waveDevices | ForEach-Object {                 @{                     Hostname = if ($_. Pagrindinio kompiuterio vardas) { $_. Hostname } elseif ($_. HostName) { $_. HostName } dar { $null }                     BucketKey = Get-BucketKey $_                 }             })         }         # Įsitikinkite, kad "WaveHistory" visada yra masyvas prieš pridedant (neleidžia maišos suliejimo problemų)         $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord)         $rolloutState.TotalDevicesTargeted += @($waveDevices). Skaičius         Save-RolloutState būsenos $rolloutState         Write-Log "Wave $($rolloutState.CurrentWave) įdiegta.                                                                                                                                                                                        Laukiama $PollIntervalMinutes minučių..." "GERAI"     } dar {         # Rodyti įdiegtų įrenginių, laukiančių naujinimų, būseną         Write-Log "INFO"         Write-Log "========== VISUOSE ĮDIEGTUOSE ĮRENGINIUOSE – LAUKIAMA BŪSENOS ==========" "INFO"                  # Gauti visus įdiegtus įrenginius iš bangų istorijos         $allDeployedLookup = @{}         foreach ($wave in $rolloutState.WaveHistory) {             ($device $wave. Įrenginiai) {                 jei ($device. Pagrindinio kompiuterio vardas) {                     $allDeployedLookup[$device. Pagrindinio kompiuterio vardas] = @{                         Pagrindinio kompiuterio vardas = $device. Hostname                         BucketKey = $device. "BucketKey"                         DeployedAt = $wave. Darbo pradžia                         WaveNumber = $wave. "WaveNumber"                     }                 }             }         }         $allDeployedDevices = @($allDeployedLookup.Values)                  jei ($allDeployedDevices.Count -gt 0) {             # Raskite, kurie įdiegti įrenginiai vis dar laukia (sąraše NotUpdated)             $stillPendingCount = 0             $noLongerPendingCount = 0             $pendingSample = @()             foreach ($deployed in $allDeployedDevices) {                 jei ($notUpdatedIndexes.HostSet.Contains($deployed. Pagrindinio kompiuterio vardas)) {                     $stillPendingCount++                     jei ($pendingSample.Count -lt $DeviceLogSampleSize) {                         $pendingSample += $deployed. Hostname                     }                 } dar {                     $noLongerPendingCount++                 }             }                          # Gauti faktinius atnaujintus skaičius iš agregavimo – atskirti įvykį 1808 ir UEFICA2023Status             $summaryCsv = Get-ChildItem -Path $aggregationPath -Filter "*Summary*.csv" |                  Sort-Object LastWriteTime -Descending | Select-Object 1 pirmas             $actualUpdated = 0             $totalDevicesFromSummary = 0             $event 1808Count = 0             $uefiStatusUpdated = 0             $needsRebootSample = @()                          jei ($summaryCsv) {                 $summary = Import-Csv $summaryCsv.FullName | Select-Object – pirmas 1                 jei ($summary. Atnaujinta) { $actualUpdated = [sveikasis skaičius]$summary. Atnaujinta }                 jei ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices }             }                          # Apskaičiuokite greitį pagal bangų istoriją (įrenginiai atnaujinti per dieną)             $devicesPerDay = 0             jei ($rolloutState.StartedAt -and $actualUpdated -gt 0) {                 $startDate = [datetime]::P arse($rolloutState.StartedAt)                 $daysElapsed = ((Get-Date) - $startDate). TotalDays                 jei ($daysElapsed -gt 0) {                     $devicesPerDay = $actualUpdated / $daysElapsed                 }             }                          # Įrašyti diegimo suvestinę su savaitgalio projekcijomis             # Vientisumui užtikrinti naudokite rinkiklio NotUptodate skaičių (neįtraukia SB OFF įrenginių)             $notUpdatedCount = jei ($summary ir $summary. NotUptodate) { [int]$summary. NotUptodate } dar { $totalDevicesFromSummary - $actualUpdated }             Save-RolloutSummary – būsenos $rolloutState "                 -TotalDevices $totalDevicesFromSummary '                 -UpdatedDevices $actualUpdated '                 -NotUpdatedDevices $notUpdatedCount "                 -DevicesPerDay $devicesPerDay                          # Patikrinkite neapdorotus duomenis įrenginiams, kuriuose naudojama UEFICA2023Status=Atnaujinta, bet nėra 1808 įvykio (reikia perkrauti)             $dataFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue             $totalDataFiles = @($dataFiles). Skaičius             $batchSize = [Matematika]::Maks.(500, $ProcessingBatchSize)             jei ($LargeScaleMode) {                 $batchSize = [Matematika]::Maks.(2000, $ProcessingBatchSize)             }

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

                    foreach ($file in $batchFiles) {                         išbandykite {                             $deviceData = Get-Content $file. FullName – žalias | KonvertuotiFrom-Json                             $hostname = $deviceData.Hostname                             jei (-not $hostname) { continue }                             $has 1808 = [int]$deviceData.Event1808Count -gt 0                             $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Atnaujinta"                             jei ($has 1808) {                                 $event 1808Count++                             } elseif ($hasUefiUpdated) {                                 $uefiStatusUpdated++                                 jei ($needsRebootSample.Count -lt $DeviceLogSampleSize) {                                     $needsRebootSample += $hostname                                 }                             }                         } sugauti { }                     }                                                          

                    Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{                         Event1808Count = $event 1808Count                         UEFIUpdatedAwaitingReboot = $uefiStatusUpdated                     }                 }             }             Write-Log "Total deployed: $($allDeployedDevices.Count)" "INFO"             Write-Log "Atnaujinta (1808 įvykis patvirtintas): $event 1808Count" "Gerai"             jei ($uefiStatusUpdated -gt 0) {                 Write-Log "Updated (UEFICA2023Status=Updated, awaiting reboot): $uefiStatusUpdated" "OK"                 $rebootSuffix = jei ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated – $DeviceLogSampleSize) daugiau)" } dar { "" }                 Write-Log " Įrenginiai, kuriems reikia paleisti iš naujo 1808 įvykiui (pavyzdys): $($needsRebootSample -join ', ')$rebootSuffix" "INFO"                 Write-Log " Šie įrenginiai praneš apie įvykį 1808 po kito paleidimo iš naujo" "INFO"             }             Write-Log "Nebėra laukiama: $noLongerPendingCount (įskaitant "SecureBoot OFF", trūkstamus įrenginius)" "INFO"             Write-Log "Laukiama būsenos: $stillPendingCount" "INFO"             jei ($stillPendingCount -gt 0) {                 $pendingSuffix = if ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount – $DeviceLogSampleSize) daugiau)" } dar { "" }                 Write-Log "Laukiantys įrenginiai (pavyzdys): $($pendingSample -join ', ')$pendingSuffix" "WARN"             }         } dar {             Write-Log "Dar neįdiegta jokių įrenginių" "INFO"         }         Write-Log "================================================================" "INFO"         Write-Log "INFO"     }     # Palaukite prieš kitą iteraciją     jei (-not $DryRun) {         Write-Log "Miega $PollIntervalMinutes minutėms..." "INFO"         Start-Sleep – sekundės ($PollIntervalMinutes * 60)     } dar {         Write-Log "[DRYRUN] Palauks $PollIntervalMinutes minučių" "INFO"         break # Exit after one iteration in dry run     } }                               

# ============================================================================ # GALUTINĖ SUVESTINĖ # ============================================================================

Write-Host "" Write-Host (=" * 80) - Priekinio planopalva žalia Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -ForegroundColor Green Write-Host (=" * 80) - Priekinio planopalva žalia 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 "Tiksliniai įrenginiai: $($finalState.TotalDevicesTargeted)" Write-Host "Užblokuotos talpyklos: $($finalBlocked.Count)" -ForegroundColor $(if ($finalBlocked.Count -gt 0) { "Red" } dar { "Žalias" }) Write-Host "Įrenginiai susekti: $($deviceHistory.Count)" -Priekinio planopalva pilka 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" – priekinio plano spalva raudona         Write-Host " Priežastis: $($info. Reason)" -Priekinio planopalva pilka     }     Write-Host     Write-Host "Blocked buckets file: $blockedBucketsPath" -ForegroundColor Yellow }

Write-Host "" Write-Host "State files:" -ForegroundColor Cyan Write-Host " Rollout State: $rolloutStatePath" Write-Host " Blocked Buckets: $blockedBucketsPath" Write-Host " Įrenginio retrospektyva: $deviceHistoryPath" Write-Host ""  

​​​​​​​

Reikia daugiau pagalbos?

Norite daugiau parinkčių?

Sužinokite apie prenumeratos pranašumus, peržiūrėkite mokymo kursus, sužinokite, kaip apsaugoti savo įrenginį ir kt.