Kopeerige ja kleepige see näidisskript ning muutke seda vastavalt oma keskkonnale.
<# . SÜNOPSIS Koondab secure Booti oleku JSON-andmed mitmest seadmest kokkuvõtlikesse aruannetesse.
. KIRJELDUS Loeb kogutud Secure Booti oleku JSON-failid ja loob: - HTML-armatuurlaud diagrammide ja filtreerimisega - Summary by ConfidenceLevel - Kordumatu seadme salve analüüs testimisstrateegia jaoks Toetab: - Arvutipõhised failid: HOSTNAME_latest.json (soovitatav) - Üks JSON-fail HostName dedubleerib automaatselt, säilitades uusima CollectionTime'i. Vaikimisi hõlmab see ainult seadmeid, millel on "Action Req" või "High" usaldus et keskenduda aktiveeritavatele salvidele. Alistamiseks kasutage alistamise funktsiooni -IncludeAllConfidenceLevels.
. PARAMETER InputPath JSON-failide tee: - Kaust: loeb ette kõik *_latest.json failid (või *.json, kui _latest faile pole) - Fail: loeb ette ühe JSON-faili
. PARAMETER OutputPath Loodud aruannete tee (vaikesäte: .\SecureBootReports)
. NÄIDE # Arvutipõhiste failide kaustast koondamine (soovitatav) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # Loeb: \\contoso\SecureBootLogs$\*_latest.json
. NÄIDE # Kohandatud väljundi asukoht .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -OutputPath "C:\Reports\SecureBoot"
. NÄIDE # Kaasa ainult toimingu kordusq ja suur usaldusväärsus (vaikekäitumine) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # Välistab: jälgimine, peatatud, toetuseta
. NÄIDE # Kaasa kõik usaldustasemed (alistamise filter) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeAllConfidenceLevels
. NÄIDE # Kohandatud usaldustaseme filter .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeConfidenceLevels @("Action Req", "High", "Observation")
. NÄIDE # ENTERPRISE SCALE: Inkrementrežiim – ainult muudetud failide töötlemine (kiired järgnevad toimingud) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode # Esmakäivitus: täiskoormus ~2 tundi 500 000 seadme jaoks # Järgmised toimingud: sekundid, kui muudatusi pole, deltade minutid
. NÄIDE # Jäta HTML vahele, kui midagi ei muutu (kiireim jälgimine) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode -SkipReportIfUnchanged # Kui pärast viimast käitust pole faile muudetud: ~5 sekundit
. NÄIDE # Ainult kokkuvõtterežiim – jätke vahele mahukad seadmetabelid (1–2 minutit vs 20 minutit) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -SummaryOnly # Genereerib CSV-d, kuid jätab html-armatuurlaua vahele koos seadme täistabelitega
. MÄRKMED Siduge Detect-SecureBootCertUpdateStatus.ps1 ettevõtte juurutuse jaoks.Täieliku juurutusjuhendi leiate GPO-DEPLOYMENT-GUIDE.md. Vaikekäitumine välistab jälgimis-, peatamis- ja toetuseta seadmed et keskenduda aruandluse ainult täitmisseadmete salvedele.#>
param( [Parameeter(kohustuslik = $true)] [string]$InputPath, [Parameeter(kohustuslik = $false)] [string]$OutputPath = ".\SecureBootReports", [Parameeter(kohustuslik = $false)] [string]$ScanHistoryPath = ".\SecureBootReports\ScanHistory.json", [Parameeter(kohustuslik = $false)] [string]$RolloutStatePath, # RolloutState.json tee inProgress-seadmete tuvastamiseks [Parameeter(kohustuslik = $false)] [string]$RolloutSummaryPath, # Orchestratorist SecureBootRolloutSummary.json tee (sisaldab projektsiooniandmeid) [Parameeter(kohustuslik = $false)] [string[]]$IncludeConfidenceLevels = @("Nõutav toiming", "Suur usaldusväärsus"), # Kaasa ainult need usaldustasemed (vaikimisi: ainult sooritatavad salved) [Parameeter(kohustuslik = $false)] [switch]$IncludeAllConfidenceLevels, # Override filter to include all confidence levels [Parameeter(kohustuslik = $false)] [switch]$SkipHistoryTracking, [Parameeter(kohustuslik = $false)] [switch]$IncrementalMode, # Enable delta processing - load changed files since last run [Parameeter(kohustuslik = $false)] [string]$CachePath, # tee vahemälukaustani (vaikesäte: OutputPath\.cache) [Parameeter(kohustuslik = $false)] [int]$ParallelThreads = 8, # Faili laadimiseks mõeldud paralleellõimede arv (PS7+) [Parameeter(kohustuslik = $false)] [switch]$ForceFullRefresh, # Force full reload even incremental mode [Parameter(Mandatory = $false)] [switch]$SkipReportIfUnchanged, # Jäta HTML-i/CSV genereerimine vahele, kui faile pole muudetud (ainult väljundstatistika) [Parameeter(kohustuslik = $false)] [switch]$SummaryOnly, # Genereeri ainult kokkuvõttestatistika (suuri seadmetabeleid pole) - palju kiirem [Parameeter(kohustuslik = $false)] [switch]$StreamingMode # Memory-efficient mode: process chunks, write CSVs inkrementally, keep only summaries in memory )
# Eneseparandus: EEMALDAGE HTML-artiklitest kopeerimisel veebi CMS-i lisatud nähtamatud Unicode'i märgid.# support.microsoft.com CMS süstib nulllaiusega tühikud (U+200B), mittekattuvad tühikud (U+00A0) ja muud # nähtamatuid märke siin-stringides olevate HTML-siltide ümber, põhjustades PowerShelli sõelumistõrkeid.if ($MyInvocation.MyCommand.Path) { $rawScript = [System.IO.File]::ReadAllText($MyInvocation.MyCommand.Path) if ($rawScript -match '[\u200B-\u200F\uFEFF]' -või $rawScript -match '\xA0') { Write-Host "HOIATUS: tuvastati nähtamatud Unicode'i märgid (tõenäoliselt veebist kopeerimise ja kleepimise korral) – automaatse puhastamise skript..." -Esiplaanivärvi kollane $cleaned = $rawScript -replace '[\u200B-\u200F\uFEFF]', '' $cleaned = $cleaned -replace '\xA0', ' ' [System.IO.File]::WriteAllText($MyInvocation.MyCommand.Path, $cleaned, [System.Text.UTF8Encoding]::new($false)) Write-Host "Skript on puhastatud. Re-launching..." -ForegroundColor Green & $MyInvocation.MyCommand.Path @PSBoundParameters $LASTEXITCODE sulgemine } }
# Kui powerShell 7 on saadaval, saate selle automaatselt üle viia (suurte andmehulkade korral 6x kiiremini) if ($PSVersionTable.PSVersion.Major -lt 7) { $pwshPath = Get-Command pwsh -ErrorAction SilentlyContinue | Select-Object – laiendatud property allikas if ($pwshPath) { Write-Host "Tuvastati PowerShelli $($PSVersionTable.PSVersion) – uuesti käivitamine PowerShell 7 abil kiiremaks töötlemiseks..." – Esiplaanivärvi kollane # Koosta argumendiloend seotud parameetrite põhjal uuesti $relaunchArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $MyInvocation.MyCommand.Path) foreach ($key $PSBoundParameters.Keys) { $val = $PSBoundParameters[$key] if ($val -is [switch]) { kui ($val. IsPresent) { $relaunchArgs += "-$key" } } elseif ($val -is [massiiv]) { $relaunchArgs += "-$key" $relaunchArgs += ($val -join ',') } else { $relaunchArgs += "-$key" $relaunchArgs += "$val" } } & $pwshPath @relaunchArgs $LASTEXITCODE sulgemine } }
$ErrorActionPreference = "Jätka" $timestamp = Get-Date – vorming "yyyyMmd-HHmmss" $scanTime = Get-Date – vorming "yyyy-MM-dd HH:mm:ss" $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Juurutamis- ja seirenäidised"
# Märkus. See skript ei sõltu muudest skriptidest.# Tööriistakomplekti täielikuks allalaadimiseks laadige alla asukohast: $DownloadUrl -> $DownloadSubPage
#region häälestus Write-Host "=" * 60 -ForegroundColor Cyan Write-Host "Secure Boot Data Aggregation" –ForegroundColor Cyan Write-Host "=" * 60 -ForegroundColor Cyan
# Loo väljundkaust kui (-not (testtee $OutputPath)) { New-Item -ItemType'i kataloog -tee $OutputPath -Force | Nullväärtuseta }
# Load data – toetab CSV-vorminguid (pärand) ja JSON-vorminguid Write-Host "'nLoading data from: $InputPath" -ForegroundColor Yellow
# Helper function to normalize device object (handle field name differences) funktsioon Normalize-DeviceRecord { param($device) # Handle Hostname vs HostName (JSON kasutab hostinime, CSV kasutab HostName'i) if ($device. PSObject.Properties['Hostname'] - and -not $device. PSObject.Properties['HostName']) { $device | Add-Member -NotePropertyName 'HostName' -NotePropertyValue $device. Hostinimi – jõustamine } # Handle Confidence vs ConfidenceLevel (JSON uses Confidence, CSV uses ConfidenceLevel) # ConfidenceLevel on ametlik väljanimi – vastendage selle usaldus if ($device. PSObject.Properties['Confidence'] - and -not $device. PSObject.Properties['ConfidenceLevel']) { $device | Add-Member -NotePropertyName 'ConfidenceLevel' -NotePropertyValue $device. Usaldus – jõud } # Jälgige värskenduse olekut sündmuse1808Count OR UEFICA2023Status="Updated" kaudu # See võimaldab jälgida, mitu seadet igas usaldusvahemikus on värskendatud $event 1808 = 0 if ($device. PSObject.Properties['Event1808Count']) { $event 1808 = [int]$device. Sündmus1808Count } $uefiCaUpdated = $false if ($device. PSObject.Properties['UEFICA2023Status'] -and $device. UEFICA2023Status -eq "Värskendatud") { $uefiCaUpdated = $true } kui ($event 1808 -gt 0 -või $uefiCaUpdated) { # Märgi armatuurlaua/väljalaske loogika jaoks värskendatuks, kuid DON'T alistada ConfidenceLevel $device | Add-Member -NotePropertyName 'IsUpdated' -NotePropertyValue $true -Force } else { $device | Add-Member -NotePropertyName 'IsUpdated' -NotePropertyValue $false -Force # ConfidenceLevel klassifikatsioon: # - "Väga kindlustunne", "Under Observation...", "Temporarily Paused...", "Not Supported..." = use as-is # - Kõik muu (null, tühi, "UpdateType:...", "Tundmatu", "N/A") = langeb loendurites nõutavale toimingule # Normaliseerimist pole vaja teha – voogesituse loenduri else-haru käsitleb seda } # Handle OEMManufacturerName vs WMI_Manufacturer (JSON uses OEM*, legacy uses WMI_*) if ($device. PSObject.Properties['OEMManufacturerName'] - and -not $device. PSObject.Properties['WMI_Manufacturer']) { $device | Add-Member -NotePropertyName 'WMI_Manufacturer' -NotePropertyValue $device. OEMManufacturerName – jõustamine } # Handle OEMModelNumber vs WMI_Model if ($device. PSObject.Properties['OEMModelNumber'] - and -not $device. PSObject.Properties['WMI_Model']) { $device | Add-Member -NotePropertyName 'WMI_Model' -NotePropertyValue $device. OEMModelNumber – jõustamine } # Handle FirmwareVersion vs BIOSDescription if ($device. PSObject.Properties['FirmwareVersion'] - and -not $device. PSObject.Properties['BIOSDescription']) { $device | Add-Member $device -NotePropertyName 'BIOSDescription' -NotePropertyValue. FirmwareVersion – jõustamine } tagastus $device }
#region astmeline töötlemine/ vahemäluhaldus # Vahemäluteede häälestamine kui (-mitte $CachePath) { $CachePath = Join-Path $OutputPath ".cache" } $manifestPath = Join-Path $CachePath "FileManifest.json" $deviceCachePath = Join-Path $CachePath "DeviceCache.json"
# Vahemäluhaldusfunktsioonid funktsioon Get-FileManifest { param([string]$Path) if (testtee $Path) { proovige { $json = Get-Content $Path -Toor | ConvertFrom-Json # Teisendage PSObject räsitavaks (PS5.1-ga ühilduv - PS7-l on -AsHashtable) $ht = @{} $json. PSObject.Properties | ForEach-Object { $ht[$_. Nimi] = $_. Value } tagastus $ht } püüdmine { tagasta @{} } } tagasta @{} }
funktsioon Save-FileManifest { param([hashtable]$Manifest, [string]$Path) $dir = Split-Path $Path -ema kui (-not (testtee $dir)) { New-Item -ItemType'i kataloog -tee $dir -Force | Nullväärtuseta } $Manifest | ConvertTo-Json -Sügavus 3 -Tihenda | Set-Content $Path – jõusta }
funktsioon Get-DeviceCache { param([string]$Path) if (testtee $Path) { proovige { $cacheData = Get-Content $Path -Toor | ConvertFrom-Json Write-Host " Laaditud seadme vahemälu: $($cacheData.Count) seadmed" -ForegroundColor DarkGray tagastus $cacheData } püüdmine { Write-Host " Cache corrupted, will rebuild" -ForegroundColor Yellow tagasta @() } } tagasta @() }
funktsioon Save-DeviceCache { param($Devices; [string]$Path) $dir = Split-Path $Path -ema kui (-not (testtee $dir)) { New-Item -ItemType'i kataloog -tee $dir -Force | Nullväärtuseta } # Teisenda massiiviks ja salvesta $deviceArray = @($Devices) $deviceArray | ConvertTo-Json -Sügavus 10 -Tihenda | Set-Content $Path – jõustamine Write-Host "Salvestatud seadme vahemälu: $($deviceArray.Count) seadmed" -ForegroundColor DarkGray }
funktsioon Get-ChangedFiles { param( [System.IO.FileInfo[]]$AllFiles, [hashtable]$Manifest ) $changed = [System.Collections.ArrayList]::new() $unchanged = [System.Collections.ArrayList]::new() $newManifest = @{} # Loo manifestist tõstutundetu otsing (normaliseeri väiketähtedeks) $manifestLookup = @{} foreach ($mk $Manifest.Keys) { $manifestLookup[$mk. ToLowerInvariant()] = $Manifest[$mk] } foreach ($file in $AllFiles) { $key = $file. FullName.ToLowerInvariant() # Normalize path to lowercase $lwt = $file. LastWriteTimeUtc.ToString("o") $newManifest[$key] = @{ LastWriteTimeUtc = $lwt Suurus = $file. Pikkus } if ($manifestLookup.ContainsKey($key)) { $cached = $manifestLookup[$key] if ($cached. LastWriteTimeUtc -eq $lwt -and $cached. Suurus - eq $file. Pikkus) { [kehtetu]$unchanged. Lisa ($file) Jätkata } } [kehtetu]$changed. Lisa ($file) } tagasta @{ Muudetud = $changed Muutmata = $unchanged NewManifest = $newManifest } }
# Ultra-fast parallel file loading using batched processing funktsioon Load-FilesParallel { param( [System.IO.FileInfo[]]$Files, [int]$Threads = 8 )
$totalFiles = $Files. Loendus # Kasutage parema mälukontrolli jaoks ~1000 faili pakette $batchSize = [matemaatika]::Min(1000, [matemaatika]::Ceiling($totalFiles / [matemaatika]::Max(1, $Threads))) $batches = [System.Collections.Generic.List[objekt]]::new()
for ($i = 0; $i -lt $totalFiles; $i += $batchSize) { $end = [matemaatika]::Min($i + $batchSize, $totalFiles) $batch = $Files[$i.. ($end-1)] $batches. Lisa ($batch) } Write-Host " ($($batches. Count) paketid ~$batchSize failid iga)" -NoNewline -ForegroundColor DarkGray $flatResults = [System.Collections.Generic.List[objekt]]::new() # Kontrollige, kas PowerShell 7+ paralleel on saadaval $canParallel = $PSVersionTable.PSVersion.Major -ge 7 kui ($canParallel -ja $Threads -gt 1) { # PS7+: Töötle pakette paralleelselt $results = $batches | ForEach-Object -ThrottleLimit $Threads -Parallel { $batchFiles = $_ $batchResults = [System.Collections.Generic.List[objekt]]::new() foreach ($file in $batchFiles) { proovige { $content = [System.IO.File]::ReadAllText($file. Täisnimi) | ConvertFrom-Json $batchResults.Add($content) } püüdmine { } } $batchResults.ToArray() } foreach ($batch $results) { if ($batch) { foreach ($item $batch) { $flatResults.Add($item) } } } } else { # PS5.1 taandeteave: järjestikune töötlemine (endiselt kiire <10K-failide jaoks) foreach ($file in $Files) { proovige { $content = [System.IO.File]::ReadAllText($file. Täisnimi) | ConvertFrom-Json $flatResults.Add($content) } püüdmine { } } } return $flatResults.ToArray() } #endregion
$allDevices = @() if (Test-path $InputPath -PathType Leaf) { # Üks JSON-fail if ($InputPath -like "*.json") { $jsonContent = Get-Content -tee $InputPath -Toor | ConvertFrom-Json $allDevices = @($jsonContent) | ForEach-Object { Normalize-DeviceRecord $_ } Write-Host "Laaditud $($allDevices.Count) kirjed failist" } else { Write-Error "Toetatud on ainult JSON-vorming. Faili laiend peab olema .json." väljumine 1 } } elseif (testtee $InputPath -PathType'i ümbris) { # kaust – ainult JSON $jsonFiles = @(Get-ChildItem -path $InputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_. Name -notmatch "ScanHistory|RolloutState|RolloutPlan" }) # Eelista *_latest.json faile, kui need on olemas (arvutipõhises režiimis) $latestJson = $jsonFiles | Where-Object { $_. Nimi -like "*_latest.json" } if ($latestJson.Count -gt 0) { $jsonFiles = $latestJson } $totalFiles = $jsonFiles.Count if ($totalFiles -eq 0) { Write-Error "JSON-faile ei leitud: $InputPath" väljumine 1 } Write-Host "Leitud $totalFiles JSON-failid" – esiplaanivärvi hall # Helper function to match confidence levels (handles both short and full forms) # Määratletakse varakult, et seda saaksid kasutada nii StreamingMode kui ka tavalised teed funktsioon Test-ConfidenceLevel { param([string]$Value, [string]$Match) if ([string]::IsNullOrEmpty($Value)) { tagastab $false } lüliti ($Match) { "HighConfidence" { return $Value -eq "High Confidence" } "UnderObservation" { return $Value -like "Under Observation*" } "ActionRequired" { return ($Value -like "*Action Required*" -or $Value -eq "Action Required") } "TemporarilyPaused" { return $Value -like "Temporarily Paused*" } "Pole toetatud" { tagastus ($Value -like "Pole toetatud*" -või $Value -eq "Pole toetatud") } vaiketagastus { return $false } } } #region VOOGESITUSREŽIIM – suurte andmehulkade mälutõhus töötlemine # Kasuta streamingMode'i alati mälutõhusaks töötlemiseks ja uue laadi armatuurlaua jaoks kui (-mitte $StreamingMode) { Write-Host "Auto-enabling StreamingMode (new-style dashboard)" -ForegroundColor Yellow $StreamingMode = $true if (-not $IncrementalMode) { $IncrementalMode = $true } } # Kui -StreamingMode on lubatud, töötle faile osadena, hoides mälus ainult loendureid.# Seadmetaseme andmed kirjutatakse JSON-failidesse kogumi kohta nõudmisel laadimiseks armatuurlaual.# Mälukasutus: ~1,5 GB olenemata andmekomplekti mahust (vs 10–20 GB ilma voogesituseta).if ($StreamingMode) { Write-Host "STREAMING MODE enabled - memory-efficient processing" -ForegroundColor Green $streamSw = [System.Diagnostics.Stopwatch]::StartNew() # INCREMENTAL CHECK: Kui pärast viimast käivitamist faile ei muudetud, jätke töötlemine täielikult vahele if ($IncrementalMode -and -not $ForceFullRefresh) { $stManifestDir = Join-Path $OutputPath ".cache" $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" if (testtee $stManifestPath) { Write-Host "Muudatuste otsimine pärast viimast voogesitust..." – Esiplaanivärvi tsüaan $stOldManifest = Get-FileManifest –tee $stManifestPath if ($stOldManifest.Count -gt 0) { $stChanged = $false # Kiirkontroll: sama failiarvestus? if ($stOldManifest.Count -eq $totalFiles) { # Kontrollige 100 UUSIMAT faili (sorditud viimase aja järgi laskuvas järjestuses) # Kui mõnda faili on muudetud, on sellel kõige uuem ajatempel ja see kuvatakse esimesena $sampleSize = [matemaatika]:Min(100; $totalFiles) $sampleFiles = $jsonFiles | Sort-Object LastWriteTimeUtc -Descending | Select-Object – esimene $sampleSize foreach ($sf $sampleFiles) { $sfKey = $sf. FullName.ToLowerInvariant() if (-not $stOldManifest.ContainsKey($sfKey)) { $stChanged = $true Murda } # Compare timestamps – vahemällu talletatud võib olla DateTime või string pärast JSON-i ümardamist $cachedLWT = $stOldManifest[$sfKey]. LastWriteTimeUtc $fileDT = $sf. LastWriteTimeUtc proovige { # Kui vahemällu talletatud väärtus on juba DateTime (ConvertFrom-Json automaatne teisendamine), kasutage otse if ($cachedLWT -is [DateTime]) { $cachedDT = $cachedLWT.ToUniversalTime() } else { $cachedDT = [DateTimeOffset]::P arse("$cachedLWT"). UtcDateTime } if ([matemaatika]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt 1) { $stChanged = $true Murda } } püüdmine { $stChanged = $true Murda } } } else { $stChanged = $true } kui (-mitte $stChanged) { # Kontrollige, kas väljundfailid on olemas $stSummaryExists = Get-ChildItem (join-path $OutputPath "SecureBoot_Summary_*.csv") -EA SilentlyContinue | Select-Object – esimene 1 $stDashExists = Get-ChildItem (join-path $OutputPath "SecureBoot_Dashboard_*.html") -EA SilentlyContinue | Select-Object – esimene 1 if ($stSummaryExists -and $stDashExists) { Write-Host " Muutusi ei tuvastatud ($totalFiles faile muutmata) - töötlemise vahelejätmine" -Esiplaanivärvi roheline Write-Host " Viimane armatuurlaud: $($stDashExists.FullName)" -ForegroundColor White $cachedStats = Get-Content $stSummaryExists.FullName | ConvertFrom-Csv Write-Host " Seadmed: $($cachedStats.TotalDevices) | Värskendatud: $($cachedStats.Värskendatud) | Tõrked: $($cachedStats.WithErrors)" -ForegroundColor Gray Write-Host " Lõpetatud $([matemaatika]::Round($streamSw.Elapsed.TotalSeconds, 1)))s (töötlust pole vaja)" -Esiplaanivärvi roheline tagastus $cachedStats } } else { # DELTA PATCH: Täpselt muudetud failide otsimine Write-Host " Tuvastatud muudatused - tuvastati muudetud failid..." -Esiplaanivärvi kollane $changedFiles = [System.Collections.ArrayList]::new() $newFiles = [System.Collections.ArrayList]::new() foreach ($jf in $jsonFiles) { $jfKey = $jf. FullName.ToLowerInvariant() if (-not $stOldManifest.ContainsKey($jfKey)) { [kehtetu]$newFiles.Add($jf) } else { $cachedLWT = $stOldManifest[$jfKey]. LastWriteTimeUtc $fileDT = $jf. LastWriteTimeUtc proovige { $cachedDT = if ($cachedLWT -is [DateTime]) { $cachedLWT.ToUniversalTime() } else { [DateTimeOffset]::P arse("$cachedLWT"). UtcDateTime } if ([matemaatika]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt 1) { [void]$changedFiles.Add($jf) } } püüdmine { [kehtetu]$changedFiles.Add($jf) } } } $totalChanged = $changedFiles.Count + $newFiles.Count $changePct = [matemaatika]::Round(($totalChanged / $totalFiles) * 100; 1) Write-Host " Muudetud: $($changedFiles.Count) | Uus: $($newFiles.Count) | Kokku: $totalChanged ($changePct%)" –esiplaanikollane if ($totalChanged -gt 0 -ja $changePct -lt 10) { # DELTA PATCH MODE: <muudetud 10%, olemasolevate andmete paikamine Write-Host " Delta paikamisrežiim ($changePct% < 10%) - failide $totalChanged paigamine..." - Esiplaanivärvi roheline $dataDir = Join-Path $OutputPath "andmed" # Muudetud/uute seadmekirjete laadimine $deltaDevices = @{} $allDeltaFiles = @($changedFiles) + @($newFiles) foreach ($df in $allDeltaFiles) { proovige { $devData = Get-Content $df. Täisnimi – toor | ConvertFrom-Json $dev = Normalize-DeviceRecord $devData if ($dev. HostName) { $deltaDevices[$dev. HostName] = $dev } } püüdmine { } } Write-Host " Loaded $($deltaDevices.Count) changed device records" -ForegroundColor Gray # Iga kategooria JSON: eemalda muudetud hostinimede vanad kirjed, lisa uued kirjed $categoryFiles = @("tõrked", "known_issues", "missing_kek", "not_updated", "task_disabled", "temp_failures", "perm_failures", "updated_devices", "action_required", "secureboot_off", "rollout_inprogress") $changedHostnames = [System.Collections.Generic.HashSet[string]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($hn in $deltaDevices.Keys) { [void]$changedHostnames.Add($hn) } foreach ($cat in $categoryFiles) { $catPath = Join-Path $dataDir "$cat.json" if (testtee $catPath) { proovige { $catData = Get-Content $catPath -Toor | ConvertFrom-Json # Eemalda muudetud hostinimede vanad kirjed $catData = @($catData | Where-Object { -not $changedHostnames.Contains($_. HostName) }) # Liigitage iga muudetud seade uuesti kategooriatesse # (lisatakse pärast klassifitseerimist allpool) $catData | ConvertTo-Json -Sügavus 5 | Set-Content $catPath – kodeerimine UTF8 } püüdmine { } } } # Liigitage iga muudetud seade ja lisage õigesse kategooriasse kuuluvad failid foreach ($dev in $deltaDevices.Values) { $slim = [tellitud]@{ HostName = $dev. Hostname WMI_Manufacturer = if ($dev. PSObject.Properties['WMI_Manufacturer']) { $dev. WMI_Manufacturer } else { "" } WMI_Model = if ($dev. PSObject.Properties['WMI_Model']) { $dev. WMI_Model } else { "" } BucketId = if ($dev. PSObject.Properties['BucketId']) { $dev. BucketId } else { "" } ConfidenceLevel = if ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } else { "" } IsUpdated = $dev. IsUpdated UEFICA2023Error = if ($dev. PSObject.Properties['UEFICA2023Error']) { $dev. UEFICA2023Error } muu { $null } SecureBootTaskStatus = if ($dev. PSObject.Properties['SecureBootTaskStatus']) { $dev. SecureBootTaskStatus } else { "" } KnownIssueId = if ($dev. PSObject.Properties['KnownIssueId']) { $dev. TeadaolevadIssueId } muu { $null } SkipReasonKnownIssue = if ($dev. PSObject.Properties['SkipReasonKnownIssue']) { $dev. SkipReasonKnownIssue } else { $null } } $isUpd = $dev. IsUpdated –eq $true $conf = if ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } else { "" } $hasErr = (-not [string]::IsNullOrEmpty($dev. UEFICA2023Error) - ja $dev. UEFICA2023Error -ne "0" -ja $dev. UEFICA2023Error – ne "") $tskDis = ($dev. SecureBootTaskEnabled -eq $false -või $dev. SecureBootTaskStatus -eq 'Disabled' -or $dev. SecureBootTaskStatus – eq 'NotFound') $tskNF = ($dev. SecureBootTaskStatus – eq 'NotFound') $sbOn = ($dev. SecureBootEnabled -ne $false -and "$($dev. SecureBootEnabled)" -ne "False") $e 1801 = if ($dev. PSObject.Properties['Event1801Count']) { [int]$dev. Sündmus1801Count } else { 0 } $e 1808 = if ($dev. PSObject.Properties['Event1808Count']) { [int]$dev. Sündmus1808Count } else { 0 } $e 1803 = if ($dev. PSObject.Properties['Event1803Count']) { [int]$dev. Sündmus1803Count } else { 0 } $mKEK = ($e 1803 -gt 0 -või $dev. MissingKEK -eq $true) $hKI = ((-not [string]::IsNullOrEmpty($dev. SkipReasonKnownIssue)) - või (-not [string]::IsNullOrEmpty($dev. KnownIssueId))) $rStat = if ($dev. PSObject.Properties['RolloutStatus']) { $dev. RolloutStatus } else { "" } # Lisa vastavatesse kategooriafailidesse $targets = @() kui ($isUpd) { $targets += "updated_devices" } if ($hasErr) { $targets += "errors" } kui ($hKI) { $targets += "known_issues" } kui ($mKEK) { $targets += "missing_kek" } kui (-not $isUpd -ja $sbOn) { $targets += "not_updated" } if ($tskDis) { $targets += "task_disabled" } kui (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPaused')) { $targets += "temp_failures" } kui (-not $isUpd -and ((Test-ConfidenceLevel $conf 'Toetuseta') -või ($tskNF -ja $hasErr))) { $targets += "perm_failures" } if (-not $isUpd -and (Test-ConfidenceLevel $conf 'ActionRequired')) { $targets += "action_required" } kui (-mitte $sbOn) { $targets += "secureboot_off" } if ($e 1801 -gt 0 -ja $e 1808 -eq 0 -and -not $hasErr -and $rStat -eq "InProgress") { $targets += "rollout_inprogress" } foreach ($tgt in $targets) { $tgtPath = Join-Path $dataDir "$tgt.json" if (testtee $tgtPath) { $existing = Get-Content $tgtPath -Toor | ConvertFrom-Json $existing = @($existing) + @([PSCustomObject]$slim) $existing | ConvertTo-Json -Sügavus 5 | Set-Content $tgtPath – kodeering UTF8 } } } # Taasta CSV-id paigatud JSON-idelt Write-Host "CSV-de taastamine paigatud andmete põhjal..." – esiplaanivärvi hall $newTimestamp = Get-Date - Format "yyyyMdd-HHmmss" foreach ($cat in $categoryFiles) { $catJsonPath = Join-Path $dataDir "$cat.json" $catCsvPath = Join-Path $OutputPath "SecureBoot_${cat}_$newTimestamp.csv" if (testtee $catJsonPath) { proovige { $catJsonData = Get-Content $catJsonPath -Toor | ConvertFrom-Json if ($catJsonData.Count -gt 0) { $catJsonData | Export-Csv – tee $catCsvPath -NoTypeInformation – UTF8 kodeerimine } } püüdmine { } } } # Recount stats from the patched JSON files Write-Host " Kokkuvõtte ümberarvutamine paigatud andmete põhjal..." – esiplaanivärvi hall $patchedStats = [tellitud]@{ ReportGeneratedAt = (Toomiskuupäev). ToString("yyyy-MM-dd HH:mm:ss") } $pTotal = 0; $pUpdated = 0; $pErrors = 0; $pKI = 0; $pKEK = 0 $pTaskDis = 0; $pTempFail = 0; $pPermFail = 0; $pActionReq = 0; $pSBOff = 0; $pRIP = 0 foreach ($cat in $categoryFiles) { $catPath = Join-Path $dataDir "$cat.json" $cnt = 0 if (testtee $catPath) { try { $cnt = (Get-Content $catPath -Raw | ConvertFrom-Json). Count } püüdmine { } } lüliti ($cat) { "updated_devices" { $pUpdated = $cnt } "errors" { $pErrors = $cnt } "known_issues" { $pKI = $cnt } "missing_kek" { $pKEK = $cnt } "not_updated" { } # arvutatud "task_disabled" { $pTaskDis = $cnt } "temp_failures" { $pTempFail = $cnt } "perm_failures" { $pPermFail = $cnt } "action_required" { $pActionReq = $cnt } "secureboot_off" { $pSBOff = $cnt } "rollout_inprogress" { $pRIP = $cnt } } } $pNotUpdated = (Get-Content (Join-path $dataDir "not_updated.json") -Raw | ConvertFrom-Json). Loendus $pTotal = $pUpdated + $pNotUpdated + $pSBOff Write-Host " Delta paik on valmis: $totalChanged seadmed on värskendatud" -Esiplaanivärvi roheline Write-Host " Kokku: $pTotal | Värskendatud: $pUpdated | Toetuseta: $pNotUpdated | Tõrked: $pErrors" – esiplaanivärv valge # Värskenda manifesti $stManifestDir = Join-Path $OutputPath ".cache" $stNewManifest = @{} foreach ($jf in $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o"); Suurus = $jf. Pikkus } } Save-FileManifest – manifesti $stNewManifest – tee $stManifestPath Write-Host " Completed in $([matemaatika]::Round($streamSw.Elapsed.TotalSeconds, 1))s (delta patch - $totalChanged devices)" -ForegroundColor Green # Minge html-i armatuurlaua taastamiseks täieliku voogesituse taastöötluse juurde # Andmefailid on juba paigatud, nii et see tagab armatuurlaua ajakohase Write-Host "Taastav armatuurlaud paigatud andmete põhjal..." –Esiplaanivärvi kollane } else { Write-Host " $changePct% faile on muudetud (>= 10%) – nõutav on täielik voogesituse ümbertöötlemine" -ForegroundColor Yellow } } } } } # Loo nõudmisel seadme JSON-failide jaoks andmete alamkataloog $dataDir = Join-Path $OutputPath "andmed" if (-not (testtee $dataDir)) { New-Item -ItemType'i kataloog -tee $dataDir -Force | Out-Null } # Deduplication via HashSet (O(1) per lookup, ~50MB for 600K hostnames) $seenHostnames = [System.Collections.Generic.HashSet[string]::new([System.StringComparer]::OrdinalIgnoreCase) # Lihtsustatud kokkuvõtteloendurid (asendab $allDevices + $uniqueDevices mälus) $c = @{ Kokku = 0; SBEnabled = 0; SBOff = 0 Värskendatud = 0; HighConf = 0; UnderObs = 0; ActionReq = 0; TempPaused = 0; Toetuseta = 0; NoConfData = 0 TaskDisabled = 0; TaskNotFound = 0; TaskDisabledNotUpdated = 0 WithErrors = 0; InProgress = 0; NotyetInitiated = 0; RolloutInProgress = 0 WithknownIssues = 0; WithMissingKEK = 0; TempFailures = 0; PermFailures = 0; NeedsReboot = 0 UpdatePending = 0 } # AtRisk/SafeListi salve jälgimine (lihtsustatud komplektid) $stFailedBuckets = [System.Collections.Generic.HashSet[string]]::new() $stSuccessBuckets = [System.Collections.Generic.HashSet[string]::new() $stAllBuckets = @{} $stMfrCounts = @{} $stErrorCodeCounts = @{}; $stErrorCodeSamples = @{} $stKnownIssueCounts = @{} # Paketirežiimis seadme andmefailid: kogunemine kogumi kohta, tühjendamine kogumi piirides $stDeviceFiles = @("tõrked", "known_issues", "missing_kek", "not_updated", "task_disabled", "temp_failures", "perm_failures", "updated_devices", "action_required", "secureboot_off", "rollout_inprogress", "under_observation", "needs_reboot", "update_pending") $stDeviceFilePaths = @{}; $stDeviceFileCounts = @{} foreach ($dfName in $stDeviceFiles) { $dfPath = Join-Path $dataDir "$dfName.json" [System.IO.File]::WriteAllText($dfPath, "['n", [System.Text.Encoding]::UTF8) $stDeviceFilePaths[$dfName] = $dfPath; $stDeviceFileCounts[$dfName] = 0 } # Slim device record for JSON output (only essential fields, ~200 bait vs ~2KB full) funktsioon Get-SlimDevice { param($Dev) tagastus [tellitud]@{ HostName = $Dev.HostName WMI_Manufacturer = if ($Dev.PSObject.Properties['WMI_Manufacturer']) { $Dev.WMI_Manufacturer } else { "" } WMI_Model = if ($Dev.PSObject.Properties['WMI_Model']) { $Dev.WMI_Model } else { "" } BucketId = if ($Dev.PSObject.Properties['BucketId']) { $Dev.BucketId } else { "" } ConfidenceLevel = if ($Dev.PSObject.Properties['ConfidenceLevel']) { $Dev.ConfidenceLevel } else { "" } IsUpdated = $Dev.IsUpdated UEFICA2023Error = if ($Dev.PSObject.Properties['UEFICA2023Error']) { $Dev.UEFICA2023Error } else { $null } SecureBootTaskStatus = if ($Dev.PSObject.Properties['SecureBootTaskStatus']) { $Dev.SecureBootTaskStatus } else { "" } KnownIssueId = if ($Dev.PSObject.Properties['KnownIssueId']) { $Dev.KnownIssueId } else { $null } SkipReasonKnownIssue = if ($Dev.PSObject.Properties['SkipReasonKnownIssue']) { $Dev.SkipReasonKnownIssue } else { $null } UEFICA2023Status = if ($Dev.PSObject.Properties['UEFICA2023Status']) { $Dev.UEFICA2023Status } else { $null } AvailableUpdatesPolicy = if ($Dev.PSObject.Properties['AvailableUpdatesPolicy']) { $Dev.AvailableUpdatesPolicy } else { $null } WinCSKeyApplied = if ($Dev.PSObject.Properties['WinCSKeyApplied']) { $Dev.WinCSKeyApplied } veel { $null } } } # Flush batch to JSON file (lisamisrežiim) funktsioon Flush-DeviceBatch { param([string]$StreamName, [System.Collections.Generic.List[object]$Batch) if ($Batch.Count -eq 0) { return } $fPath = $stDeviceFilePaths[$StreamName] $fSb = [System.Text.StringBuilder]::new() foreach ($fDev in $Batch) { if ($stDeviceFileCounts[$StreamName] -gt 0) { [kehtetu]$fSb.Append(",'n") } [void]$fSb.Append(($fDev | ConvertTo-Json -Compress)) $stDeviceFileCounts[$StreamName]++ } [System.IO.File]::AppendAllText($fPath, $fSb.ToString(), [System.Text.Encoding]::UTF8) } # PÕHIVOOGESITUSTSÜKKEL $stChunkSize = if ($totalFiles -le 10000) { $totalFiles } else { 10000 } $stTotalChunks = [matemaatika]::Ülemmäär($totalFiles / $stChunkSize) $stPeakMemMB = 0 kui ($stTotalChunks -gt 1) { Write-Host "$totalFiles failide töötlemine $stTotalChunks $stChunkSize osades (voogesitus, $ParallelThreads lõimed):" -ForegroundColor Cyan } else { Write-Host "$totalFiles failide töötlemine (voogesitus, $ParallelThreads lõimed):" -Esiplaanivärvi tsüaan } for ($ci = 0; $ci -lt $stTotalChunks; $ci++) { $cStart = $ci * $stChunkSize $cEnd = [matemaatika]::Min($cStart + $stChunkSize, $totalFiles) - 1 $cFiles = $jsonFiles[$cStart.. $cEnd] kui ($stTotalChunks -gt 1) { Write-Host " Chunk $($ci + 1)/$stTotalChunks ($($cFiles.Count) failid): " -NoNewline -ForegroundColor Gray } else { Write-Host " $($cFiles.Count) failide laadimine: " -NoNewline -ForegroundColor Gray } $cSw = [System.Diagnostics.Stopwatch]::StartNew() $rawDevices = Load-FilesParallel -Files $cFiles –lõimed $ParallelThreads # Kogumipõhised paketiloendid $cBatches = @{} foreach ($df $stDeviceFiles) { $cBatches[$df] = [System.Collections.Generic.List[object]]:new() } $cNew = 0; $cDupe = 0 foreach ($raw $rawDevices) { kui (-ei $raw) { continue } $device = Normalize-DeviceRecord $raw $hostname = $device. Hostname kui (-pole $hostname) { continue } if ($seenHostnames.Contains($hostname)) { $cDupe++; continue } [void]$seenHostnames.Add($hostname) $cNew++; $c.Total++ $sbOn = ($device. SecureBootEnabled -ne $false -and "$($device. SecureBootEnabled)" -ne "False") kui ($sbOn) { $c.SBEnabled++ } else { $c.SBOff++; $cBatches["secureboot_off"]. Add((Get-SlimDevice $device)) } $isUpd = $device. IsUpdated –eq $true $conf = if ($device. PSObject.Properties['ConfidenceLevel'] -and $device. ConfidenceLevel) { "$($device. ConfidenceLevel)" } else { "" } $hasErr = (-not [string]::IsNullOrEmpty($device. UEFICA2023Error) - ja "$($device. UEFICA2023Error)" -ne "0" -ja "$($device. UEFICA2023Error)" -ne "") $tskDis = ($device. SecureBootTaskEnabled -eq $false -or "$($device. SecureBootTaskStatus)" -eq 'Disabled' -or "$($device. SecureBootTaskStatus)" -eq 'NotFound') $tskNF = ("$($device. SecureBootTaskStatus)" -eq 'NotFound') $bid = if ($device. PSObject.Properties['BucketId'] -and $device. BucketId) { "$($device. BucketId)" } else { "" } $e 1808 = if ($device. PSObject.Properties['Event1808Count']) { [int]$device. Sündmus1808Count } else { 0 } $e 1801 = if ($device. PSObject.Properties['Event1801Count']) { [int]$device. Sündmus1801Count } else { 0 } $e 1803 = if ($device. PSObject.Properties['Event1803Count']) { [int]$device. Sündmus1803Count } else { 0 } $mKEK = ($e 1803 -gt 0 -või $device. MissingKEK -eq $true -or "$($device. MissingKEK)" -eq "True") $hKI = ((-not [string]::IsNullOrEmpty($device. SkipReasonKnownIssue)) - või (-not [string]::IsNullOrEmpty($device. KnownIssueId))) $rStat = if ($device. PSObject.Properties['RolloutStatus']) { $device. RolloutStatus } else { "" } $mfr = if ($device. PSObject.Properties['WMI_Manufacturer'] -and -not [string]::IsNullOrEmpty($device. WMI_Manufacturer)) { $device. WMI_Manufacturer } else { "Unknown" } $bid = kui (-not [string]::IsNullOrEmpty($bid)) { $bid } else { "" } # Eelarvutus: värskenduse ootel lipp (poliitika/WinCS on rakendatud, olek pole veel värskendatud, SB ON, ülesanne pole keelatud) $uefiStatus = if ($device. PSObject.Properties['UEFICA2023Status']) { "$($device. UEFICA2023Status)" } else { "" } $hasPolicy = ($device. PSObject.Properties['AvailableUpdatesPolicy'] -and $null -ne $device. AvailableUpdatesPolicy ja "$($device. AvailableUpdatesPolicy)" -ne '') $hasWinCS = ($device. PSObject.Properties['WinCSKeyApplied'] - ja $device. WinCSKeyApplied – eq $true) $statusPending = ([string]::IsNullOrEmpty($uefiStatus) -või $uefiStatus -eq 'NotStarted' -or $uefiStatus -eq 'InProgress') $isUpdatePending = (($hasPolicy -või $hasWinCS) -ja $statusPending -and -not $isUpd -ja $sbOn -and -not $tskDis) kui ($isUpd) { $c.Updated++; [void]$stSuccessBuckets.Add($bid); $cBatches["updated_devices"]. Add((Get-SlimDevice $device)) # Jälgi värskendatud seadmeid, mis vajavad taaskäivitamist (UEFICA2023Status=Värskendatud, kuid Sündmus1808=0) if ($e 1808 -eq 0) { $c.NeedsReboot++; $cBatches["needs_reboot"]. Add((Get-SlimDevice $device)) } } elseif (-mitte $sbOn) { # SecureBoot OFF – pole ulatusest väljas, ärge liigitage kindlustunde järgi } else { if ($isUpdatePending) { } # Loendatakse eraldi jaotises Värskendamise ootel – vastastikku eksklusiivselt sektordiagrammi jaoks elseif (Test-ConfidenceLevel $conf "HighConfidence") { $c.HighConf++ } elseif (Test-ConfidenceLevel $conf "UnderObservation") { $c.UnderObs++ } elseif (Test-ConfidenceLevel $conf "TemporarilyPaused") { $c.TempPaused++ } elseif (Test-ConfidenceLevel $conf "NotSupported") { $c.NotSupported++ } else { $c.ActionReq++ } if ([string]::IsNullOrEmpty($conf)) { $c.NoConfData++ } } if ($tskDis) { $c.TaskDisabled++; $cBatches["task_disabled"]. Add((Get-SlimDevice $device)) } if ($tskNF) { $c.TaskNotFound++ } kui (-not $isUpd -and $tskDis) { $c.TaskDisabledNotUpdated++ } if ($hasErr) { $c.WithErrors++; [kehtetu]$stFailedBuckets.Add($bid); $cBatches["tõrked"]. Add((Get-SlimDevice $device)) $ec = $device. UEFICA2023Error if (-not $stErrorCodeCounts.ContainsKey($ec)) { $stErrorCodeCounts[$ec] = 0; $stErrorCodeSamples[$ec] = @() } $stErrorCodeCounts[$ec]++ if ($stErrorCodeSamples[$ec]. Count -lt 5) { $stErrorCodeSamples[$ec] += $hostname } } kui ($hKI) { $c.WithknownIssues++; $cBatches["known_issues"]. Add((Get-SlimDevice $device)) $ki = if (-not [string]::IsNullOrEmpty($device. SkipReasonKnownIssue)) { $device. SkipReasonKnownIssue } else { $device. KnownIssueId } kui (-not $stKnownIssueCounts.ContainsKey($ki)) { $stKnownIssueCounts[$ki] = 0 }; $stKnownIssueCounts[$ki]++ } if ($mKEK) { $c.WithMissingKEK++; $cBatches["missing_kek"]. Add((Get-SlimDevice $device)) } kui (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPaused'))) { $c.TempFailures++; $cBatches["temp_failures"]. Add((Get-SlimDevice $device)) } kui (-not $isUpd -and ((Test-ConfidenceLevel $conf 'Toetuseta') -või ($tskNF -and $hasErr))) { $c.PermFailures++; $cBatches["perm_failures"]. Add((Get-SlimDevice $device)) } if ($e 1801 -gt 0 -and $e 1808 -eq 0 -and -not $hasErr -and $rStat -eq "InProgress") { $c.RolloutInProgress++; $cBatches["rollout_inprogress"]. Add((Get-SlimDevice $device)) } if ($e 1801 -gt 0 -and $e 1808 -eq 0 -and -not $hasErr -and $rStat -ne "InProgress") { $c.NotYetInitiated++ } if ($rStat -eq "InProgress" -and $e 1808 -eq 0) { $c.InProgress++ } # Värskendamise ootel: poliitika või WinCS on rakendatud, olek on ootel, SB ON, ülesanne pole keelatud kui ($isUpdatePending) { $c.UpdatePending++; $cBatches["update_pending"]. Add((Get-SlimDevice $device)) } if (-not $isUpd -and $sbOn) { $cBatches["not_updated"]. Add((Get-SlimDevice $device)) } # Jaotises Jälgimisseadmed (eraldi toimingust nõutav) if (-not $isUpd -and (Test-ConfidenceLevel $conf 'UnderObservation')) { $cBatches["under_observation"]. Add((Get-SlimDevice $device)) } # Nõutav toiming: värskendamata, SB ON, ei vasta muudele usalduskategooriatele, mitte värskendamise ootel kui (-not $isUpd -and $sbOn -and -not $isUpdatePending -and -not (Test-ConfidenceLevel $conf 'HighConfidence') -and -not (Test-ConfidenceLevel $conf 'UnderObservation') -and -not (Test-ConfidenceLevel $conf 'AjutiseltPaused') -and -not (Test-ConfidenceLevel $conf 'Toetuseta')) { $cBatches["action_required"]. Add((Get-SlimDevice $device)) } kui (-not $stMfrCounts.ContainsKey($mfr)) { $stMfrCounts[$mfr] = @{ Total=0; Värskendatud=0; UpdatePending=0; HighConf=0; UnderObs=0; ActionReq=0; TempPaused=0; Toetuseta=0; SBOff=0; WithErrors=0 } } $stMfrCounts[$mfr]. Kokku++ if ($isUpd) { $stMfrCounts[$mfr]. Värskendatud+ + } elseif (-not $sbOn) { $stMfrCounts[$mfr]. SBOff++ } elseif ($isUpdatePending) { $stMfrCounts[$mfr]. UpdatePending++ } elseif (Test-ConfidenceLevel $conf "HighConfidence") { $stMfrCounts[$mfr]. HighConf++ } elseif (Test-ConfidenceLevel $conf "UnderObservation") { $stMfrCounts[$mfr]. UnderObs++ } elseif (Test-ConfidenceLevel $conf "TemporarilyPaused") { $stMfrCounts[$mfr]. TempPaused++ } elseif (Test-ConfidenceLevel $conf "NotSupported") { $stMfrCounts[$mfr]. Toetuseta++ } else { $stMfrCounts[$mfr]. ActionReq++ } if ($hasErr) { $stMfrCounts[$mfr]. WithErrors++ } # Jälgi kõiki seadmeid salve järgi (sh tühi BucketId) $bucketKey = if ($bid -ja $bid -ne "") { $bid } else { "(empty)" } kui (-not $stAllBuckets.ContainsKey($bucketKey)) { $stAllBuckets[$bucketKey] = @{ Count=0; Värskendatud=0; Manufacturer=$mfr; Mudel=""; BIOS="" } if ($device. PSObject.Properties['WMI_Model']) { $stAllBuckets[$bucketKey]. Mudel = $device. WMI_Model } if ($device. PSObject.Properties['BIOSDescription']) { $stAllBuckets[$bucketKey]. BIOS = $device. BIOSDescription } } $stAllBuckets[$bucketKey]. Count++ if ($isUpd) { $stAllBuckets[$bucketKey]. Värskendatud+ + } } # Puhasta partiid kettale foreach ($df $stDeviceFiles) { Flush-DeviceBatch -StreamName $df -Batch $cBatches[$df] } $rawDevices = $null; $cBatches = $null; [System.GC]::Collect() $cSw.Stop() $cTime = [Matemaatika]::Round($cSw.Elapsed.TotalSeconds, 1) $cRem = $stTotalChunks – $ci - 1 $cEta = if ($cRem -gt 0) { " | ETA: ~$([Matemaatika]::Round($cRem * $cSw.Elapsed.TotalSeconds / 60, 1)) min" } else { "" } $cMem = [matemaatika]::Round([System.GC]::GetTotalMemory($false) / 1MB, 0) if ($cMem -gt $stPeakMemMB) { $stPeakMemMB = $cMem } Write-Host " +$cNew uus, $cDupe dupes, ${cTime}s | Mem: ${cMem}MB$cEta" -ForegroundColor Green } # JSON-massiivide viimistlemine foreach ($dfName in $stDeviceFiles) { [System.IO.File]::AppendAllText($stDeviceFilePaths[$dfName], "'n]", [System.Text.Encoding]::UTF8) Write-Host " $dfName.json: $($stDeviceFileCounts[$dfName]) seadmed" -ForegroundColor DarkGray } # Compute tuletatud statistika $stAtRisk = 0; $stSafeList = 0 foreach ($bid $stAllBuckets.Keys) { $b = $stAllBuckets[$bid]; $nu = $b.Count – $b.Värskendatud if ($stFailedBuckets.Contains($bid)) { $stAtRisk += $nu } elseif ($stSuccessBuckets.Contains($bid)) { $stSafeList += $nu } } $stAtRisk = [matemaatika]:Max(0; $stAtRisk – $c.WithErrors) # NotUptodate = arv not_updated paketist (SB-ga ja värskendamata seadmed) $stNotUptodate = $stDeviceFileCounts["not_updated"] $stats = [tellitud]@{ ReportGeneratedAt = (Get-Date). ToString("yyyy-MM-dd HH:mm:ss") TotalDevices = $c.Total; SecureBootEnabled = $c.SBEnabled; SecureBootOFF = $c.SBOff Värskendatud = $c.Värskendatud; HighConfidence = $c.HighConf; UnderObservation = $c.UnderObs ActionRequired = $c.ActionReq; TemporarilyPaused = $c.TempPaused; Toetuseta = $c.Toetuseta NoConfidenceData = $c.NoConfData; TaskDisabled = $c.TaskDisabled; TaskNotFound = $c.TaskNotFound TaskDisabledNotUpdated = $c.TaskDisabledNotUpdated CertificatesUpdated = $c.Updated; NotUptodate = $stNotUptodate; FullyUpdated = $c.Updated UpdatesPending = $stNotUptodate; UpdatesComplete = $c.Updated WithErrors = $c.WithErrors; InProgress = $c.InProgress; NotYetInitiated = $c.NotYetInitiated RolloutInProgress = $c.RolloutInProgress; WithknownIssues = $c.WithknownIssues WithMissingKEK = $c.WithMissingKEK; TemporaryFailures = $c.TempFailures; PermanentFailures = $c.PermFailures NeedsReboot = $c.NeedsReboot; UpdatePending = $c.UpdatePending AtRiskDevices = $stAtRisk; SafeListDevices = $stSafeList PercentWithErrors = if ($c.Total -gt 0) { [matemaatika]::Round(($c.WithErrors/$c.Total)*100,2) } else { 0 } PercentAtRisk = if ($c.Total -gt 0) { [matemaatika]::Round(($stAtRisk/$c.Total)*100,2) } else { 0 } PercentSafeList = if ($c.Total -gt 0) { [matemaatika]::Round(($stSafeList/$c.Total)*100,2) } else { 0 } PercentHighConfidence = if ($c.Total -gt 0) { [matemaatika]::Round(($c.HighConf/$c.Total)*100,1) } else { 0 } PercentCertUpdated = if ($c.Total -gt 0) { [matemaatika]::Round(($c.Updated/$c.Total)*100,1) } else { 0 } PercentActionRequired = if ($c.Total -gt 0) { [matemaatika]::Round(($c.ActionReq/$c.Total)*100,1) } else { 0 } PercentNotUptodate = if ($c.Total -gt 0) { [matemaatika]::Round($stNotUptodate/$c.Total*100,1) } else { 0 } PercentFullyUpdated = if ($c.Total -gt 0) { [matemaatika]::Round(($c.Updated/$c.Total)*100,1) } else { 0 } UniqueBuckets = $stAllBuckets.Count; PeakMemoryMB = $stPeakMemMB; ProcessingMode = "Streaming" } # KIRJUTA CSV-sid [PSCustomObject]$stats | Export-Csv - tee (liitmistee $OutputPath "SecureBoot_Summary_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } – laskuv järjestus | ForEach-Object { [PSCustomObject]@{ Manufacturer=$_. Võti; Count=$_. Value.Total; Värskendatud=$_. Value.Updated; HighConfidence=$_. Value.HighConf; ActionRequired=$_. Value.ActionReq } } | Export-Csv -tee (liitmistee $OutputPath "SecureBoot_ByManufacturer_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stErrorCodeCounts.GetEnumerator() | Sort-Object väärtus - laskuvas järjestuses | ForEach-Object { [PSCustomObject]@{ ErrorCode=$_. Võti; Count=$_. Väärtus; SampleDevices=($stErrorCodeSamples[$_. Key] -join ", ") } } | Export-Csv - tee (liitmistee $OutputPath "SecureBoot_ErrorCodes_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stAllBuckets.GetEnumerator() | Sort-Object { $_. Value.Count } – laskuvas järjestuses | ForEach-Object { [PSCustomObject]@{ BucketId=$_. Võti; Count=$_. Value.Count; Värskendatud=$_. Value.Updated; NotUpdated=$_. Value.Count-$_. Value.Updated; Tootja=$_. Value.Manufacturer } } | Export-Csv - tee (liitmistee $OutputPath "SecureBoot_UniqueBuckets_$timestamp.csv") -NoTypeInformation -Encoding UTF8 # Loo orkestriga ühilduvad CSV-d (Start-SecureBootRolloutOrchestrator.ps1 eeldatavad failinimed) $notUpdatedJsonPath = Join-Path $dataDir "not_updated.json" if (testtee $notUpdatedJsonPath) { proovige { $nuData = Get-Content $notUpdatedJsonPath -Toor | ConvertFrom-Json if ($nuData.Count -gt 0) { # NotUptodate CSV - orchestrator searches for *NotUptodate*.csv $nuData | Export-Csv - tee (join-path $OutputPath "SecureBoot_NotUptodate_$timestamp.csv") -NoTypeInformation -Encoding UTF8 Write-Host " Orchestrator CSV: SecureBoot_NotUptodate_$timestamp.csv ($($nuData.Count) devices)" -ForegroundColor Gray } } püüdmine { } } # JSON-andmete kirjutamine armatuurlauale $stats | ConvertTo-Json -Sügavus 3 | Set-Content (join-path $dataDir "summary.json") - Kodeerimine UTF8 # AJALOOLINE JÄLITUS: Andmepunkti salvestamine trendidiagrammi jaoks # Kasutage stabiilset vahemäluasukohta, et trendiandmed püsivad ajatempliga koondatud kaustades. # Kui OutputPath näeb välja nagu "...\Aggregation_yyyyMMdd_HHmmss", läheb vahemälu emakausta.# Muidu läheb vahemälu OutputPathi.$parentDir = Split-Path $OutputPath –ema $leafName = Split-Path $OutputPath -Leht if ($leafName -match '^Aggregation_\d{8}' -või $leafName -eq 'Aggregation_Current') { # Orchestrator-created timestamped folder – kasutage stabiilse vahemälu jaoks emakausta $historyPath = Join-Path $parentDir ".cache\trend_history.json" } else { $historyPath = Join-Path $OutputPath ".cache\trend_history.json" } $historyDir = Split-Path $historyPath -ema if (-not (testtee $historyDir)) { New-Item -ItemType'i kataloog -tee $historyDir -Force | Out-Null } $historyData = @() if (testtee $historyPath) { proovige { $historyData = @(Get-Content $historyPath -Raw | ConvertFrom-Json) } püüdmine { $historyData = @() } } # Kontrollige ka outputPath\.cache\ (vanemate versioonide pärandasukoht) # Ühenda andmepunktid, mis pole veel põhiajaloos if ($leafName -eq 'Aggregation_Current' -või $leafName -match '^Aggregation_\d{8}') { $innerHistoryPath = Join-Path $OutputPath ".cache\trend_history.json" if ((testtee $innerHistoryPath) -ja $innerHistoryPath -ne $historyPath) { proovige { $innerData = @(Get-Content $innerHistoryPath -Raw | ConvertFrom-Json) $existingDates = @($historyData | ForEach-Object { $_. Kuupäev }) foreach ($entry in $innerData) { if ($entry. Kuupäev ja $entry. Kuupäev -pole $existingDates) { $historyData += $entry } } if ($innerData.Count -gt 0) { Write-Host " Ühendatud $($innerData.Count) andmepunktid sisemisest vahemälust" -ForegroundColor DarkGray } } püüdmine { } } }
# BOOTSTRAP: Kui trendiajalugu on tühi/hõre, taastage ajalooliste andmete põhjal if ($historyData.Count -lt 2 -and ($leafName -match '^Aggregation_\d{8}' -või $leafName -eq 'Aggregation_Current')) { Write-Host " Bootstrapping trendiajalugu ajaloolistest andmetest..." -Esiplaanivärvi kollane $dailyData = @{} # 1. allikas: csV-kokkuvõtted praeguses kaustas (Aggregation_Current säilitab kõik CSV-sid kokkuvõtvad CSV-d) $localSummaries = Get-ChildItem $OutputPath -Filter "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Sort-Object nimi foreach ($summCsv in $localSummaries) { proovige { $summ = Import-Csv $summCsv.FullName | Select-Object – esimene 1 if ($summ. TotalDevices - and [int]$summ. TotalDevices -gt 0 -and $summ. ReportGeneratedAt) { $dateStr = ([kuupäev ja kellaaeg]$summ. ReportGeneratedAt). ToString("yyyy-MM-dd") $updated = if ($summ. Värskendatud) { [int]$summ. Värskendatud } veel { 0 } $notUpd = if ($summ. NotUptodate) { [int]$summ. NotUptodate } else { [int]$summ. TotalDevices – $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Kuupäev = $dateStr; Kokku = [int]$summ. TotalDevices; Värskendatud = $updated; NotUpdated = $notUpd NeedsReboot = 0; Vead = 0; ActionRequired = if ($summ. ActionRequired) { [int]$summ. ActionRequired } else { 0 } } } } püüdmine { } } # 2. allikas: vanad ajatempliga Aggregation_* kaustad (pärandkaustad, kui need on endiselt olemas) $aggFolders = Get-ChildItem $parentDir -Directory -Filter "Aggregation_*" -EA SilentlyContinue | Where-Object { $_. Nimi -vaste '^Aggregation_\d{8}' } | Sort-Object nimi foreach ($folder in $aggFolders) { $summCsv = Get-ChildItem $folder. FullName – filter "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Select-Object – esimene 1 if ($summCsv) { proovige { $summ = Import-Csv $summCsv.FullName | Select-Object –esimene 1 if ($summ. TotalDevices - and [int]$summ. TotalDevices -gt 0) { $dateStr = $folder. Nimi – asenda :^Aggregation_(\d{4})(\d{2})(\d{2})_.*, '$1-$2-$3' $updated = if ($summ. Värskendatud) { [int]$summ. Värskendatud } veel { 0 } $notUpd = if ($summ. NotUptodate) { [int]$summ. NotUptodate } else { [int]$summ. TotalDevices – $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Kuupäev = $dateStr; Kokku = [int]$summ. TotalDevices; Värskendatud = $updated; NotUpdated = $notUpd NeedsReboot = 0; Vead = 0; ActionRequired = if ($summ. ActionRequired) { [int]$summ. ActionRequired } else { 0 } } } } püüdmine { } } } # Allikas 3: RolloutState.json WaveHistory (sisaldab lainepõhiseid ajatempleid alates 1. päevast) # See annab lähteandmepunktid ka siis, kui vanu koondamiskaustu pole $rolloutStatePaths = @( (Join-path $parentDir "RolloutState\RolloutState.json"), (Join-Path $OutputPath "RolloutState\RolloutState.json") ) foreach ($rsPath in $rolloutStatePaths) { if (testtee $rsPath) { proovige { $rsData = Get-Content $rsPath -Toor | ConvertFrom-Json if ($rsData.WaveHistory) { # Kasuta laine alguskuupäevi trendi andmepunktidena # Arvutab igale lainele suunatud kumulatiivsed seadmed $cumulativeTargeted = 0 foreach ($wave in $rsData.WaveHistory) { kui ($wave. StartedAt - and $wave. DeviceCount) { $waveDate = ([kuupäev ja kellaaeg]$wave. StartedAt). ToString("yyyy-MM-dd") $cumulativeTargeted += [int]$wave. DeviceCount kui (-not $dailyData.ContainsKey($waveDate)) { # Ligikaudne: laine algusajal värskendati ainult eelnevate lainete seadmeid $dailyData[$waveDate] = [PSCustomObject]@{ Kuupäev = $waveDate; Kokku = $c.Kogusumma; Värskendatud = [matemaatika]::Max(0; $cumulativeTargeted – [int]$wave. DeviceCount) NotUpdated = $c.Total – [matemaatika]:Max(0; $cumulativeTargeted – [int]$wave. DeviceCount) NeedsReboot = 0; Vead = 0; ActionRequired = 0 } } } } } } püüdmine { } break # Use first found } }
if ($dailyData.Count -gt 0) { $historyData = @($dailyData.GetEnumerator() | Sort-Object võti | ForEach-Object { $_. Väärtus }) Write-Host " Bootstrapped $($historyData.Count) andmepunktid ajaloolistest kokkuvõtetest" -Esiplaanivärviroheline } }
# Praeguse andmepunkti lisamine (deduplicate by day - keep latest per day) $todayKey = (toomiskuupäev). ToString("yyyy-MM-dd") $existingToday = $historyData | Where-Object { "$($_. Date)" -like "$todayKey*" } if ($existingToday) { # Asenda tänane kirje $historyData = @($historyData | Where-Object { "$($_. Date)" -notlike "$todayKey*" }) } $historyData += [PSCustomObject]@{ Kuupäev = $todayKey Kokku = $c.Kogusumma Värskendatud = $c.Värskendatud Toetuseta = $stNotUptodate NeedsReboot = $c.NeedsReboot Tõrked = $c.WithErrors ActionRequired = $c.ActionReq } # Eemaldage halvad andmepunktid (kokku 0) ja säilita viimased 90 $historyData = @($historyData | Where-Object { [int]$_. Kokku -gt 0 }) # Ülempiirita – trendiandmed on ~100 baiti/kirjet, täisaasta = ~36 kB $historyData | ConvertTo-Json -Sügavus 3 | Set-Content $historyPath – kodeering UTF8 Write-Host " Trendiajalugu: $($historyData.Count) andmepunktid" -ForegroundColor DarkGray # Järk trendidiagrammi andmed HTML-i jaoks $trendLabels = ($historyData | ForEach-Object { "$($_. Date)'" }) -join "," $trendUpdated = ($historyData | ForEach-Object { $_. Värskendatud }) -join "," $trendNotUpdated = ($historyData | ForEach-Object { $_. NotUpdated }) - liitu "," $trendTotal = ($historyData | ForEach-Object { $_. Kokku }) -liitmine "," # Projection: extend trend line using exponential doubling (2,4,8,16...) # Tuletab laine suuruse ja vaatlusperioodi tegelikest trendiajaloo andmetest.# - Laine suurus = ajaloo suurim üheperioodiline kasv (kõige hiljutisem juurutatud laine) # - Vaatluspäevad = trendi andmepunktide keskmine kalendripäev (kui sageli me käitame) # Seejärel kahekordistab iga perioodi laine suurust, mis vastab orkestri 2x kasvustrateegiale.$projLabels = ""; $projUpdated = ""; $projNotUpdated = ""; $hasProjection = $false if ($historyData.Count -ge 2) { $lastUpdated = $c.Värskendatud $remaining = $stNotUptodate # Ainult SB-ON-i värskendamata seadmed (välja arvatud SecureBoot OFF) $projDates = @(); $projValues = @(); $projNotUpdValues = @() $projDate = Get-Date
# Laine suuruse ja vaatlusperioodi tuletamine trendiajaloost $increments = @() $dayGaps = @() for ($hi = 1; $hi -lt $historyData.Count; $hi++) { $inc = $historyData[$hi]. Värskendatud – $historyData[$hi-1]. Uuendatud kui ($inc -gt 0) { $increments += $inc } proovige { $d 1 = [datetime]::P arse($historyData[$hi-1]. Kuupäev) $d 2 = [datetime]::P arse($historyData[$hi]. Kuupäev) $gap = ($d 2 - $d 1). Kogupäevad kui ($gap -gt 0) { $dayGaps += $gap } } püüdke {} } # Laine suurus = viimane positiivne inkrement (praegune laine), tagasilöök keskmisele, minimaalne 2 $waveSize = if ($increments. Loendus -gt 0) { [matemaatika]::Max(2; $increments[-1]) } else { 2 } # Vaatlusperiood = keskmine vahe andmepunktide vahel (kalendripäevad laine kohta), minimaalne 1 $waveDays = if ($dayGaps.Count -gt 0) { [matemaatika]::Max(1; [matemaatika]::Round(($dayGaps | Measure-Object -Keskmine). Keskmine; 0)) } else { 1 }
Write-Host " Projection: waveSize=$waveSize (alates viimasest astmest), waveDays=$waveDays (avg gap from history)" -ForegroundColor DarkGray
$dayCounter = 0 # Projitseerida, kuni kõik seadmed on värskendatud või 365 päeva max for ($pi = 1; $pi -le 365; $pi++) { $projDate = $projDate.AddDays(1) $dayCounter++ # Juurutage igal vaatlusperioodi piiril laine, seejärel topelt if ($dayCounter -ge $waveDays) { $devicesThisWave = [matemaatika]:Min($waveSize; $remaining) $lastUpdated += $devicesThisWave $remaining –= $devicesThisWave if ($lastUpdated -gt ($c.Updated + $stNotUptodate)) { $lastUpdated = $c.Updated + $stNotUptodate; $remaining = 0 } # Kahelaineline suurus järgmise perioodi jaoks (orchestrator 2x strategy) $waveSize = $waveSize * 2 $dayCounter = 0 } $projDates += "'$($projDate.ToString("yyyy-MM-dd")")"" $projValues += $lastUpdated $projNotUpdValues += [matemaatika]:Max(0; $remaining) if ($remaining -le 0) { break } } $projLabels = $projDates -join "," $projUpdated = $projValues -join "," $projNotUpdated = $projNotUpdValues -join "," $hasProjection = $projDates.Count -gt 0 } elseif ($historyData.Count -eq 1) { Write-Host " Projection: need at at 2 trend data points to derive wave timing" -ForegroundColor DarkGray } # Siin-stringi jaoks diagrammi kombineeritud andmestringide koostamine $allChartLabels = kui ($hasProjection) { "$trendLabels,$projLabels" } else { $trendLabels } $projDataJS = kui ($hasProjection) { $projUpdated } else { "" } $projNotUpdJS = kui ($hasProjection) { $projNotUpdated } else { "" } $histCount = ($historyData | Mõõt-objekt). Loendus $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } – laskuv järjestus | ForEach-Object { @{ nimi=$_. Võti; total=$_. Value.Total; värskendatud=$_. Value.Updated; highConf=$_. Value.HighConf; actionReq=$_. Value.ActionReq } } | ConvertTo-Json -Sügavus 3 | Set-Content (join-path $dataDir "manufacturers.json") – kodeerimine UTF8 # JSON-andmefailide teisendamine CSV-failiks inimle loetavate Exceli allalaaditavate failide jaoks Write-Host "Seadme andmete teisendamine CSV-failiks Exceli allalaadimiseks..." – esiplaanivärv, hall foreach ($dfName in $stDeviceFiles) { $jsonFile = Join-Path $dataDir "$dfName.json" $csvFile = Join-Path $OutputPath "SecureBoot_${dfName}_$timestamp.csv" if (testtee $jsonFile) { proovige { $jsonData = Get-Content $jsonFile -Toor | ConvertFrom-Json if ($jsonData.Count -gt 0) { # Kaasa csV-update_pending lisaveerud $selectProps = if ($dfName -eq "update_pending") { @('HostName', 'WMI_Manufacturer', 'WMI_Model', 'BucketId', 'ConfidenceLevel', 'IsUpdated', 'UEFICA2023Status', 'UEFICA2023Error', 'AvailableUpdatesPolicy', 'WinCSKeyApplied', 'SecureBootTaskStatus') } else { @('HostName', 'WMI_Manufacturer', 'WMI_Model', 'BucketId', 'ConfidenceLevel', 'IsUpdated', 'UEFICA2023Error', 'SecureBootTaskStatus', 'KnownIssueId', 'SkipReasonKnownIssue') } $jsonData | Select-Object $selectProps | Export-Csv – tee $csvFile -NoTypeInformation – UTF8 kodeerimine Write-Host " $dfName -> $($jsonData.Count) read -> CSV" -ForegroundColor DarkGray } } catch { Write-Host " $dfName - skipped" -ForegroundColor DarkYellow } } } # Genereeri iseseisev HTML-armatuurlaud $htmlPath = Join-Path $OutputPath "SecureBoot_Dashboard_$timestamp.html" Write-Host "Isehallatava HTML-armatuurlaua genereerimine..." – esiplaanivärvi kollane värv # VELOCITY PROJECTION: Arvuta skannimise ajaloost või eelmisest kokkuvõttest $stDeadline = [datetime]"2026-06-24" # KEK cert expiry $stDaysToDeadline = [matemaatika]::Max(0; ($stDeadline - (Toomiskuupäev)). Päeva) $stDevicesPerDay = 0 $stProjectedDate = $null $stVelocitySource = "N/A" $stWorkingDays = 0 $stCalendarDays = 0 # Proovige kõigepealt trendiajalugu (lihtsustatud, mida juba haldab liitja – asendab bloobitud ScanHistory.json) if ($historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Kokku -gt 0 -ja [int]$_. Värskendatud – ge 0 }) if ($validHistory.Count -ge 2) { $prev = $validHistory[-2]; $curr = $validHistory[-1] $prevDate = [datetime]::P arse($prev. Date.Substring(0; [Matemaatika]:Min(10; $prev. Date.Length))) $currDate = [datetime]::P arse($curr. Date.Substring(0; [Matemaatika]:Min(10; $curr). Date.Length))) $daysDiff = ($currDate - $prevDate). Kogupäevad kui ($daysDiff -gt 0) { $updDiff = [int]$curr. Värskendatud – [int]$prev. Uuendatud kui ($updDiff -gt 0) { $stDevicesPerDay = [matemaatika]::Round($updDiff / $daysDiff; 0) $stVelocitySource = "TrendHistory" } } } } # Proovige orkestraatori väljalaske kokkuvõtet (on eelarvutatud kiirusega) if ($stVelocitySource -eq "N/A" -and $RolloutSummaryPath -and (testtee $RolloutSummaryPath)) { proovige { $rolloutSummary = Get-Content $RolloutSummaryPath -Toor | ConvertFrom-Json if ($rolloutSummary.DevicesPerDay -and [double]$rolloutSummary.DevicesPerDay -gt 0) { $stDevicesPerDay = [matemaatika]::Round([topelt]$rolloutSummary.DevicesPerDay, 1) $stVelocitySource = "Orchestrator" if ($rolloutSummary.ProjectedCompletionDate) { $stProjectedDate = $rolloutSummary.ProjectedCompletionDate } if ($rolloutSummary.WorkingDaysRemaining) { $stWorkingDays = [int]$rolloutSummary.WorkingDaysRemaining } if ($rolloutSummary.CalendarDaysRemaining) { $stCalendarDays = [int]$rolloutSummary.CalendarDaysRemaining } } } püüdmine { } } # Fallback: proovige eelmist kokkuvõtet CSV-faili (otsi praegusest kaustast JA ema-/õed-õdede liitmiskaustadest) if ($stVelocitySource -eq "N/A") { $searchPaths = @( (Join-Path $OutputPath "SecureBoot_Summary_*.csv") ) # Lisaks otsitakse õdeüksuste liitmiskaustu (orchestrator loob iga käituse jaoks uue kausta) $parentPath = Split-Path $OutputPath -ema if ($parentPath) { $searchPaths += (liitmistee $parentPath "Aggregation_*\SecureBoot_Summary_*.csv") $searchPaths += (liitmistee $parentPath "SecureBoot_Summary_*.csv") } $prevSummary = $searchPaths | ForEach-Object { Get-ChildItem $_ -EA SilentlyContinue } | Sort-Object LastWriteTime -Descending | Select-Object – esimene 1 kui ($prevSummary) { proovige { $prevStats = Get-Content $prevSummary.FullName | ConvertFrom-Csv $prevDate = [datetime]$prevStats.ReportGeneratedAt $daysSinceLast = ((Toomiskuupäev) - $prevDate). Kogupäevad if ($daysSinceLast -gt 0,01) { $prevUpdated = [int]$prevStats.Updated $updDelta = $c.Updated – $prevUpdated kui ($updDelta -gt 0) { $stDevicesPerDay = [matemaatika]::Round($updDelta / $daysSinceLast; 0) $stVelocitySource = "PreviousReport" } } } püüdmine { } } } # Fallback: täistrendiajaloo span-i kiiruse arvutamine (esimene vs uusim andmepunkt) if ($stVelocitySource -eq "N/A" -and $historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Kokku -gt 0 -ja [int]$_. Värskendatud – ge 0 }) if ($validHistory.Count -ge 2) { $first = $validHistory[0] $last = $validHistory[-1] $firstDate = [datetime]::P arse($first. Date.Substring(0; [Matemaatika]:Min(10; $first). Date.Length))) $lastDate = [datetime]::P arse($last. Date.Substring(0; [Matemaatika]:Min(10; $last. Date.Length))) $daysDiff = ($lastDate - $firstDate). Kogupäevad kui ($daysDiff -gt 0) { $updDiff = [int]$last. Värskendatud – [int]$first. Uuendatud kui ($updDiff -gt 0) { $stDevicesPerDay = [matemaatika]::Round($updDiff / $daysDiff, 1) $stVelocitySource = "TrendHistory" } } } } # Arvuta projitseerimine eksponentsil doublingu abil (kooskõlas trendidiagrammiga) # Kasutage uuesti diagrammi jaoks juba arvutatud projektsiooniandmeid, kui need on saadaval if ($hasProjection -and $projDates.Count -gt 0) { # Kasutage viimast prognoositud kuupäeva (kui kõiki seadmeid värskendatakse) $lastProjDateStr = $projDates[-1] -replace "'", "" $stProjectedDate = ([kuupäevaaeg]::P arse($lastProjDateStr)). ToString("MMM dd, yyyy") $stCalendarDays = ([datetime]::P arse($lastProjDateStr) - (Get-Date)). Päevad $stWorkingDays = 0 $d = Toomiskuupäev for ($i = 0; $i -lt $stCalendarDays; $i++) { $d = $d.AddDays(1) if ($d.DayOfWeek -ne 'Laupäev' -and $d.DayOfWeek -ne 'pühapäev') { $stWorkingDays++ } } } elseif ($stDevicesPerDay -gt 0 -ja $stNotUptodate -gt 0) { # Fallback: lineaarne projektsioon, kui eksponentandmeid pole saadaval $daysNeeded = [matemaatika]::Ceiling($stNotUptodate / $stDevicesPerDay) $stProjectedDate = (toomiskuupäev). AddDays($daysNeeded). ToString("MMM dd, yyyy") $stWorkingDays = 0; $stCalendarDays = $daysNeeded $d = Toomiskuupäev for ($i = 0; $i -lt $daysNeeded; $i++) { $d = $d.AddDays(1) if ($d.DayOfWeek -ne 'Laupäev' -and $d.DayOfWeek -ne 'pühapäev') { $stWorkingDays++ } } } # Järgu kiiruse HTML $velocityHtml = kui ($stDevicesPerDay -gt 0) { "<div><strong>🚀 Seadmed/päev:</strong> $($stDevicesPerDay.ToString('N0')) (allikas: $stVelocitySource)</div>" + "<div><strong>📅 Kavandatud lõpuleviimine:</strong> $stProjectedDate" + $(if ($stProjectedDate -and [datetime]::P arse($stProjectedDate) -gt $stDeadline) { " <span style='color:#dc3545; font-weight:bold'>⚠ PAST DEADLINE</span>" } else { " <span style='color:#28a745'>✓ Before deadline</span>" }) + "</div>" + "<jagage><tugevaid>🕐 Tööpäevad:</strong> $stWorkingDays | <tugev>Calendar päeva:</strong> $stCalendarDays</div>" + "<div style='font-size:.8em; color:#888'>Tähtaeg: 24. juuni 2026 (KEK-serdi aegumine) | Jäänud päevad: $stDaysToDeadline</div>" } else { "<div style='padding:8px; taust:#fff3cd; piir-raadius:4px; border-left:3px solid #ffc107'>" + "<tugev>📅 Kavandatud lõpuleviimine:</strong> Kiiruse arvutamiseks pole piisavalt andmeid. " + "Käivitage koondamine vähemalt kaks korda andmete muudatustega, et määrata määr.<br/>" + "<tugev>tähtaeg:</strong> 24. juuni 2026 (KEK-i serdi aegumine) | <tugevad>allesjäänud päevad:</strong> $stDaysToDeadline</div>" } Serdi aegumise loendus $certToday = Toomiskuupäev $certKekExpiry = [kuupäev ja kellaaeg]"2026-06-24" $certUefiExpiry = [kuupäev ja kellaaeg]"2026-06-27" $certPcaExpiry = [kuupäev ja kellaaeg]"2026-10-19" $daysToKek = [matemaatika]::Max(0; ($certKekExpiry - $certToday). Päeva) $daysToUefi = [matemaatika]::Max(0; ($certUefiExpiry - $certToday). Päeva) $daysToPca = [matemaatika]:Max(0; ($certPcaExpiry - $certToday). Päeva) $certUrgency = if ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # Abiline: kirjete lugemine JSON-ist, salve kokkuvõte + esimesed N-seadme read $maxInlineRows = 200 funktsioon Build-InlineTable { param([string]$JsonPath, [int]$MaxRows = 200, [string]$CsvFileName = "") $bucketSummary = "" $deviceRows = "" $totalCount = 0 if (testtee $JsonPath) { proovige { $data = Get-Content $JsonPath -Toor | ConvertFrom-Json $totalCount = $data. Loendus # BUCKET SUMMARY: Group by BucketId, show counts per bucket with Updated from global bucket stats kui ($totalCount -gt 0) { $buckets = $data | Group-Object vahemiku ID | Sort-Object arv –laskuv järjestus $bucketSummary = "><2 h3 style='font-size:.95em; värv:#333; margin:10px 0 5px'><3 By Hardware Bucket ($($buckets. Count) buckets)><4 /h3>" $bucketSummary += "><6 div style='max-height:300px; ületäitumine:automaatne; margin-bottom:15px'><table><thead><tr><th><5 BucketID><6 /th><th style='text-align:right'>Total</th><th style='text-align:right; värv:#28a745'>värskendatud</th><th style='text-align:right; color:#dc3545'>not updated</th><th><1 Manufacturer><2 /th></tr></thead><tbody>" foreach ($b $buckets) { $bid = kui ($b.Name) { $b.Name } else { "(empty)" } $mfr = ($b.rühm | Select-Object -Esimene 1). WMI_Manufacturer # Saate värskendatud arvu globaalsetest salve statistikatest (kõik selle salve seadmed kogu andmekomplekti ulatuses) $lookupKey = $bid $globalBucket = if ($stAllBuckets.ContainsKey($lookupKey)) { $stAllBuckets[$lookupKey] } else { $null } $bUpdatedGlobal = kui ($globalBucket) { $globalBucket.Updated } else { 0 } $bTotalGlobal = kui ($globalBucket) { $globalBucket.Count } else { $b.Count } $bNotUpdatedGlobal = $bTotalGlobal - $bUpdatedGlobal $bucketSummary += "<tr><td style='font-size:.8em'>$bid><4 /td><td style='text-align:right; font-weight:bold'>$bTotalGlobal><8 /td><td style='text-align:right; värv:#28a745; font-weight:bold'>$bUpdatedGlobal><2 /td><td style='text-align:right; värv:#dc3545; font-weight:bold'>$bNotUpdatedGlobal><6 /td><td><9 $mfr</td></tr>'n" } $bucketSummary += "</tbody></table></div>" } # DEVICE DETAIL: First N rows as flat list $slice = $data | Select-Object – esimene $MaxRows foreach ($d in $slice) { $conf = $d.ConfidenceLevel $confBadge = if ($conf -match "High") { '<span class="badge-success">High Conf><2 /span>' } elseif ($conf -match "Not Sup") { '<span class="märgimärgioht">pole toetatud><6 /span>' } elseif ($conf -match "Under") { '<span class="badge-info">Under Obs><0 /span>' } elseif ($conf -match "Peatatud") { '<span class="märgimärgi hoiatus">peatatud><4 /span>" } else { '<span class="badge-warning">Action Req><8 /span>' } $statusBadge = if ($d.IsUpdated) { '><00 span class="badge-success"><01 Updated</span>' } elseif ($d.UEFICA2023Error) { '><04 span class="märgimärgi oht"><05 tõrge</span>" } else { '><08 span class="märgimärgi hoiatus"><09 ootel><0 /span>" } $deviceRows += "><12 tr><td><5 $($d.HostName)><16 /td><td><9 $($d.WMI_Manufacturer))><20 /td><td><3 $($d.WMI_Model)><24 /td><td><7 $confBadge><8 /td><td><1 $statusBadge><2></td><5 $(if($d.UEFICA2023Error){$d.UEFICA2023Error}else{'-'})><36 /td><td style='font-size:.75em'><39 $($d.BucketId)><40 /td></tr><3 'n" } } püüdmine { } } if ($totalCount -eq 0) { return "><44 div style='padding:20px; värv:#888; font-style:italic'><45 No devices in this category.><46 /div>" } $showing = [matemaatika]:Min($MaxRows; $totalCount) $header = "><48 div style='margin:5px 0; font-size:.85em; color:#666'><49 Kokku: $($totalCount.ToString("N0")) seadmed" kui ($CsvFileName) { $header += " | ><50 href='$CsvFileName' style='color:#1a237e; font-weight:bold'>📄 Laadige alla Täielik CSV Exceli><3 /a>" } $header += "><55 /div>" $deviceHeader = "><57 h3 style='font-size:.95em; värv:#333; margin:10px 0 5px'><58 Device Details (showing first $showing)><59 /h3>" $deviceTable = "><61 div style='max-height:500px; overflow-y:auto'><table><thead><tr><th><0 HostName><1 /th><><4 Tootja><5 /th><><8 mudeli><9 /th><><2><><2><6><3><><Olek><7 /th><><0 tõrge><1 /th><><4 bucketId><5 /th></tr></thead><tbody><2 $deviceRows><3 /tbody></table></div>" return "$header$bucketSummary$deviceHeader$deviceTable" } # Koostage tekstisisesed tabelid juba kettal olevate JSON-failide põhjal, linkides CSV-dega $tblErrors = Build-InlineTable -JsonPath (join-path $dataDir "errors.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_errors_$timestamp.csv" $tblKI = Build-InlineTable -JsonPath (join-path $dataDir "known_issues.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_known_issues_$timestamp.csv" $tblKEK = Build-InlineTable -JsonPath (join-path $dataDir "missing_kek.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_missing_kek_$timestamp.csv" $tblNotUpd = Build-InlineTable -JsonPath (join-path $dataDir "not_updated.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_not_updated_$timestamp.csv" $tblTaskDis = Build-InlineTable -JsonPath (join-path $dataDir "task_disabled.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_task_disabled_$timestamp.csv" $tblTemp = Build-InlineTable -JsonPath (join-path $dataDir "temp_failures.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_temp_failures_$timestamp.csv" $tblPerm = Build-InlineTable -JsonPath (join-path $dataDir "perm_failures.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_perm_failures_$timestamp.csv" $tblUpdated = Build-InlineTable -JsonPath (join-path $dataDir "updated_devices.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_updated_devices_$timestamp.csv" $tblActionReq = Build-InlineTable -JsonPath (join-path $dataDir "action_required.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_action_required_$timestamp.csv" $tblUnderObs = Build-InlineTable -JsonPath (join-path $dataDir "under_observation.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_under_observation_$timestamp.csv" $tblNeedsReboot = Build-InlineTable -JsonPath (join-path $dataDir "needs_reboot.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_needs_reboot_$timestamp.csv" $tblSBOff = Build-InlineTable -JsonPath (join-path $dataDir "secureboot_off.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_secureboot_off_$timestamp.csv" $tblRolloutIP = Build-InlineTable -JsonPath (join-path $dataDir "rollout_inprogress.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_rollout_inprogress_$timestamp.csv" # Värskendamise ootel kohandatud tabel – sisaldab veerge UEFICA2023Status ja UEFICA2023Error $tblUpdatePending = "" $upJsonPath = Join-Path $dataDir "update_pending.json" if (testtee $upJsonPath) { proovige { $upData = Get-Content $upJsonPath -Toor | ConvertFrom-Json $upCount = $upData.Count kui ($upCount -gt 0) { $upHeader = "<div style='margin:5px 0; font-size:.85em; color:#666'>Kokku: $($upCount.ToString("N0")) seadmed | <href='SecureBoot_update_pending_$timestamp.csv' style='color:#1a237e; font-weight:bold'>📄 Excel><4 /a></div> täieliku CSV allalaadimine" $upRows = "" $upSlice = $upData | Select-Object – esimene $maxInlineRows foreach ($d in $upSlice) { $uefiSt = if ($d.UEFICA2023Status) { $d.UEFICA2023Status } else { '<span style="color:#999">null><0 /span>' } $uefiErr = if ($d.UEFICA2023Error) { "<span style='color:#dc3545'>$($d.UEFICA2023Error)</span>" } else { '-' } $policyVal = if ($d.AvailableUpdatesPolicy) { $d.AvailableUpdatesPolicy } else { '-' } $wincsVal = if ($d.WinCSKeyApplied) { '<span class="märgi märk-edu">Jah><8 /span>" } else { '-' } $upRows += "<tr><td><3 $($d.HostName)</td><td><7 $($d.WMI_Manufacturer)</td><td><1 $($d.WMI_Model)</td><td><5 $uefiSt><6 /td><td><9 $uefiErr><50 /td><td><53 $policyVal><54 /td><td><57 $wincsVal><58 /td><td style='font-size:.75em'>$($d.BucketId)</td></tr><65 'n" } $upShowing = [matemaatika]:Min($maxInlineRows; $upCount) $upDevHeader = "<h3 style='font-size:.95em; värv:#333; margin:10px 0 5px'>Device Details (showing first $upShowing)</h3>" $upTable = "<div style='max-height:500px; overflow-y:auto'><tabel><thead><tr><th><9 HostName><0 /th><th><3 Tootja><4 /th><th><7 mudel><8 /th><th><1 UEFICA2023Status><2 /th><th><5 UEFICA2023Tõrge><6 /th><><9 poliitika</th><WinCS-i võti></th><>BucketId</th></tr></thead><tbody><5 $upRows><6 /tbody></table></div>" $tblUpdatePending = "$upHeader$upDevHeader$upTable" } else { $tblUpdatePending = "<div style='padding:20px; värv:#888; font-style:italic'>No devices in this category.</div>" } } püüdmine { $tblUpdatePending = "<div style='padding:20px; värv:#888; font-style:italic'>No devices in this category.</div>" } } else { $tblUpdatePending = "<div style='padding:20px; värv:#888; font-style:italic'>No devices in this category.</div>" } Serdi aegumise loendus $certToday = toomiskuupäev $certKekExpiry = [kuupäev ja kellaaeg]"2026-06-24" $certUefiExpiry = [kuupäev ja kellaaeg]"2026-06-27" $certPcaExpiry = [datetime]"2026-10-19" $daysToKek = [matemaatika]::Max(0; ($certKekExpiry - $certToday). Päeva) $daysToUefi = [matemaatika]::Max(0; ($certUefiExpiry - $certToday). Päeva) $daysToPca = [matemaatika]:Max(0; ($certPcaExpiry - $certToday). Päeva) $certUrgency = if ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # Järgu tootja diagrammiandmed teksti sees (esikümme seadmete arvu järgi) $mfrSorted = $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } – laskuv järjestus | Select-Object – esimene 10 $mfrChartTitle = if ($stMfrCounts.Count -le 10) { "Tootjalt" } veel { "Kümme parimat tootjat" } $mfrLabels = ($mfrSorted | ForEach-Object { "$($_. Key)'" }) -join "," $mfrUpdated = ($mfrSorted | ForEach-Object { $_. Value.Updated }) - join "," $mfrUpdatePending = ($mfrSorted | ForEach-Object { $_. Value.UpdatePending }) - liitu "," $mfrHighConf = ($mfrSorted | ForEach-Object { $_. Value.HighConf }) – ühendage "," $mfrUnderObs = ($mfrSorted | ForEach-Object { $_. Value.UnderObs }) - liitmine "," $mfrActionReq = ($mfrSorted | ForEach-Object { $_. Value.ActionReq }) - liitmine "," $mfrTempPaused = ($mfrSorted | ForEach-Object { $_. Value.TempPaused }) – liitmine "," $mfrNotSupported = ($mfrSorted | ForEach-Object { $_. Value.NotSupported }) -join "," $mfrSBOff = ($mfrSorted | ForEach-Object { $_. Value.SBOff }) - liitmine "," $mfrWithErrors = ($mfrSorted | ForEach-Object { $_. Value.WithErrors }) - liitmine "," # Järgu tootja tabel $mfrTableRows = "" $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } – laskuv järjestus | ForEach-Object { $mfrTableRows += "<tr><td><7 $($_. Võti)</td><td>$($_. Value.Total.ToString("N0")))</td><td>$($_. Value.Updated.ToString("N0"))</td><td>$($_. Value.HighConf.ToString("N0")))><0 /td><td>$($_. Value.ActionReq.ToString("N0"))><4 /td></tr>'n' } # HTML-i sulgemisfragmendid: osadeks murtud, et veebi CMS-i platvormid ei # tõlgendage neid tegeliku HTML-ina ja sisestage nende ümber nähtamatud Unicode'i märgid.$endScript = '</scr' + 'ipt>' $endStyle = '</sty' + 'le>' $endHead = '</he' + 'reklaami>' $endBody = '</bo' + 'dy>' $endHtml = '</ht' + 'ml>' $htmlContent = @" <! DOCTYPE HTML-> <html lang="en"> <pea> <metamärgistik="UTF-8"> <metanimi="viewport" content="width=device-width, initial-scale=1.0"> <tiitel>turvalise algkäivituse serdi oleku armatuurlaua</title> <script src="https://cdn.jsdelivr.net/npm/chart.js">$endScript <laad> *{box-sizing:border-box; veeris:0; täidis:0} body{font-family:'Segoe UI',Tahoma,sans-serif; taust:#f0f2f5; värv:#333} .header{background:linear-gradient(135deg,#1a237e,#0d47a1); värv:#fff; padding:20px 30px} .header h1{font-size:1.6em; margin-bottom:5px} .header .meta{font-size:.85em; läbipaistmatus:.9} .container{max-width:1400px; veeris:0 automaatne; padding:20px} .cards{display:grid; grid-template-columns:repeat(auto-fill;minmax(170px,1fr)); vahe:12px; veeris:20px 0} .card{background:#fff; piir-raadius:10px; padding:15px; box-shadow:0 2px 8px rgba(0,0,0,08); border-left:4px solid #ccc;transition:transform .2s} .card:hover{transform:translateY(-2px); box-shadow:0 4px 15px rgba(0,0,0,,12)} .card .value{font-size:1.8em; font-weight:700} .card .label{font-size:.8em; värv:#666; margin-top:4px} .card .pct{font-size:.75em; värv:#888} .section{background:#fff; piir-raadius:10px; padding:20px; veeris:15px 0; box-shadow:0 2px 8px rgba(0,0,0,08)} .section h2{font-size:1.2em; värv:#1a237e; veeris-all:10px; kursor:kursor; kasutaja valimine:none} .section h2:hover{text-decoration:underline} .section-body{display:none} .section-body.open{display:block} .charts{display:grid; grid-template-columns:1fr 1fr; vahe:20px; veeris:20px 0} .chart-box{background:#fff; piir-raadius:10px; padding:20px; box-shadow:0 2px 8px rgba(0,0,0,08)} tabel{width:100%; border-collapse:collapse; font-size:.85em} th{background:#e8eaf6; padding:8px 10px; tekst-align:left; asend:kleepuv; top:0; z-indeks:1} td{padding:6px 10px; border-bottom:1px solid #eee} tr:hõljuta{background:#f5f5f5} .badge{display:inline-block; padding:2px 8px;border-radius:10px; font-size:.75em; font-weight:700} .badge-success{background:#d4edda; värv:#155724} .badge-danger{background:#f8d7da; värv:#721c24} .badge-warning{background:#fff3cd; värv:#856404} .badge-info{background:#d1ecf1; värv:#0c5460} .top-link{float:right; font-size:.8em; värv:#1a237e; text-decoration:none} .footer{text-align:center; padding:20px; värv:#999; font-size:.8em} a{color:#1a237e}$endStyle $endHead <sisu> <div class="header"> <h1>turvalise algkäivituse serdi oleku armatuurlaua</h1> <div class="meta">Loodud: $($stats. ReportGeneratedAt) | Seadmeid kokku: $($c.Total.ToString("N0")) | Kordumatud salved: $($stAllBuckets.Count)</div> </div> <div class="container">
KPI-kaartide <!-- – klõpsatav, jaotistele lingitud --> <div class="cards"> <class="card" href="#s-nu" onclick="openSection('d-nu')" style="border-left-color:#dc3545; text-decoration:none; position:relative"><div style="position:absolute; top:8px; parem:8px; taust:#dc3545; värv:#fff; täidis:1px 6px; piir-raadius:8px; font-size:.65em; font-weight:700">PRIMARY</div><div class="value" style="color:#dc3545">$($stNotUptodate.ToString("N0")))</div><div class="label">NOT UPDATED><6 /div><div class="pct">$($stats). PercentNotUptodate)% – VAJAB TOIMINGUT><0 /div></a><3 <class="card" href="#s-upd" onclick="openSection('d-upd')" style="border-left-color:#28a745; text-decoration:none; position:relative"><div style="position:absolute; top:8px; parem:8px; taust:#28a745; värv:#fff; täidis:1px 6px; piir-raadius:8px; font-size:.65em; font-weight:700">PRIMARY><8 /div><div class="value" style="color:#28a745">$($c.Updated.ToString("N0"))</div><div class="label">Värskendatud><6 /div><div class="pct">$($stats. PercentCertUpdated)%</div></a><3 <class="card" href="#s-sboff" onclick="openSection('d-sboff')" style="border-left-color:#6c757d; text-decoration:none; position:relative"><div style="position:absolute; top:8px; parem:8px; taust:#6c757d; värv:#fff; täidis:1px 6px; piir-raadius:8px; font-size:.65em; font-weight:700">PRIMARY><8 /div><div class="value"><1 $($c.SBOff.ToString("N0"))><2 /div><div class="label"><5 SecureBoot OFF</div><div class="pct"><9 $(if($c.Total -gt 0){[matemaatika]::Round(($c.SBOff/$c.Total)*100,1)}else{0})% – ulatus on><0 /div></a><3 <class="card" href="#s-nrb" onclick="openSection('d-nrb')" style="border-left-color:#ffc107; text-decoration:none"><div class="value" style="color:#ffc107">$($c.NeedsReboot.ToString("N0"))</div><div class="label">Needs Reboot><2 /div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.NeedsReboot/$c.Total)*100,1)}else{0})% – oodatakse taaskäivitamist><6 /div></a><9 <class="card" href="#s-upd-pend" onclick="openSection('d-upd-pend')" style="border-left-color:#6f42c1; text-decoration:none"><div class="value" style="color:#6f42c1">$($c.UpdatePending.ToString("N0")))</div><div class="label">Update Pending</div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round((($c.UpdatePending/$c.Total)*100,1)}else{0})% – rakendatud poliitika/WinCS värskendust ootamas><2 /div></a><5 <class="card" href="#s-rip" onclick="openSection('d-rip')" style="border-left-color:#17a2b8; text-decoration:none"><div class="value">$($c.RolloutInProgress)</div><div class="label">Rollout In Progress><4 /div><div class="pct">>Rollout In Progress $(if($c.Total -gt 0){[matemaatika]::Round(($c.RolloutInProgress/$c.Total)*100,1)}else{0})%</div></a><11 <class="card" href="#s-nu" onclick="openSection('d-nu')" style="border-left-color:#28a745; text-decoration:none"><div class="value" style="color:#28a745">$($c.HighConf.ToString("N0")))</div><div class="label">High Confidence><20 /div><div class="pct">$($stats. PercentHighConfidence)% –><24 /div></a><27 <class="card" href="#s-uo" onclick="openSection('d-uo')" style="border-left-color:#17a2b8; text-decoration:none"><div class="value" style="color:#ffc107"><1 $($c.UnderObs.ToString("N0")))><2 /div><div class="label"><5 Jaotises Vaatlus><36 /div><div class="pct"><9 $(if($c.Total -gt 0){[matemaatika]::Round(($c.UnderObs/$c.Total)*100,1)}else{0})%</div></a><3 <class="card" href="#s-ar" onclick="openSection('d-ar')" style="border-left-color:#fd7e14; text-decoration:none"><div class="value" style="color:#fd7e14">$($c.ActionReq.ToString("N0")))</div><div class="label">Action Nõutav><2 /div><div class="pct">$($stats. PercentActionRequired)% – peab testima><6 /div></a><9 <class="card" href="#s-err" onclick="openSection('d-err')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($stAtRisk.ToString("N0")))</div><div class="label">At Risk><68 /div><div class="pct">$($stats). PercentAtRisk)% – sarnane nurjunud><2 /div></a><5 <class="card" href="#s-td" onclick="openSection('d-td')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($c.TaskDisabled.ToString("N0"))</div><div class="label">Task Disabled><4 /div><div class ="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.TaskDisabled/$c.Total)*100,1)}else{0})% – blokeeritud><8 /div></a><91 <class="card" href="#s-tf" onclick="openSection('d-tf')" style="border-left-color:#fd7e14; text-decoration:none"><div class="value" style="color:#fd7e14">$($c.TempPaused.ToString("N0")))</div><div class="label">Temp. Peatatud</div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.TempPaused/$c.Total)*100,1)}else{0})%</div></a> <class="card" href="#s-ki" onclick="openSection('d-ki')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($c.WithKnownIssues.ToString("N0"))</div><div class="label">teadaolevad probleemid><6 /div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.WithKnownIssues/$c.Total)*100,1)}else{0})%</div></a><3 <class="card" href="#s-kek" onclick="openSection('d-kek')" style="border-left-color:#fd7e14; text-decoration:none"><div class="value" style="color:#fd7e14">$($c.WithMissingKEK.ToString("N0"))</div><div class="label">Puuduv KEK</div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.WithMissingKEK/$c.Total)*100,1)}else{0})%</div></a> <class="card" href="#s-err" onclick="openSection('d-err')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($c.WithErrors.ToString("N0")))</div><div class="label">With Errors</div><div class="pct"><1 $($stats. PercentWithErrors)% – UEFI tõrked</div></a> ><6 class="card" href="#s-tf" onclick="openSection('d-tf')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545"><9 $($c.TempFailures.ToString("N0"))</div><div class="label">Temp. Tõrked</div><div class="pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.TempFailures/$c.Total)*100,1)}else{0})%</div></a> <class="card" href="#s-pf" onclick="openSection('d-pf')" style="border-left-color:#721c24; text-decoration:none"><div class="value" style="color:#721c24">$($c.PermFailures.ToString("N0"))</div><div class="label">Not Supported><6 /div><div class=" pct">$(if($c.Total -gt 0){[matemaatika]::Round(($c.PermFailures/$c.Total)*100,1)}else{0})%</div></a><3 </div>
<!-- deployment Velocity & Cert Expiry --> <div id="s-velocity" style="display:grid; grid-template-columns:1fr 1fr; vahe:20px; veeris:15px0"> <div class="section" style="margin:0"> <h2>📅 Deployment Velocity</h2> ><2 div class="section-body open"><3 ><4 div style="font-size:2.5em; font-weight:700; color:#28a745"><5 $($c.Updated.ToString("N0")))><6 /div> ><8 div style="color:#666"><9 seadmete värskendatud $($c.Total.ToString("N0")))</div> <div style="margin:10px 0; taust:#e8eaf6; kõrgus:20px; piir-raadius:10px; ületäitumine:peidetud"><div style="background:#28a745; kõrgus:100%; laius:$($stats. PercentCertUpdated)%; border-radius:10px"></div></div> <div style="font-size:.8em; color:#888">$($stats. PercentCertUpdated)%</div> <div style="margin-top:10px; padding:10px; taust:#f8f9fa; piir-raadius:8px; font-size:.85em"> <div><strong>Jäänud:</strong> $($stNotUptodate.ToString("N0")) seadmed vajavad meetmeid</div> <div><strong>Blocking:</strong> $($c.WithErrors + $c.PermFailures + $c.TaskDisabledNotUpdated) seadmed (tõrked + püsiv + ülesanne keelatud)</div> <juurutamiseks jagada><tugevaid>turvaliselt:</strong> $($stSafeList.ToString("N0")) seadmed (sama ämber kui õnnestunud)</div> $velocityHtml </div> </div> </div> <div class="section" style="margin:0; border-left:4px solid #dc3545"> <h2 style="color:#dc3545">⚠ serdi aegumise loendus</h2> <div class="section-body open"> <div style="display:flex; vahe:15px; margin-top:10px"> <div style="text-align:center; padding:15px; piir-raadius:8px; min-width:120px; taust:lineaarastmik(135eg;#fff5f5;#ffe0e0); ääris:2px ühtlane #dc3545; flex:1"> <div style="font-size:.65em; värv:#721c24; text-transform:uppercase; font-weight:bold">⚠ FIRST TO EXPIRE</div> ><4 div style="font-size:.85em; font-weight:bold; värv:#dc3545; margin:3px 0"><5 KEK CA 2011</div> ><8 div id="daysKek" style="font-size:2.5em; font-weight:700; värv:#dc3545; rea kõrgus:1"><9 $daysToKek><0 /div><1 <div style="font-size:.8em; color:#721c24">päeva (24. juuni 2026)</div><5 </div><7 <div style="text-align:center; padding:15px; piir-raadius:8px; min-width:120px; taust:lineaarastmik(135eg;#fffef5;#fff3cd); ääris:2px ühtlane #ffc107; flex:1"> ><00 div style="font-size:.65em; värv:#856404; text-transform:uppercase; font-weight:bold"><01 UEFI CA 2011</div> ><04 div id="daysUefi" style="font-size:2.2em; font-weight:700; värv:#856404; rea kõrgus:1; veeris:5px 0"><05 $daysToUefi</div> ><08 div style="font-size:.8em; color:#856404"><09 päevad (27. juuni 2026)><10 /div> ><12 /div> ><14 div style="text-align:center; padding:15px; piir-raadius:8px; min-width:120px; taust:lineaarastmik(135eg;#f0f8ff;#d4edff); ääris:2px ühtlane #0078d4; flex:1"><15 ><16 div style="font-size:.65em; värv:#0078d4; text-transform:uppercase; font-weight:bold"><17 Windows PCA</div> ><20 div id="daysPca" style="font-size:2.2em; font-weight:700; värv:#0078d4; rea kõrgus:1; veeris:5px 0"><21 $daysToPca><2 /div><3 ><24 div style="font-size:.8em; color:#0078d4"><25 päevad (19. okt 2026)><26 /div><7 ><28 /div><9 ><30 /div><1 ><32 div style="margin-top:15px; padding:10px; taust:#f8d7da; piir-raadius:8px; font-size:.85em; border-left:4px solid #dc3545"><33 ><34 tugev>⚠ CRITICAL:><37 /strong> Enne serdi aegumist tuleb kõik seadmed värskendada. Seadmed, mida pole tähtajaks värskendatud, ei saa pärast aegumist rakendada käivitushalduri ja secure Booti edaspidisi turbevärskendusi.</div> </div> </div> </div>
<!-- diagrammid --> <div class="charts"> <div class="chart-box"><h3>Deployment Status</h3><canvas id="deployChart" height="200"></canvas></div><5 <div class="chart-box"><h3><9 $mfrChartTitle</h3><canvas id="mfrChart" height="200"></canvas></div> </div>
$(if ($historyData.Count -ge 1) { "<!-- ajalooline trendidiagramm --> <div class='section'> <h2 onclick='"toggle('d-trend')'">📈 Aja jooksul edenemise värskendamine <class='top-link' href='#'>↑</a></h2> <div id='d-trend' class='section-body open'> <lõuend id='trendChart' height='120'></canvas> <div style='font-size:.75em; värv:#888; margin-top:5px'>Solid lines = actual data$(if ($historyData.Count -ge 2) { " | Kriipsjoon = projitseeritud (eksponentsiaalne kahekordistamine: 2→4→8→16... seadet laine kohta)" } else { " | Käivitage koondfunktsioon homme uuesti, et näha trendijooni ja prognoosi" })</div> </div> </div>" })
CSV-<!-- allalaaditavad failid --> <div class="section"> <h2 onclick="toggle('dl-csv')">📥 Täisandmete allalaadimine (Exceli CSV) <class="top-link" href="#">populaarsemad</a></h2> <div id="dl-csv" class="section-body open" style="display:flex; flex-wrap:wrap; vahe:5px"> <a href="SecureBoot_not_updated_$timestamp.csv" style="display:inline-block; taust:#dc3545; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">Not Updated ($($stNotUptodate.ToString("N0")))</a> <a href="SecureBoot_errors_$timestamp.csv" style="display:inline-block; taust:#dc3545; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">tõrked ($($c.WithErrors.ToString("N0"))))</a><2 <a href="SecureBoot_action_required_$timestamp.csv" style="display:inline-block; taust:#fd7e14; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">Nõutav toiming ($($c.ActionReq.ToString("N0"))))</a><6 <a href="SecureBoot_known_issues_$timestamp.csv" style="display:inline-block; taust:#dc3545; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">teadaolevad probleemid ($($c.WithknownIssues.ToString("N0"))))</a> <a href="SecureBoot_task_disabled_$timestamp.csv" style="display:inline-block; taust:#dc3545; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">Task Disabled ($($c.TaskDisabled.ToString("N0"))))</a> <a href="SecureBoot_updated_devices_$timestamp.csv" style="display:inline-block; taust:#28a745; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">Updated ($($c.Updated.ToString("N0")))</a> <a href="SecureBoot_Summary_$timestamp.csv" style="display:inline-block; taust:#6c757d; värv:#fff; padding:6px 14px; piir-raadius:5px; text-decoration:none; font-size:.8em">Summary</a> <div style="width:100%; font-size:.75em; värv:#888; margin-top:5px">CSV-failid avatakse Excelis. Saadaval veebiserveris majutamisel.</div> </div> </div>
<!-- tootja jaotus --> <div class="section"> <h2 onclick="toggle('mfr')">Tootja <class="top-link" href="#">top</a></h2><1 <div id="mfr" class="section-body open"> <tabel><ad><tr><><1 Tootja><2 /th><nda><5><6 kokku><><9 värskendatud><9 värskendatud><0 /th><><3 kõrge usaldusväärsusega><4 /th><toimingu><7 nõutav><8 /th></tr></thead><3 <><5 $mfrTableRows><6 /tbody></table><9 </div><1 </div>
<!-- seadme jaotised (esimene 200 tekstisisene + CSV allalaadimine) --> <div class="section" id="s-err"> <h2 onclick="toggle('d-err')">🔴 Tõrgetega seadmed ($($c.WithErrors.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-err" class="section-body">$tblErrors</div> </div> <div class="section" id="s-ki"> <h2 onclick="toggle('d-ki')" style="color:#dc3545">🔴 Teadaolevad probleemid ($($c.WithknownIssues.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-ki" class="section-body">$tblKI</div> </div> <div class="section" id="s-kek"> <h2 onclick="toggle('d-kek')">🟠 KEK puudub – sündmus 1803 ($($c.WithMissingKEK.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> >↑ 0 div id="d-kek" class="section-body">↑ 1 $tblKEK</div> >↑ 4 /div> >↑ 6 div class="section" id="s-ar">↑ 7 >↑ 8 h2 onclick="toggle('d-ar')" style="color:#fd7e14">🟠 Toiming on nõutav ($($c.ActionReq.ToString("N0"))) <class="top-link" href="#">↑ esimesed><4 /a></h2><7 <div id="d-ar" class="section-body">$tblActionReq</div> </div> <div class="section" id="s-uo"> <h2 onclick="toggle('d-uo')" style="color:#17a2b8">🔵 Jaotises Vaatlus ($($c.UnderObs.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-uo" class="section-body">$tblUnderObs</div> </div> <div class="section" id="s-nu"> <h2 onclick="toggle('d-nu')" style="color:#dc3545">🔴 Värskendamata ($($stNotUptodate.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-nu" class="section-body">$tblNotUpd</div> </div> >↑ 0 div class="section" id="s-td">↑ 1 >↑ 2 h2 onclick="toggle('d-td')" style="color:#dc3545">🔴 Ülesanne on keelatud ($($c.TaskDisabled.ToString("N0"))) >↑ 5 class="top-link" href="#">↑ top</a></h2><1 <div id="d-td" class="section-body">$tblTaskDis><4 /div><5 </div><7 <div class="section" id="s-tf"> <h2 onclick="toggle('d-tf')" style="color:#dc3545">🔴 Ajutised tõrked ($($c.TempFailures.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-tf" class="section-body">$tblTemp</div> </div> <div class="section" id="s-pf"> <h2 onclick="toggle('d-pf')" style="color:#721c24">🔴 Püsivad tõrked / pole toetatud ($($c.PermFailures.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-pf" class="section-body">$tblPerm</div> </div> <div class="section" id="s-upd-pend"> <h2 onclick="toggle('d-upd-pend')" style="color:#6f42c1">⏳ värskenduse ootel ($($c.UpdatePending.ToString("N0"))) – poliitika/WinCS on rakendatud Värskendamise ootel <klassi="top-link" href="#">↑ top</a></h2> <div id="d-upd-pend" class="section-body"><p style="color:#666; margin-bottom:10px">Devices where AvailableUpdatesPolicy või WinCS key is applied but UEFICA2023Status is still NotStarted, InProgress või null.</p>$tblUpdatePending</div> </div> <div class="section" id="s-rip"> <h2 onclick="toggle('d-rip')" style="color:#17a2b8">🔵 Avaldamine on pooleli ($($c.RolloutInProgress.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-rip" class="section-body">$tblRolloutIP</div> </div> <div class="section" id="s-sboff"> <h2 onclick="toggle('d-sboff')" style="color:#6c757d">⚫ SecureBoot OFF ($($c.SBOff.ToString("N0"))) – ulatusest väljas <class="top-link" href="#">↑ Top</a></h2> <div id="d-sboff" class="section-body">$tblSBOff</div> </div> <div class="section" id="s-upd"> <h2 onclick="toggle('d-upd')" style="color:#28a745">🟢 Värskendatud seadmed ($($c.Updated.ToString("N0"))) <class="top-link" href="#">↑ top</a></h2> <div id="d-upd" class="section-body">$tblUpdated</div> </div> <div class="section" id="s-nrb"> <h2 onclick="toggle('d-nrb')" style="color:#ffc107">🔄 Värskendatud – vajab taaskäivitamist ($($c.NeedsReboot.ToString("N0"))) <class="top-link" href="#">↑ Top</a></h2> <div id="d-nrb" class="section-body">$tblNeedsReboot</div> </div>
<div class="footer">Secure Boot Certificate Rollout Dashboard | Loodud $($stats. ReportGeneratedAt) | Voogesituse voogesitus | Tippmälu: ${stPeakMemMB} MB</div> </div><!-- /container -->
skripti>< funktsiooni tumbler(id){var e=document.getElementById(id); e.classList.toggle('open')} function openSection(id){var e=document.getElementById(id); if(e&&!e.classList.contains('open')){e.classList.add('open')}} new Chart(document.getElementById('deployChart'),{type:'doughnut',data:{labels:['Updated','Update Pending','High Confidence','Under Observation','Action Required','Temp. Peatatud,'Pole toetatud','SecureBoot OFF','With errors'],datasets:[{data:[$($c.Updated),$($c.UpdatePending),$($c.HighConf ),$($c.UnderObs),$($c.ActionReq),$($c.TempPaused),$($c.NotSupported),$($c.SBOff),$($c.SBOff),$( ($c.WithErrors)],backgroundColor:['#28a745','#6f42c1','#20c997','#17a2b8','#fd7e14','#6c757d','#721c24 ','#adb5bd','#dc3545']}]},suvandid:{responsive:true,plugins:{legend:{position:'right',labels:{font:{size:11}}}}}}); new Chart(document.getElementById('mfrChart'),{type:'bar',data:{labels:[$mfrLabels],datasets:[{label:'Updated',data:[$mfrUpdated],backgroundColor:'#28a745'},{label:'Update Pending',data:[$mfrUpdatePending],backgroundColor:'#6f42c1'},{label:'High Confidence',data:[$mfrHighConf],backgroundColor:'#20c997'},{label:'Under Observation',data:[$mfrUnderObs],backgroundColor:'#17a2b8'},{label:'Action Required',data:[$mfrActionReq],backgroundColor:'#fd7e14'},{ silt:'Temp. Peatatud',andmed:[$mfrTempPaused],backgroundColor:'#6c757d'},{label:'Not Supported',data:[$mfrNotSupported],backgroundColor:'#721c24'},{label:'SecureBoot OFF',data:[$mfrSBOff],backgroundColor:'#adb5bd'},{label :'Tõrgetega',andmed:[$mfrWithErrors],backgroundColor:'#dc3545'}]},suvandid:{responsive:true,scales:{x:{stacked:true},y:{stacked:true}},plugins:{legend:{position:'top'}}}); Ajalooline trendidiagramm if (document.getElementById('trendChart')) { var allLabels = [$allChartLabels]; var actualUpdated = [$trendUpdated]; var actualNotUpdated = [$trendNotUpdated]; var actualTotal = [$trendTotal]; var projData = [$projDataJS]; var projNotUpdData = [$projNotUpdJS]; var histLen = actualUpdated.length; var projLen = projData.length; var paddedUpdated = actualUpdated.concat(Array(projLen).fill(null)); var paddedNotUpdated = actualNotUpdated.concat(Array(projLen).fill(null)); var paddedTotal = actualTotal.concat(Array(projLen).fill(null)); var projLine = Array(histLen).fill(null); var projNotUpdLine = Array(histLen).fill(null); if (projLen > 0) { projLine[histLen-1] = actualUpdated[histLen-1]; projLine = projLine.concat(projData); projNotUpdLine[histLen-1] = actualNotUpdated[histLen-1]; projNotUpdLine = projNotUpdLine.concat(projNotUpdData); } var datasets = [ {label:'Updated',data:paddedUpdated,borderColor:'#28a745',backgroundColor:'rgba(40\167\69\0.1)',fill:true,tension:0.3,borderWidth:2}, {label:'Not Updated',data:paddedNotUpdated,borderColor:'#dc3545',backgroundColor:'rgba(220\53\69\0.1)',fill:true,tension:0.3,borderWidth:2}, {label:'Total',data:paddedTotal,borderColor:'#6c757d',borderDash:[5,5],fill:false,tension:0,pointRadius:0,borderWidth:1} ]; if (projLen > 0) { datasets.push({label:'Projected Updated (2x doubling)',data:projLine,borderColor:'#28a745',borderDash:[8,4],borderWidth:3,fill:false,tension:0.3,pointRadius:3,pointStyle:'triangle'}); datasets.push({label:'Projected Not Updated',data:projNotUpdLine,borderColor:'#dc3545',borderDash:[8,4],borderWidth:3,fill:false,tension:0.3,pointRadius:3,pointStyle:'triangle'}); } new Chart(document.getElementById('trendChart'),{type:'line',data:{labels:allLabels,datasets:datasets},options:{responsive:true,scales:{y:{beginAtZero:true,title:{display:true,text:'Devices'}},x:{title:{display:true,text:'Date'}}},lisandmoodulid:{legend:{position:'top'},title:{display:true,tekst:'Secure Boot Update'i edenemine aja jooksul'}}}); } Dünaamiline loendus (function(){var t=new Date(),k=new Date('2026-06-24'),u=new Date('2026-06-27'),p=new Date('2026-10-19'); var dk=document.getElementById('daysKek'),du=document.getElementById('daysUefi'),dp=document.getElementById('daysPca'); if(dk)dk.textContent=Math.max(0;Matemaatika.ceil((k-t)/864e5)); if(du)du.textContent=Math.max(0;Math.ceil((u-t)/864e5)); if(dp)dp.textContent=Math.max(0;Matemaatika.ceil((p-t)/864e5)))}))();$endScript $endBody $endHtml "@ [System.IO.File]::WriteAllText($htmlPath, $htmlContent, [System.Text.UTF8Encoding]::new($false)) # Hoidke alati stabiilne "Uusim" koopia, et administraatorid ei pea ajatempleid jälgima $latestPath = Join-Path $OutputPath "SecureBoot_Dashboard_Latest.html" Copy-Item $htmlPath $latestPath – jõustamine $stTotal = $streamSw.Elapsed.TotalSeconds # Failimanifesti salvestamine astmelises režiimis (kiire muutusteta tuvastamine järgmisel käivitamisel) if ($IncrementalMode -või $StreamingMode) { $stManifestDir = Join-Path $OutputPath ".cache" kui (-not (testtee $stManifestDir)) { New-Item -ItemType'i kataloog -tee $stManifestDir -Force | Out-Null } $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" $stNewManifest = @{} Write-Host "Failimanifesti salvestamine astmelises režiimis..." – esiplaanivärvi hall foreach ($jf in $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o") Suurus = $jf. Pikkus } } Save-FileManifest – manifesti $stNewManifest – tee $stManifestPath Write-Host " $($stNewManifest.Count) failide salvestatud manifest" -ForegroundColor DarkGray } SÄILITUSPUHASTUS # # Orchestrator reusable folder (Aggregation_Current): keep only latest run (1) # Haldus käsitsi käitamine / muud kaustad: säilita viimased 7 käitust # CsV-kokkuvõtteid EI kustutata KUNAGI – need on tillukesed (~1 KB) ja on trendiajaloo varukoopia allikas $outputLeaf = Split-Path $OutputPath -leht $retentionCount = if ($outputLeaf -eq 'Aggregation_Current') { 1 } else { 7 } # Faili eesliidete puhastamine on ohutu (korrapäratud hetktõmmised) $cleanupPrefixes = @( "SecureBoot_Dashboard_", "SecureBoot_action_required_", "SecureBoot_ByManufacturer_", "SecureBoot_ErrorCodes_", "SecureBoot_errors_", "SecureBoot_known_issues_", "SecureBoot_missing_kek_", "SecureBoot_needs_reboot_", "SecureBoot_not_updated_", "SecureBoot_secureboot_off_", "SecureBoot_task_disabled_", "SecureBoot_temp_failures_", "SecureBoot_perm_failures_", "SecureBoot_under_observation_", "SecureBoot_UniqueBuckets_", "SecureBoot_update_pending_", "SecureBoot_updated_devices_", "SecureBoot_rollout_inprogress_", "SecureBoot_NotUptodate_", 'SecureBoot_Kusto_' ) # Otsi ainult puhastatavatest failidest kõik kordumatud ajatemplid $cleanableFiles = Get-ChildItem $OutputPath -Fail -EA SilentlyContinue | Where-Object { $f = $_. Nimi; ($cleanupPrefixes | Where-Object { $f.StartsWith($_) }). Loendus -gt 0 } $allTimestamps = @($cleanableFiles | ForEach-Object { kui ($_. Name -match '(\d{8}-\d{6})') { $Matches[1] } } | Sort-Object – kordumatu –laskuv järjestus) if ($allTimestamps.Count -gt $retentionCount) { $oldTimestamps = $allTimestamps | Select-Object – jätke $retentionCount vahele $removedFiles = 0; $freedBytes = 0 foreach ($oldTs in $oldTimestamps) { foreach ($prefix in $cleanupPrefixes) { $oldFiles = Get-ChildItem $OutputPath -Fail -Filter "${prefix}${oldTs}*" -EA SilentlyContinue foreach ($f in $oldFiles) { $freedBytes += $f.Length Remove-Item $f.FullName – force –EA SilentlyContinue $removedFiles++ } } } $freedMB = [matemaatika]::Round($freedBytes / 1MB, 1) Write-Host "Säilituspuhastus: eemaldatud $removedFiles failid $($oldTimestamps.Count) vanadest käitustest, vabastatud ${freedMB} MB (viimase $retentionCount + kõik Summary/NotUptodate CSVs)" -ForegroundColor DarkGray } Write-Host "'n$("=" * 60)" -Esiplaanivärvi tsüaan Write-Host "STREAMING AGGREGATION COMPLETE" -ForegroundColor Green Write-Host ("=" * 60) -Esiplaanivärvi tsüaan Write-Host " Seadmete koguarv: $($c.Total.ToString("N0"))" -ForegroundColor White Write-Host " POLE VÄRSKENDATUD: $($stNotUptodate.ToString("N0")) ($($stats. PercentNotUptodate)%)" -ForegroundColor $(if ($stNotUptodate -gt 0) { "Yellow" } else { "Green" }) Write-Host " Värskendatud: $($c.Updated.ToString("N0")) ($($stats. PercentCertUpdated)%)" – esiplaanivärvi roheline värv Write-Host " Tõrgetega: $($c.WithErrors.ToString("N0"))" -ForegroundColor $(if ($c.WithErrors -gt 0) { "Red" } else { "Green" }) Write-Host " Tippmälu: ${stPeakMemMB} MB" -ForegroundColor Cyan Write-Host " Kellaaeg: $([matemaatika]::Round($stTotal/60;1)) min" -Esiplaanivärvi valge Write-Host " Armatuurlaud: $htmlPath" –esiplaanivärv, valge return [PSCustomObject]$stats } #ENDREGION VOOGESITUSREŽIIM } else { Write-Error "Sisestusteed ei leitud: $InputPath" väljumine 1 }