Nukopijuokite ir įklijuokite šį scenarijaus pavyzdį ir modifikuokite, kiek reikia jūsų aplinkai:
<# . ANOTACIJA Sujungia saugiosios įkrovos būsenos JSON duomenis iš kelių įrenginių į suvestinės ataskaitas.
sąsa APRAŠYMAS / KONTROLĖ Nuskaito surinktus saugiosios įkrovos būsenos JSON failus ir sugeneruoja: - HTML ataskaitų sritis su diagramomis ir filtravimas - Santrauka pagal ConfidenceLevel - Unikali įrenginio talpyklos analizė testavimo strategijai Palaiko: - Kompiuterio failai: HOSTNAME_latest.json (rekomenduojama) - Vienas JSON failas Automatiškai dubliuoja "HostName", išlaikydami naujausią "CollectionTime". Pagal numatytuosius nustatymus apima tik įrenginius, kurių patikimumo lygis veiksmo req arba didelis , kad sutelktumėte dėmesį į aktyvias talpyklas. Norėdami perrašyti, naudokite -IncludeAllConfidenceLevels.
sąsa PARAMETER InputPath Kelias į JSON failą (-us): - Aplankas: nuskaito visus *_latest.json failus (arba *.json, jei nėra _latest failų) - Failas: skaito vieną JSON failą
sąsa PARAMETER OutputPath Sugeneruotų ataskaitų kelias (numatytasis: .\SecureBootReports)
sąsa PAVYZDYS # Agreguota iš kiekvieno kompiuterio failų aplanko (rekomenduojama) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # perskaito: \\contoso\SecureBootLogs$\*_latest.json
sąsa PAVYZDYS # Pasirinktinė išvesties vieta .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -OutputPath "C:\Reports\SecureBoot"
sąsa PAVYZDYS # Įtraukti tik veiksmo req ir didelį patikimumą (numatytasis veikimas) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # neapima: stebėjimas, pristabdymas, nepalaikomas
sąsa PAVYZDYS # Įtraukti visus patikimumo lygius (nepaisyti filtro) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeAllConfidenceLevels
sąsa PAVYZDYS # Pasirinktinis patikimumo lygio filtras .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeConfidenceLevels @("Action Req", "High", "Observation")
sąsa PAVYZDYS # ĮMONĖS MASTELIS: papildantysis režimas – apdorojami tik pakeisti failai (greitai sekantys paleidimai) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode # Pirmasis paleidimas: visiška apkrova ~2 val. 500 K įrenginiams # Paskesni paleidimai: sekundės, jei pakeitimų nėra, delta minučių skaičius
sąsa PAVYZDYS # Praleisti HTML, jei niekas nepasikeitė (greičiausiai stebėti) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode -SkipReportIfUnchanged # Jei po paskutinio paleidimo nepasikeitė jokie failai: ~5 sek.
sąsa PAVYZDYS # Tik suvestinės režimas – praleisti didelių įrenginių lenteles (1–2 min., palyginti su 20 minučių) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -SummaryOnly # Generuoja CSV, bet praleidžia HTML ataskaitų sritį su išsamiomis įrenginio lentelėmis
sąsa PASTABOS Susieti su Detect-SecureBootCertUpdateStatus.ps1, skirta diegti įmonėje.Visą diegimo vadovą žr. GPO-DEPLOYMENT-GUIDE.md. Numatytasis veikimas neapima stebėjimo, pristabdymo ir nepalaikomų įrenginių , kad būtų galima kurti ataskaitas tik į aktyvias įrenginių talpyklas.#>
param( [Parametras(Privalomas = $true)] [eilutė]$InputPath, [Parametras(Privalomas = $false)] [eilutė]$OutputPath = ".\SecureBootReports", [Parametras(Privalomas = $false)] [eilutė]$ScanHistoryPath = ".\SecureBootReports\ScanHistory.json", [Parametras(Privalomas = $false)] [eilutė]$RolloutStatePath, # kelias į RolloutState.json, kad būtų galima identifikuoti "InProgress" įrenginius [Parametras(Privalomas = $false)] [eilutė]$RolloutSummaryPath, # kelias į SecureBootRolloutSummary.json iš valdymo modulio (apima projekcijos duomenis) [Parametras(Privalomas = $false)] [string[]]$IncludeConfidenceLevels = @("Būtinas veiksmas", "Didelis patikimumas"), # Įtraukite tik šiuos patikimumo lygius (numatytoji reikšmė: tik veiksmų talpyklos) [Parametras(Privalomas = $false)] [switch]$IncludeAllConfidenceLevels, # Override filter to include all confidence levels [Parametras(Privalomas = $false)] [jungiklis]$SkipHistoryTracking, [Parametras(Privalomas = $false)] [jungiklis]$IncrementalMode, # Įgalinti delta apdorojimą – įkelti tik pakeistus failus nuo paskutinio vykdymo [Parametras(Privalomas = $false)] [eilutė]$CachePath, # kelias į talpyklos katalogą (numatytoji reikšmė: OutputPath\.cache) [Parametras(Privalomas = $false)] [sveikasis skaičius]$ParallelThreads = 8, # Lygiagrečių gijų, skirtų failui įkelti, skaičius (PS7+) [Parametras(Privalomas = $false)] [switch]$ForceFullRefresh, # Force full reload even in incremental mode [Parametras(Privalomas = $false)] [jungiklis]$SkipReportIfUnchanged, # Praleisti HTML / CSV generavimą, jei failų nepasikeitė (tik išvesties statistika) [Parametras(Privalomas = $false)] [jungiklis]$SummaryOnly, # Generuoti suvestinės statistiką tik (nėra didelių įrenginių lentelių) – daug greičiau [Parametras(Privalomas = $false)] [switch]$StreamingMode # Memory-efficient mode: process chunks, write CSV incrementally, keep only summaries in memory )
# Automatiškai pakelti į "PowerShell 7", jei galima (6x greičiau dideliems duomenų rinkiniams) jei ($PSVersionTable.PSVersion.Major -lt 7) { $pwshPath = Get-Command pwsh -ErrorAction SilentlyContinue | Select-Object –ExpandProperty šaltinis jei ($pwshPath) { Write-Host aptikta "PowerShell $ ($PSVersionTable.PSVersion) – paleidimas iš naujo naudojant "PowerShell 7", kad būtų greičiau apdorojama..." - "ForegroundColor Yellow" # Perkurti argumentų sąrašą iš susietų parametrų $relaunchArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $MyInvocation.MyCommand.Path) foreach ($key in $PSBoundParameters.Keys) { $val = $PSBoundParameters[$key] jei ($val - yra [jungiklis]) { jei ($val. IsPresent) { $relaunchArgs += "-$key" } } elseif ($val -is [masyvas]) { $relaunchArgs += "-$key" $relaunchArgs += ($val -join ',") } dar { $relaunchArgs += "-$key" $relaunchArgs += "$val" } } & $pwshPath @relaunchArgs išeiti iš $LASTEXITCODE } }
$ErrorActionPreference = "Tęsti" $timestamp = Get-Date -Formatas "yyyyMMdd-HHmmss" $scanTime = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Diegimo ir stebėjimo pavyzdžiai"
# pastaba: šis scenarijus neturi priklausomybių nuo kitų scenarijų. # Viso įrankių rinkinio atsisiuntimas iš: $DownloadUrl -> $DownloadSubPage
#region sąranka Write-Host "=" * 60 -ForegroundColor Cyan Write-Host "Secure Boot Data Aggregation" -ForegroundColor Cyan Write-Host "=" * 60 -ForegroundColor Cyan
# Išvesties katalogo kūrimas jei (-not (testo kelias $OutputPath)) { New-Item -ItemType katalogas -Kelias $OutputPath -Force | Nu neapibrėžta reikšmė (Out-Null) }
# Įkelti duomenis – palaiko CSV (senstelėjusį) ir JSON (savuosius) formatus Write-Host "'nLoading data from: $InputPath" -ForegroundColor Yellow
# Pagalbinio asmens funkcija įrenginio objektui normalizuoti (tvarkyti lauko pavadinimo skirtumus) funkcija Normalize-DeviceRecord { param($device) # Handle Hostname vs HostName (JSON naudoja Hostname, CSV naudoja HostName) jei ($device. PSObject.Properties['Hostname'], o ne $device. PSObject.Properties['HostName']) { $device | Add-Member -NotePropertyName HostName -NotePropertyValue $device. Pagrindinio kompiuterio vardas – priverstinai } #Handle Confidence vs ConfidenceLevel (JSON naudoja patikimumą, CSV naudoja ConfidenceLevel) # ConfidenceLevel yra oficialus lauko pavadinimas – žemėlapio patikimumas jei ($device. PSObject.Properties['Confidence'], o ne $device. PSObject.Properties['ConfidenceLevel']) { $device | Add-Member -NotePropertyName 'ConfidenceLevel' -NotePropertyValue $device. Pasitikėjimas – priverstinai } # Sekti naujinimo būseną per Event1808Count OR UEFICA2023Status="Updated" # Tai leidžia sekti, kiek įrenginių buvo atnaujinta kiekvienoje patikimumo talpykloje $event 1808 = 0 jei ($device. PSObject.Properties['Event1808Count']) { $event 1808 = [sveikasis skaičius]$device. Event1808Count } $uefiCaUpdated = $false jei ($device. PSObject.Properties['UEFICA2023Status'] ir $device. UEFICA2023Status -eq "Atnaujinta") { $uefiCaUpdated = $true } jei ($event 1808 -gt 0 -arba $uefiCaUpdated) { # Pažymėti kaip atnaujintą ataskaitų srities / diegimo logikai, bet NEPAISYKITE ConfidenceLevel $device | Add-Member -NotePropertyName IsUpdated -NotePropertyValue $true -Force } dar { $device | Add-Member -NotePropertyName IsUpdated -NotePropertyValue $false -Force # ConfidenceLevel klasifikacija: # - "High Confidence", "Under Observation...", "Temporarily Paused...", "Not Supported..." = use as-is # - Visa kita (neapibrėžta, tuščia, "UpdateType:...", "Unknown", "N/A") = patenka į veiksmą, būtiną skaitikliuose # Normalizuoti nereikia – srautinio perdavimo skaitiklio šaka ją apdoroja } # Rankenėlė OEMManufacturerName ir WMI_Manufacturer (JSON naudoja OĮG*, senstelėjusi naudoja WMI_*) jei ($device. PSObject.Properties['OEMManufacturerName'], o ne $device. PSObject.Properties['WMI_Manufacturer']) { $device | $device Add-Member -NotePropertyName WMI_Manufacturer -NotePropertyValue. OEMManufacturerName – jėga } # Handle OEMModelNumber vs WMI_Model jei ($device. PSObject.Properties['OEMModelNumber'], o ne $device. PSObject.Properties['WMI_Model']) { $device | Add-Member NotePropertyName WMI_Model -NotePropertyValue $device. OEMModelNumber - Force } #Handle FirmwareVersion vs BIOSDescription jei ($device. PSObject.Properties['FirmwareVersion'] - ir -not $device. PSObject.Properties['BIOSDescription']) { $device | $device Add-Member -NotePropertyName BIOSDescription -NotePropertyValue. FirmwareVersion - Force } grąžinimo $device }
#region papildantįjį apdorojimą / talpyklos valdymą # Nustatyti talpyklos kelius jei (-not $CachePath) { $CachePath = Join-Path $OutputPath ".cache" } $manifestPath = Join-Path $CachePath "FileManifest.json" $deviceCachePath = Join-Path $CachePath "DeviceCache.json"
# Talpyklos valdymo funkcijos funkcija Get-FileManifest { param([eilutė]$Path) if (Test-Path $Path) { išbandykite { $json = Get-Content $Path -Neapdorota | KonvertuotiFrom-Json # Convert PSObject to maišos lentelę (suderinamas su PS5.1 - PS7 turi -AsHashtable) $ht = @{} $json. PSObject.Properties | ForEach-Object { $ht[$_. Pavadinimas] = $_. Reikšmė } grąžinimo $ht } sugauti { grąžinti @{} } } grąžinti @{} }
funkcija Save-FileManifest { param([maišos lentelė]$Manifest, [eilutė]$Path) $dir = pirminis Split-Path $Path jei (-not (bandomasis kelias $dir)) { New-Item -ItemType Katalogas -Kelias $dir -Priverstinai | Nu neapibrėžta reikšmė (Out-Null) } $Manifest | ConvertTo-Json gylis 3 -Glaudinti | Set-Content $Path –Force }
funkcija Get-DeviceCache { param([eilutė]$Path) if (Test-Path $Path) { išbandykite { $cacheData = Get-Content $Path -Neapdorota | KonvertuotiFrom-Json Write-Host " Įkelta įrenginio talpykla: $($cacheData.Count) įrenginiai" -ForegroundColor DarkGray grąžinimo $cacheData } sugauti { Write-Host " Cache corrupted, will rebuild" -ForegroundColor Yellow grįžti @() } } grįžti @() }
funkcija Save-DeviceCache { param($Devices, [eilutė]$Path) $dir = pirminis Split-Path $Path jei (-not (bandomasis kelias $dir)) { New-Item -ItemType Katalogas -Kelias $dir -Priverstinai | Nu neapibrėžta reikšmė (Out-Null) } # Konvertuoti į masyvą ir įrašyti $deviceArray = @($Devices) $deviceArray | ConvertTo-Json -Gylis 10 -Glaudinti | Set-Content $Path –Force Write-Host " Įrašytų įrenginių talpykla: $($deviceArray.Count) įrenginiai" -ForegroundColor DarkGray }
funkcija Get-ChangedFiles { param( [System.IO.FileInfo[]]$AllFiles, [maišos lentelė]$Manifest ) $changed = [System.Collections.ArrayList]::new() $unchanged = [System.Collections.ArrayList]::new() $newManifest = @{} # Kurti nejautrų didžiųjų ir mažųjų raidžių peržvalgą iš deklaracijos (normalizuoti mažosiomis raidėmis) $manifestLookup = @{} foreach ($mk in $Manifest.Keys) { $manifestLookup[$mk. ToLowerInvariant()] = $Manifest[$mk] } foreach ($file in $AllFiles) { $key = $file. FullName.ToLowerInvariant() # Normalizuoti kelią į mažąsias raides $lwt = $file. LastWriteTimeUtc.ToString("o") $newManifest[$key] = @{ LastWriteTimeUtc = $lwt Dydis = $file. Ilgis } jei ($manifestLookup.ContainsKey($key)) { $cached = $manifestLookup[$key] jei ($cached. LastWriteTimeUtc – eq $lwt ir $cached. Dydis -eq $file. Ilgis) { [negalioja]$unchanged. Pridėti($file) Toliau } } [negalioja]$changed. Add($file) } grąžinti @{ Pakeista = $changed Nepakitęs = $unchanged NewManifest = $newManifest } }
# Itin greitas lygiagretus failų įkėlimas naudojant paketinį apdorojimą funkcija Load-FilesParallel { param( [System.IO.FileInfo[]]$Files, [sveikasis skaičius]$Threads = 8 )
$totalFiles = $Files. Skaičius # Naudokite ~1000 failų paketus, kad geriau kontroliuotų atmintį $batchSize = [matematika]::Min(1000, [matematika]::Ceiling($totalFiles / [matematika]::Maks(1, $Threads))) $batches = [System.Collections.generic.List[object]]::new()
for ($i = 0; $i -lt $totalFiles; $i += $batchSize) { $end = [matematika]:Min($i + $batchSize, $totalFiles) $batch = $Files[$i.. ($end-1)] $batches. Add($batch) } Write-Host " ($($batches. Skaičius) ~$batchSize failų paketai)" -NoNewline -ForegroundColor DarkGray $flatResults = [System.Collections.Generic.List[object]]::new() # Patikrinkite, ar yra "PowerShell 7+ parallel" $canParallel = $PSVersionTable.PSVersion.Major -ge 7 jei ($canParallel -and $Threads -gt 1) { # PS7+: lygiagrečiai apdoroti paketus $results = $batches | ForEach-Object -ThrottleLimit $Threads -Parallel { $batchFiles = $_ $batchResults = [System.Collections.Generic.List[object]]::new() foreach ($file in $batchFiles) { išbandykite { $content = [System.IO.File]::ReadAllText($file. Vardas ir pavardė) | KonvertuotiFrom-Json $batchResults.Add($content) } sugauti { } } $batchResults.ToArray() } foreach ($batch in $results) { if ($batch) { foreach ($item in $batch) { $flatResults.Add($item) } } } } dar { # PS5.1 atsarginė priemonė: nuoseklus apdorojimas (vis dar greitai <10K failų) foreach ($file in $Files) { išbandykite { $content = [System.IO.File]::ReadAllText($file. Vardas ir pavardė) | KonvertuotiFrom-Json $flatResults.Add($content) } sugauti { } } } return $flatResults.ToArray() } #endregion
$allDevices = @() jei (Test-Path $InputPath -PathType Leaf) { # Vienas JSON failas jei ($InputPath -like "*.json") { $jsonContent = Get-Content -Path $InputPath -Raw | KonvertuotiFrom-Json $allDevices = @($jsonContent) | ForEach-Object { Normalize-DeviceRecord $_ } Write-Host "Įkelti $($allDevices.Count) įrašai iš failo" } dar { Write-Error "Palaikomas tik JSON formatas. Failas turi turėti .json plėtinį." išeiti iš 1 } } elseif (bandymo kelio $InputPath -PathType konteineris) { # Aplankas – tik JSON $jsonFiles = @(Get-ChildItem -Path $InputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_. Pavadinimas -notmatch "ScanHistory|RolloutState|RolloutPlan" }) # Pageidaujamas *_latest.json failus, jei jie yra (kompiuterio režimu) $latestJson = $jsonFiles | Where-Object { $_. Pavadinimas -like "*_latest.json" } jei ($latestJson.Count -gt 0) { $jsonFiles = $latestJson } $totalFiles = $jsonFiles.Count jei ($totalFiles -eq 0) { Write-Error "JSON failų nerasta: $InputPath" išeiti iš 1 } Write-Host "Rasti $totalFiles JSON failai" – priekinio planopalva pilka # pagalbine priemonė, atitinkanti patikimumo lygius (apdoroja trumpas ir pilnas formas) # Apibrėžta anksti, kad jį galėtų naudoti ir "StreamingMode", ir įprasti keliai funkcija Test-ConfidenceLevel { param([eilutė]$Value, [eilutė]$Match) if ([string]::IsNullOrEmpty($Value)) { return $false } jungiklis ($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*" } "NotSupported" { return ($Value -like "Not Supported*" -or $Value -eq "Not Supported") } numatytasis { return $false } } } #region SRAUTINIO PERDAVIMO REŽIMAS – atmintį taupantis didelių duomenų rinkinių apdorojimas # Visada naudoti StreamingMode, kad būtų efektyviai apdorojama atmintis ir naujo stiliaus ataskaitų sritis jei (-not $StreamingMode) { Write-Host "Auto-enabling StreamingMode (new-style dashboard)" -ForegroundColor Yellow $StreamingMode = $true jei (-not $IncrementalMode) { $IncrementalMode = $true } } # Kai "-StreamingMode" įjungtas, apdoroti failus segmentuose ir laikyti tik skaitiklius atmintyje.# Įrenginio lygio duomenys įrašomi į JSON failus pagal segmentą, kad būtų įkelti ataskaitų srityje pareikalavus.# Atminties naudojimas: ~1,5 GB, neatsižvelgiant į duomenų rinkinio dydį (palyginti su 10–20 GB be srautinio perdavimo).jei ($StreamingMode) { Write-Host "STREAMING MODE enabled - memory-efficient processing" -ForegroundColor Green $streamSw = [System.Diagnostics.Stopwatch]::StartNew() # PAPILDANTYSIS TIKRINIMAS: jei po paskutinio paleidimo nepasikeitė jokie failai, visiškai praleisti apdorojimą jei ($IncrementalMode -and -not $ForceFullRefresh) { $stManifestDir = Join-Path $OutputPath ".cache" $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" if (Test-Path $stManifestPath) { Write-Host "Tikrinama, ar yra pakeitimų po paskutinio srautinio perdavimo vykdymo..." -Priekinio planopalvos žydra $stOldManifest = Get-FileManifest -Path $stManifestPath jei ($stOldManifest.Count -gt 0) { $stChanged = $false # Sparčioji patikra: toks pat failų skaičius? jei ($stOldManifest.Count -eq $totalFiles) { # Patikrinkite 100 NAUJAUSIŲ failų (surūšiuoti pagal LastWriteTime mažėjimo tvarka) # Jei kuris nors failas pakeistas, jame bus naujausia laiko žyma ir ji bus rodoma pirmiausia $sampleSize = [matematika]::Min(100, $totalFiles) $sampleFiles = $jsonFiles | Sort-Object LastWriteTimeUtc -Mažėjimo tvarka | Select-Object – pirmasis $sampleSize foreach ($sf in $sampleFiles) { $sfKey = $sf. FullName.ToLowerInvariant() jei (-not $stOldManifest.ContainsKey($sfKey)) { $stChanged = $true Pertraukos } # Palyginti laiko žymas – po JSON kelionės pirmyn ir atgal talpykloje gali būti data ir laikas arba eilutė $cachedLWT = $stOldManifest[$sfKey]. LastWriteTimeUtc $fileDT = $sf. LastWriteTimeUtc išbandykite { # Jei talpykloje jau yra "DateTime" ("ConvertFrom-Json" automatinis konvertavimas), naudokite tiesiogiai jei ($cachedLWT -is [DateTime]) { $cachedDT = $cachedLWT.ToUniversalTime() } dar { $cachedDT = [DateTimeOffset]::P arse("$cachedLWT"). Utcdatetime } if ([math]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt 1) { $stChanged = $true Pertraukos } } sugauti { $stChanged = $true Pertraukos } } } dar { $stChanged = $true } jei (-not $stChanged) { # Patikrinkite, ar yra išvesties failų $stSummaryExists = Get-ChildItem (Join-Path $OutputPath "SecureBoot_Summary_*.csv") -EA SilentlyContinue | Select-Object – pirmas 1 $stDashExists = Get-ChildItem (join-path $OutputPath "SecureBoot_Dashboard_*.html") -EA SilentlyContinue | Select-Object – pirmas 1 jei ($stSummaryExists ir $stDashExists) { Write-Host " Neaptikta jokių pakeitimų ($totalFiles nepakeisti failai) - apdorojimo praleidimas" -Priekinio planopalva Žalia Write-Host " Paskutinė ataskaitų sritis: $($stDashExists.FullName)" -ForegroundColor White $cachedStats = Get-Content $stSummaryExists.FullName | ConvertFrom-Csv Write-Host " Įrenginiai: $($cachedStats.TotalDevices) | Atnaujinta: $($cachedStats.Updated) | Klaidos: $($cachedStats.WithErrors)" -Priekinio planopalva pilka Write-Host " Atlikta $([matematika]::Round($streamSw.Elapsed.TotalSeconds, 1))s (nereikia apdoroti)" -Priekinio planocolor Žalia grąžinimo $cachedStats } } dar { # DELTA PATAISA: raskite, kurie failai buvo tiksliai pakeisti Write-Host – nustatyti pakeitimai – nustatyti pakeisti failai..." – geltona priekinio plano spalva $changedFiles = [System.Collections.ArrayList]::new() $newFiles = [System.Collections.ArrayList]::new() foreach ($jf in $jsonFiles) { $jfKey = $jf. FullName.ToLowerInvariant() jei (-not $stOldManifest.ContainsKey($jfKey)) { [negalioja]$newFiles.Add($jf) } dar { $cachedLWT = $stOldManifest[$jfKey]. LastWriteTimeUtc $fileDT = $jf. LastWriteTimeUtc išbandykite { $cachedDT = jei ($cachedLWT -is [DateTime]) { $cachedLWT.ToUniversalTime() } dar { [DateTimeOffset]::P arse("$cachedLWT"). UtcDateTime } if ([math]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt 1) { [negalioja]$changedFiles.Add($jf) } } sugauti { [negalioja]$changedFiles.Add($jf) } } } $totalChanged = $changedFiles.Count + $newFiles.Count $changePct = [matematika]::Round(($totalChanged / $totalFiles) * 100, 1) Write-Host " Pakeista: $($changedFiles.Count) | Nauja: $($newFiles.Count) | Iš viso: $totalChanged ($changePct%)" - priekinio planopalva geltona jei ($totalChanged -gt 0 -ir $changePct -lt 10) { # DELTA PATAISŲ REŽIMAS: <pakeista 10 %, pataisų esami duomenys Write-Host " Delta pataisų režimas ($changePct % < 10 %) – pataisų $totalChanged failai..." -Priekinio planocolor žalia $dataDir = Join-Path $OutputPath "data" # Įkelti pakeistus / naujus įrenginio įrašus $deltaDevices = @{} $allDeltaFiles = @($changedFiles) + @($newFiles) foreach ($df in $allDeltaFiles) { išbandykite { $devData = Get-Content $df. FullName – žalias | KonvertuotiFrom-Json $dev = Normalize-DeviceRecord $devData jei ($dev. HostName) { $deltaDevices[$dev. HostName] = $dev } } sugauti { } } Write-Host " Įkelti $($deltaDevices.Count) pakeisti įrenginio įrašai" -Priekinio planopalva Pilka # Kiekvienai kategorijai JSON: pašalinkite senus pakeistų pagrindinių kompiuterių įrašus, įtraukite naujų įrašų $categoryFiles = @("klaidos", "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 $deltaDevices.Keys) { [void]$changedHostnames.Add($hn) } foreach ($cat in $categoryFiles) { $catPath = Join-Path $dataDir "$cat.json" if (Test-Path $catPath) { išbandykite { $catData = Get-Content $catPath -Raw | KonvertuotiFrom-Json # Pašalinti senus pakeistų pagrindinio kompiuterio vardų įrašus $catData = @($catData | Where-Object { -not $changedHostnames.Contains($_. Pagrindinio kompiuterio vardas) }) # Iš naujo klasifikuoti kiekvieną pakeistą įrenginį į kategorijas # (bus pridėta žemiau po klasifikavimo) $catData | ConvertTo-Json gylis 5 | Set-Content $catPath – utf8 kodavimas } sugauti { } } } # Klasifikuoti kiekvieną pakeistą įrenginį ir pridėti prie tinkamų kategorijų failų foreach ($dev in $deltaDevices.Values) { $slim = [sutvarkyta]@{ HostName = $dev. Hostname WMI_Manufacturer = jei ($dev. PSObject.Properties['WMI_Manufacturer']) { $dev. WMI_Manufacturer } dar { "" } WMI_Model = jei ($dev. PSObject.Properties['WMI_Model']) { $dev. WMI_Model } dar { "" } BucketId = jei ($dev. PSObject.Properties['BucketId']) { $dev. BucketId } dar { "" } ConfidenceLevel = jei ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } dar { "" } IsUpdated = $dev. "IsUpdated" UEFICA2023Error = jei ($dev. PSObject.Properties['UEFICA2023Error']) { $dev. UEFICA2023Error } dar { $null } SecureBootTaskStatus = jei ($dev. PSObject.Properties['SecureBootTaskStatus']) { $dev. SecureBootTaskStatus } dar { "" } KnownIssueId = jei ($dev. PSObject.Properties['KnownIssueId']) { $dev. KnownIssueId } dar { $null } SkipReasonKnownIssue = jei ($dev. PSObject.Properties['SkipReasonKnownIssue']) { $dev. SkipReasonKnownIssue } dar { $null } } $isUpd = $dev. IsUpdated – eq $true $conf = jei ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } dar { "" } $hasErr = (-not [eilutė]::IsNullOrEmpty($dev. UEFICA2023Error) ir $dev. UEFICA2023Error -ne "0" ir $dev. UEFICA2023Error -ne "") $tskDis = ($dev. SecureBootTaskEnabled – eq $false arba $dev. SecureBootTaskStatus - eq Išjungta arba $dev. SecureBootTaskStatus - eq 'NotFound') $tskNF = ($dev. SecureBootTaskStatus - eq 'NotFound') $sbOn = ($dev. SecureBootEnabled - ne $false ir $($dev. SecureBootEnabled)" -ne "False") $e 1801 = jei ($dev. PSObject.Properties['Event1801Count']) { [int]$dev. Event1801Count } dar { 0 } $e 1808 = jei ($dev. PSObject.Properties['Event1808Count']) { [int]$dev. Event1808Count } dar { 0 } $e 1803 = jei ($dev. PSObject.Properties['Event1803Count']) { [int]$dev. Event1803Count } dar { 0 } $mKEK = ($e 1803 -gt 0 -arba $dev. MissingKEK -eq $true) $hKI = ((-not [eilutė]::IsNullOrEmpty($dev. SkipReasonKnownIssue)) arba (-not [eilutė]::IsNullOrEmpty($dev. ŽinomiIssueId))) $rStat = jei ($dev. PSObject.Properties['RolloutStatus']) { $dev. RolloutStatus } dar { "" } # Pridėti prie atitinkančių kategorijų failų $targets = @() if ($isUpd) { $targets += "updated_devices" } jei ($hasErr) { $targets += "klaidos" } if ($hKI) { $targets += "known_issues" } if ($mKEK) { $targets += "missing_kek" } jei (-not $isUpd -and $sbOn) { $targets += "not_updated" } if ($tskDis) { $targets += "task_disabled" } jei (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPaused'))) { $targets += "temp_failures" } jei (-not $isUpd -and ((Test-ConfidenceLevel $conf 'NotSupported') -or ($tskNF -and $hasErr))) { $targets += "perm_failures" } jei (-not $isUpd -and (Test-ConfidenceLevel $conf 'ActionRequired')) { $targets += "action_required" } jei (-not $sbOn) { $targets += "secureboot_off" } jei ($e 1801 -gt 0 -ir $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 (Test-Path $tgtPath) { $existing = Get-Content $tgtPath -Žalias | KonvertuotiFrom-Json $existing = @($existing) + @([PSCustomObject]$slim) $existing | ConvertTo-Json gylis 5 | Set-Content $tgtPath – utf8 kodavimas } } } # Generuoti CV iš pataisų JSON Write-Host " Generuojami CSV iš pataisų duomenų..." -Priekinio planopalva Pilka $newTimestamp = Get-Date -Format "yyyyMMdd-HHmmss" foreach ($cat in $categoryFiles) { $catJsonPath = Join-Path $dataDir "$cat.json" $catCsvPath = Join-Path $OutputPath "SecureBoot_${cat}_$newTimestamp.csv" if (Test-Path $catJsonPath) { išbandykite { $catJsonData = Get-Content $catJsonPath -Neapdorota | KonvertuotiFrom-Json jei ($catJsonData.Count -gt 0) { $catJsonData | Export-Csv -Path $catCsvPath -NoTypeInformation -Encoding UTF8 } } sugauti { } } } # Recount stats from the patched JSON files Write-Host " Suvestinės perskaičiavimas iš pataisų duomenų..." -Priekinio planopalva Pilka $patchedStats = [užsakyta]@{ ReportGeneratedAt = (Get-Date). 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 jei (Test-Path $catPath) { try { $cnt = (Get-Content $catPath -Raw | ConvertFrom-Json). Skaičius } sugauti { } } jungiklis ($cat) { "updated_devices" { $pUpdated = $cnt } "klaidos" { $pErrors = $cnt } "known_issues" { $pKI = $cnt } "missing_kek" { $pKEK = $cnt } "not_updated" { } # apskaičiuota "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). Skaičius $pTotal = $pUpdated + $pNotUpdated + $pSBOff Write-Host " Delta pataisa baigta: atnaujinti $totalChanged įrenginiai" -Priekinio planopalva žalia Write-Host " Iš viso: $pTotal | Atnaujinta: $pUpdated | NotUpdated: $pNotUpdated | Klaidos: $pErrors" - "ForegroundColor White" # Atnaujinti deklaraciją $stManifestDir = Join-Path $OutputPath ".cache" $stNewManifest = @{} foreach ($jf in $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o"); Dydis = $jf. Ilgis } } Save-FileManifest -Manifest $stNewManifest -Path $stManifestPath Write-Host " Atlikta $([matematika]::Round($streamSw.Elapsed.TotalSeconds, 1))s (delta pataisa - $totalChanged įrenginiai)" -Priekinio planopalva Žalia # Fall through to full streaming reprocess to regenerate HTML dashboard # Duomenų failai jau pataisyti, todėl tai užtikrina, kad ataskaitų sritis liktų naujausia Write-Host " Regeneruojama ataskaitų sritis iš pataisų duomenų..." -Priekinio planopalva Geltona } dar { Write-Host " pakeisti $changePct % failai (>= 10 %) – reikia viso srautinio perdavimo pakartotinio apdorojimo" -Priekinio planopalva Geltona } } } } } # Kurti duomenų pakatalogį užsakomųjų įrenginių JSON failams $dataDir = Join-Path $OutputPath "data" if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir -Force | Out-Null } # Dublikatų šalinimas per "HashSet" (O(1) peržvalgą, ~50 MB 600K pagrindinio kompiuterio vardams) $seenHostnames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) # Supaprastintosios suvestinės skaitikliai (keičia atmintyje $allDevices + $uniqueDevices) $c = @{ Iš viso = 0; SBEnabled = 0; SBOff = 0 Atnaujinta = 0; HighConf = 0; UnderObs = 0; ActionReq = 0; TempPaused = 0; Nepalaikoma = 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/ SafeList" segmentų sekimas (lengvieji rinkiniai) $stFailedBuckets = [System.Collections.Generic.HashSet[string]]::new() $stSuccessBuckets = [System.Collections.Generic.HashSet[string]]::new() $stAllBuckets = @{} $stMfrCounts = @{} $stErrorCodeCounts = @{}; $stErrorCodeSamples = @{} $stKnownIssueCounts = @{} # Batch-mode device data files: accumulate per-chunk, flush at chunk boundaries $stDeviceFiles = @("klaidos", "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 bytes vs ~2KB full) funkcija Get-SlimDevice { param($Dev) grąžinti [užsakyta]@{ HostName = $Dev.HostName WMI_Manufacturer = if ($Dev.PSObject.Properties['WMI_Manufacturer']) { $Dev.WMI_Manufacturer } dar { "" } WMI_Model = if ($Dev.PSObject.Properties['WMI_Model']) { $Dev.WMI_Model } dar { "" } BucketId = if ($Dev.PSObject.Properties['BucketId']) { $Dev.BucketId } dar { "" } ConfidenceLevel = if ($Dev.PSObject.Properties['ConfidenceLevel']) { $Dev.ConfidenceLevel } dar { "" } IsUpdated = $Dev.IsUpdated UEFICA2023Error = if ($Dev.PSObject.Properties['UEFICA2023Error']) { $Dev.UEFICA2023Error } else { $null } SecureBootTaskStatus = if ($Dev.PSObject.Properties['SecureBootTaskStatus']) { $Dev.SecureBootTaskStatus } dar { "" } KnownIssueId = if ($Dev.PSObject.Properties['KnownIssueId']) { $Dev.KnownIssueId } dar { $null } SkipReasonKnownIssue = if ($Dev.PSObject.Properties['SkipReasonKnownIssue']) { $Dev.SkipReasonKnownIssue } dar { $null } UEFICA2023Status = if ($Dev.PSObject.Properties['UEFICA2023Status']) { $Dev.UEFICA2023Status } else { $null } AvailableUpdatesPolicy = jei ($Dev.PSObject.Properties['AvailableUpdatesPolicy']) { $Dev.AvailableUpdatesPolicy } dar { $null } WinCSKeyApplied = jei ($Dev.PSObject.Properties['WinCSKeyApplied']) { $Dev.WinCSKeyApplied } dar { $null } } } # Išvalyti paketą į JSON failą (pridėjimo režimas) funkcija Flush-DeviceBatch { param([eilutė]$StreamName, [System.Collections.Generic.List[object]]$Batch) jei ($Batch.Count -eq 0) { return } $fPath = $stDeviceFilePaths[$StreamName] $fSb = [System.Text.StringBuilder]::new() foreach ($fDev in $Batch) { if ($stDeviceFileCounts[$StreamName] -gt 0) { [void]$fSb.Append(",'n") } [negalioja]$fSb.Append(($fDev | ConvertTo-Json -Compress)) $stDeviceFileCounts[$StreamName]++ } [System.IO.File]::AppendAllText($fPath, $fSb.ToString(), [System.Text.Encoding]::UTF8) } # PAGRINDINIS SRAUTINIO PERDAVIMO CIKLAS $stChunkSize = jei ($totalFiles -le 10000) { $totalFiles } dar { 10000 } $stTotalChunks = [matematika]::Ceiling($totalFiles / $stChunkSize) $stPeakMemMB = 0 jei ($stTotalChunks -gt 1) { Write-Host "Apdorojami $totalFiles failai $stTotalChunks $stChunkSize segmentais (srautinis perdavimas, $ParallelThreads gijos):" -ForegroundColor Cyan } dar { Write-Host "Apdorojami $totalFiles failai (srautinis perdavimas, $ParallelThreads gijos):" - Priekinio plano spalvos žydra } skirta ($ci = 0; $ci -lt $stTotalChunks; $ci++) { $cStart = $ci * $stChunkSize $cEnd = [matematika]:Min($cStart + $stChunkSize, $totalFiles) - 1 $cFiles = $jsonFiles[$cStart.. $cEnd] jei ($stTotalChunks -gt 1) { Write-Host " Segmento $($ci + 1)/$stTotalChunks ($($cFiles.Count) failai): " -NoNewline -ForegroundColor Gray } dar { Write-Host " $($cFiles.Count) failų įkėlimas: " -NoNewline -ForegroundColor Gray } $cSw = [System.Diagnostics.Stopwatch]::StartNew() $rawDevices = Load-FilesParallel -Files $cFiles -gijos $ParallelThreads # Segmentų paketo sąrašai $cBatches = @{} foreach ($df in $stDeviceFiles) { $cBatches[$df] = [System.Collections.Generic.List[object]]::new() } $cNew = 0; $cDupe = 0 foreach ($raw in $rawDevices) { jei (-not $raw) { continue } $device = Normalize-DeviceRecord $raw $hostname = $device. Hostname jei (-not $hostname) { continue } jei ($seenHostnames.Contains($hostname)) { $cDupe++; tęsti } [negalioja]$seenHostnames.Add($hostname) $cNew++; $c.Total++ $sbOn = ($device. SecureBootEnabled - ne $false ir $($device. SecureBootEnabled)" -ne "False") jei ($sbOn) { $c.SBEnabled++ } dar { $c.SBOff++; $cBatches["secureboot_off"]. Add((Get-SlimDevice $device)) } $isUpd = $device. IsUpdated – eq $true $conf = jei ($device. PSObject.Properties['ConfidenceLevel'] ir $device. ConfidenceLevel) { "$($device. ConfidenceLevel)" } dar { "" } $hasErr = (-not [eilutė]::IsNullOrEmpty($device. UEFICA2023Error) – ir "$($device. UEFICA2023Error)" -ne "0" - ir "$($device. UEFICA2023Error)" -ne "") $tskDis = ($device. SecureBootTaskEnabled - eq $false arba $($device. SecureBootTaskStatus)" - eq Išjungta arba $($device. SecureBootTaskStatus)" -eq 'NotFound') $tskNF = ("$($device. SecureBootTaskStatus)" -eq 'NotFound') $bid = jei ($device. PSObject.Properties['BucketId'] ir $device. BucketId) { "$($device. BucketId)" } dar { "" } $e 1808 = jei ($device. PSObject.Properties['Event1808Count']) { [int]$device. Event1808Count } dar { 0 } $e 1801 = jei ($device. PSObject.Properties['Event1801Count']) { [int]$device. Event1801Count } dar { 0 } $e 1803 = jei ($device. PSObject.Properties['Event1803Count']) { [int]$device. Event1803Count } dar { 0 } $mKEK = ($e 1803 -gt 0 -arba $device. MissingKEK -eq $true - arba "$($device. MissingKEK)" -eq "True") $hKI = ((-not [eilutė]::IsNullOrEmpty($device. SkipReasonKnownIssue)) arba (-not [eilutė]::IsNullOrEmpty($device. ŽinomiIssueId))) $rStat = jei ($device. PSObject.Properties['RolloutStatus']) { $device. RolloutStatus } dar { "" } $mfr = jei ($device. PSObject.Properties['WMI_Manufacturer'] -and -not [string]::IsNullOrEmpty($device. WMI_Manufacturer)) { $device. WMI_Manufacturer } dar { "Nežinoma" } $bid = jei (-not [eilutė]::IsNullOrEmpty($bid)) { $bid } dar { "" } # Prieš skaičiavimo naujinimą laukianti vėliavėlė (taikoma strategija /WinCS, būsena dar neatnaujinta, SB ĮJUNGTA, užduotis neišjungta) $uefiStatus = jei ($device. PSObject.Properties['UEFICA2023Status']) { "$($device. UEFICA2023Status)" } else { "" } $hasPolicy = ($device. PSObject.Properties['AvailableUpdatesPolicy'] ir $null -ne $device. AvailableUpdatesPolicy ir $($device. AvailableUpdatesPolicy)" -ne ') $hasWinCS = ($device. PSObject.Properties['WinCSKeyApplied'] ir $device. WinCSKeyApplied – eq $true) $statusPending = ([eilutė]::IsNullOrEmpty($uefiStatus) -arba $uefiStatus -eq NotStarted -arba $uefiStatus -eq 'InProgress') $isUpdatePending = (($hasPolicy -arba $hasWinCS) -and $statusPending -and -not $isUpd -and $sbOn -and -not $tskDis) jei ($isUpd) { $c.Updated++; [negalioja]$stSuccessBuckets.Add($bid); $cBatches["updated_devices]." Add((Get-SlimDevice $device)) # Sekti Atnaujintus įrenginius, kuriuos reikia paleisti iš naujo (UEFICA2023Status=Updated, bet Event1808=0) jei ($e 1808 -eq 0) { $c.NeedsReboot++; $cBatches["needs_reboot"]. Add((Get-SlimDevice $device)) } } elseif (-not $sbOn) { # SecureBoot OFF - nepatenka į aprėptį, neklasifikuoti pagal pasitikėjimą } kita { jei ($isUpdatePending) { } # Skaičiuojama atskirai laukiant naujinimo – tarpusavyje nesuderinamos skritulinės diagramos 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++ } } jei ($tskDis) { $c.TaskDisabled++; $cBatches["task_disabled"]. Add((Get-SlimDevice $device)) } jei ($tskNF) { $c.TaskNotFound++ } jei (-not $isUpd -and $tskDis) { $c.TaskDisabledNotUpdated++ } jei ($hasErr) { $c.WithErrors++; [negalioja]$stFailedBuckets.Add($bid); $cBatches["klaidos"]. Add((Get-SlimDevice $device)) $ec = $device. UEFICA2023Error jei (-not $stErrorCodeCounts.ContainsKey($ec)) { $stErrorCodeCounts[$ec] = 0; $stErrorCodeSamples[$ec] = @() } $stErrorCodeCounts[$ec]++ jei ($stErrorCodeSamples[$ec]. Count -lt 5) { $stErrorCodeSamples[$ec] += $hostname } } jei ($hKI) { $c.WithKnownIssues++; $cBatches["known_issues]." Add((Get-SlimDevice $device)) $ki = if (-not [string]::IsNullOrEmpty($device. SkipReasonKnownIssue)) { $device. SkipReasonKnownIssue } dar { $device. ŽinomiIssueId } jei (-not $stKnownIssueCounts.ContainsKey($ki)) { $stKnownIssueCounts[$ki] = 0 }; $stKnownIssueCounts[$ki]++ } jei ($mKEK) { $c.WithMissingKEK++; $cBatches["missing_kek"]. Add((Get-SlimDevice $device)) } jei (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPaused'))) { $c.TempFailures++; $cBatches["temp_failures"]. Add((Get-SlimDevice $device)) } jei (-not $isUpd -and ((Test-ConfidenceLevel $conf 'NotSupported') -or ($tskNF -and $hasErr))) { $c.PermFailures++; $cBatches["perm_failures"]. Add((Get-SlimDevice $device)) } jei ($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)) } jei ($e 1801 -gt 0 -ir $e 1808 -eq 0 -and -not $hasErr -and $rStat -ne "InProgress") { $c.NotYetInitiated++ } jei ($rStat -eq "InProgress" -and $e 1808 -eq 0) { $c.InProgress++ } # Laukiama naujinimo: taikoma strategija arba WinCS, laukiama būsenos, SB ĮJUNGTA, užduotis neišjungta jei ($isUpdatePending) { $c.UpdatePending++; $cBatches[update_pending]. Add((Get-SlimDevice $device)) } jei (-not $isUpd -and $sbOn) { $cBatches["not_updated"]. Add((Get-SlimDevice $device)) } # Dalyje Stebėjimo įrenginiai (atskirai nuo būtinų veiksmų) jei (-not $isUpd -and (Test-ConfidenceLevel $conf 'UnderObservation')) { $cBatches["under_observation"]. Add((Get-SlimDevice $device)) } # Būtinas veiksmas: neatnaujinta, SB ĮJUNGTA, neatitinka kitų patikimumo kategorijų, o ne laukiama naujinimo jei (-not $isUpd -and $sbOn -and -not $isUpdatePending -and -not (Test-ConfidenceLevel $conf 'HighConfidence') -and -not (Test-ConfidenceLevel $conf 'UnderObservation') -and -not (Test-ConfidenceLevel $conf 'TemporarilyPaused') -and -not (Test-ConfidenceLevel $conf 'NotSupported')) { $cBatches["action_required]." Add((Get-SlimDevice $device)) } jei (-not $stMfrCounts.ContainsKey($mfr)) { $stMfrCounts[$mfr] = @{ Total=0; Atnaujinta = 0; UpdatePending = 0; HighConf = 0; UnderObs = 0; ActionReq = 0; TempPaused = 0; Nepalaikoma = 0; SBOff = 0; WithErrors=0 } } $stMfrCounts[$mfr]. Total++ jei ($isUpd) { $stMfrCounts[$mfr]. Atnaujinta++ } 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]. Nepalaikoma++ } else { $stMfrCounts[$mfr]. ActionReq++ } jei ($hasErr) { $stMfrCounts[$mfr]. WithErrors++ } # Sekti visus įrenginius pagal talpyklą (įskaitant tuščią BucketId) $bucketKey = if ($bid -and $bid -ne "") { $bid } else { "(empty)" } jei (-not $stAllBuckets.ContainsKey($bucketKey)) { $stAllBuckets[$bucketKey] = @{ Count=0; Atnaujinta = 0; Gamintojas = $mfr; Modelis = ""; BIOS = "" } jei ($device. PSObject.Properties['WMI_Model']) { $stAllBuckets[$bucketKey]. Modelis = $device. WMI_Model } jei ($device. PSObject.Properties['BIOSDescription']) { $stAllBuckets[$bucketKey]. BIOS = $device. BIOSDescription } } $stAllBuckets[$bucketKey]. Count++ jei ($isUpd) { $stAllBuckets[$bucketKey]. Atnaujinta++ } } # Išvalyti paketus į diską foreach ($df in $stDeviceFiles) { Flush-DeviceBatch -StreamName $df -Batch $cBatches[$df] } $rawDevices = $null; $cBatches = $null; [System.GC]::Collect() $cSw.Stop() $cTime = [Matematika]::Round($cSw.Elapsed.TotalSeconds, 1) $cRem = $stTotalChunks - $ci - 1 $cEta = if ($cRem -gt 0) { " | ETA: ~$([Matematika]::Round($cRem * $cSw.Elapsed.TotalSeconds / 60, 1)) min" } dar { "" } $cMem = [matematika]::Round([System.GC]::GetTotalMemory($false) / 1MB, 0) jei ($cMem -gt $stPeakMemMB) { $stPeakMemMB = $cMem } Write-Host " +$cNew naujas, $cDupe dupes, ${cTime}s | Mem: ${cMem}MB$cEta" -Priekinio planopalva žalia } # Baigti JSON masyvus foreach ($dfName in $stDeviceFiles) { [System.IO.File]::AppendAllText($stDeviceFilePaths[$dfName], "'n]", [System.Text.Encoding]::UTF8) Write-Host " $dfName.json: $($stDeviceFileCounts[$dfName]) įrenginiai" -Priekinio planopalva DarkGray } # Skaičiavimo išvestinė statistika $stAtRisk = 0; $stSafeList = 0 foreach ($bid in $stAllBuckets.Keys) { $b = $stAllBuckets[$bid]; $nu = $b.Count – $b.Updated jei ($stFailedBuckets.Contains($bid)) { $stAtRisk += $nu } elseif ($stSuccessBuckets.Contains($bid)) { $stSafeList += $nu } } $stAtRisk = [matematika]:Max(0, $stAtRisk - $c.WithErrors) # NotUptodate = skaičius iš not_updated paketo (įrenginiai su SB ĮJUNGTAS ir neatnaujintas) $stNotUptodate = $stDeviceFileCounts["not_updated"] $stats = [užsakyta]@{ ReportGeneratedAt = (Get-Date). ToString("mmmm-MM-dd HH:mm:ss") TotalDevices = $c.Total; SecureBootEnabled = $c.SBEnabled; SecureBootOFF = $c.SBOff Atnaujinta = $c.Updated; HighConfidence = $c.HighConf; UnderObservation = $c.UnderObs ActionRequired = $c.ActionReq; TemporarilyPaused = $c.TempPaused; Nepalaikoma = $c.NotSupported 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 = jei ($c.Total -gt 0) { [math]::Round((($c.WithErrors/$c.Total)*100,2) } dar { 0 } PercentAtRisk = jei ($c.Total -gt 0) { [math]::Round((($stAtRisk/$c.Total)*100,2) } dar { 0 } PercentSafeList = jei ($c.Total -gt 0) { [math]::Round((($stSafeList/$c.Total)*100,2) } dar { 0 } PercentHighConfidence = if ($c.Total -gt 0) { [math]::Round((($c.HighConf/$c.Total)*100,1) } dar { 0 } PercentCertUpdated = jei ($c.Total -gt 0) { [math]::Round(($c.Updated/$c.Total)*100,1) } dar { 0 } PercentActionRequired = if ($c.Total -gt 0) { [math]::Round((($c.ActionReq/$c.Total)*100,1) } dar { 0 } PercentNotUptodate = jei ($c.Total -gt 0) { [math]::Round($stNotUptodate/$c.Total*100,1) } dar { 0 } PercentFullyUpdated = jei ($c.Total -gt 0) { [math]::Round((($c.Updated/$c.Total)*100,1) } dar { 0 } UniqueBuckets = $stAllBuckets.Count; PeakMemoryMB = $stPeakMemMB; ProcessingMode = "Srautinis perdavimas" } # Rašykite CSV [PSCustomObject]$stats | Export-Csv – kelias (jungties kelio $OutputPath "SecureBoot_Summary_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } -Descending | ForEach-Object { [PSCustomObject]@{ Manufacturer=$_. Raktas; Skaičiuoti =$_. Value.Total; Atnaujinta = $_. Value.Updated; HighConfidence=$_. Value.HighConf; ActionRequired =$_. Value.ActionReq } } | Export-Csv – kelias (jungties kelias $OutputPath "SecureBoot_ByManufacturer_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stErrorCodeCounts.GetEnumerator() | Sort-Object reikšmė – mažėjanti | ForEach-Object { [PSCustomObject]@{ ErrorCode=$_. Raktas; Skaičiuoti =$_. Vertė; SampleDevices=($stErrorCodeSamples[$_. Raktas] -join ", ") } } | Export-Csv – kelias (jungties kelio $OutputPath "SecureBoot_ErrorCodes_$timestamp.csv") -NoTypeInformation -Encoding UTF8 $stAllBuckets.GetEnumerator() | Sort-Object { $_. Value.Count } – mažėjimo tvarka | ForEach-Object { [PSCustomObject]@{ BucketId=$_. Raktas; Skaičiuoti =$_. Value.Count; Atnaujinta = $_. Value.Updated; NotUpdated = $_. Value.Count-$_. Value.Updated; Gamintojas = $_. Value.Manufacturer } } | Export-Csv – kelias (jungties kelio $OutputPath "SecureBoot_UniqueBuckets_$timestamp.csv") -NoTypeInformation - Encoding UTF8 # Generuoti su valdymo moduliu suderinamus CSV (numatomi Start-SecureBootRolloutOrchestrator.ps1 failų vardai) $notUpdatedJsonPath = Join-Path $dataDir "not_updated.json" if (Test-Path $notUpdatedJsonPath) { išbandykite { $nuData = Get-Content $notUpdatedJsonPath -Žalias | KonvertuotiFrom-Json jei ($nuData.Count -gt 0) { # NotUptodate CSV – valdymo modulio ieško *NotUptodate*.csv $nuData | Export-Csv – kelias (jungties kelias $OutputPath "SecureBoot_NotUptodate_$timestamp.csv") -NoTypeInformation - kodavimas UTF8 Write-Host " Orchestrator CSV: SecureBoot_NotUptodate_$timestamp.csv ($($nuData.Count) devices)" -ForegroundColor Gray } } sugauti { } } # Rašyti JSON duomenis ataskaitų srityje $stats | ConvertTo-Json gylis 3 | Set-Content (prisijungimo kelio $dataDir "summary.json") – kodavimas UTF8 # RETROSPEKTYVINIS SEKIMAS: išsaugokite tendencijos diagramos duomenų tašką # Naudokite stabilią talpyklos vietą, kad tendencijos duomenys veiktų laiko žymos agregavimo aplankuose. # Jei OutputPath atrodo kaip "...\Aggregation_yyyyMMdd_HHmmss", talpykla patenka į pirminį aplanką.# Kitu atveju talpykla patenka į pačią OutputPath.$parentDir = pirminis Split-Path $OutputPath $leafName = Split-Path $OutputPath -Leaf jei ($leafName -atitinka ^Aggregation_\d{8} arba $leafName -eq 'Aggregation_Current') { # Orchestrator sukurtas timestamped aplankas — naudokite pirminį stabilios talpyklos $historyPath = Join-Path $parentDir ".cache\trend_history.json" } dar { $historyPath = Join-Path $OutputPath ".cache\trend_history.json" } $historyDir = pirminis Split-Path $historyPath jei (-not (testo kelias $historyDir)) { New-Item -ItemType Katalogas -Kelias $historyDir -Priverstinai | Out-Null } $historyData = @() if (Test-Path $historyPath) { try { $historyData = @(Get-Content $historyPath -Raw | ConvertFrom-Json) } sugauti { $historyData = @() } } # Taip pat patikrinkite "OutputPath\.cache\" (senstelėjusi vieta iš senesnių versijų) # Sulieti duomenų elementus, kurių dar nėra pirminėje retrospektyvoje jei ($leafName -eq 'Aggregation_Current' -arba $leafName -atitinka '^Aggregation_\d{8}') { $innerHistoryPath = Join-Path $OutputPath ".cache\trend_history.json" if ((Test-Path $innerHistoryPath) -and $innerHistoryPath -ne $historyPath) { išbandykite { $innerData = @(Get-Content $innerHistoryPath -Raw | ConvertFrom-Json) $existingDates = @($historyData | ForEach-Object { $_. Data }) foreach ($entry in $innerData) { jei ($entry. Data ir $entry. Data -notin $existingDates) { $historyData += $entry } } jei ($innerData.Count -gt 0) { Write-Host " Merged $($innerData.Count) data points from inner cache" -ForegroundColor DarkGray } } sugauti { } } }
# BOOTSTRAP: jei tendencijos retrospektyva tuščia / retas, atkurti retrospektyvinius duomenis jei ($historyData.Count -lt 2 -and ($leafName -match '^Aggregation_\d{8}' -or $leafName -eq 'Aggregation_Current')) { Write-Host " Bootstrapping trend history from historical data..." -ForegroundColor Yellow $dailyData = @{} # Source 1: Summary CSV inside current folder (Aggregation_Current keeps all Summary CSV) $localSummaries = Get-ChildItem $OutputPath -Filter "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Sort-Object pavadinimas foreach ($summCsv in $localSummaries) { išbandykite { $summ = Import-Csv $summCsv.FullName | Select-Object 1 pirmas jei ($summ. TotalDevices – ir [int]$summ. TotalDevices -gt 0 -and $summ. ReportGeneratedAt) { $dateStr = ([datetime]$summ. AtaskaitaGeneratedAt). ToString("yyyy-MM-dd") $updated = if ($summ. Atnaujinta) { [sveikasis skaičius]$summ. Atnaujinta } dar { 0 } $notUpd = jei ($summ. NotUptodate) { [int]$summ. NotUptodate } dar { [int]$summ. TotalDevices - $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Data = $dateStr; Iš viso = [sveikasis skaičius]$summ. TotalDevices; Atnaujinta = $updated; NotUpdated = $notUpd NeedsReboot = 0; Klaidos = 0; ActionRequired = jei ($summ. ActionRequired) { [sveikasis skaičius]$summ. ActionRequired } dar { 0 } } } } sugauti { } } # 2 šaltinis: Seni laiko Aggregation_* aplankai (senstelėję, jei jie vis dar yra) $aggFolders = Get-ChildItem $parentDir -Directory -Filter "Aggregation_*" -EA SilentlyContinue | Where-Object { $_. Pavadinimas - atitiktis ^Aggregation_\d{8}' } | Sort-Object pavadinimas foreach ($folder in $aggFolders) { $summCsv = Get-ChildItem $folder. FullName – filtras "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Select-Object – pirmas 1 jei ($summCsv) { išbandykite { $summ = Import-Csv $summCsv.FullName | Select-Object 1 pirmas jei ($summ. TotalDevices – ir [int]$summ. TotalDevices -gt 0) { $dateStr = $folder. Pavadinimas - pakeisti ^Aggregation_(\d{4})(\d{2})(\d{2})_.*', '$1-$2-$3' $updated = if ($summ. Atnaujinta) { [sveikasis skaičius]$summ. Atnaujinta } dar { 0 } $notUpd = jei ($summ. NotUptodate) { [int]$summ. NotUptodate } dar { [int]$summ. TotalDevices - $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Data = $dateStr; Iš viso = [sveikasis skaičius]$summ. TotalDevices; Atnaujinta = $updated; NotUpdated = $notUpd NeedsReboot = 0; Klaidos = 0; ActionRequired = jei ($summ. ActionRequired) { [sveikasis skaičius]$summ. ActionRequired } dar { 0 } } } } sugauti { } } } # Šaltinis 3: RolloutState.json WaveHistory (turi kiekvienos bangos laiko žymas nuo 1 dienos) # Tai suteikia bazinį duomenų taškus, net jei nėra senų agregavimo aplankų $rolloutStatePaths = @( (Join-Path $parentDir "RolloutState\RolloutState.json"), (Sujungimo kelio $OutputPath "RolloutState\RolloutState.json") ) foreach ($rsPath in $rolloutStatePaths) { if (Test-Path $rsPath) { išbandykite { $rsData = Get-Content $rsPath -Žalias | KonvertuotiFrom-Json jei ($rsData.WaveHistory) { # Naudoti bangų pradžios datas kaip tendencijos duomenų taškus # Skaičiuoti kaupiamuosius įrenginius, nukreiptus į kiekvieną bangą $cumulativeTargeted = 0 foreach ($wave in $rsData.WaveHistory) { jei ($wave. StartedAt – ir $wave. DeviceCount) { $waveDate = ([datetime]$wave. 2014 m. 201 ToString("yyyy-MM-dd") $cumulativeTargeted += [sveikasis skaičius]$wave. Įrenginio skaičius jei (-not $dailyData.ContainsKey($waveDate)) { # Apytikslis: bangų paleidimo metu buvo atnaujinti tik įrenginiai iš ankstesnių bangų $dailyData[$waveDate] = [PSCustomObject]@{ Data = $waveDate; Iš viso = $c.Iš viso; Atnaujinta = [matematika]::Max(0, $cumulativeTargeted – [sveikasis skaičius]$wave. Įrenginio skaičius) NotUpdated = $c.Total – [matematika]::Max(0, $cumulativeTargeted – [sveikasis skaičius]$wave. Įrenginio skaičius) NeedsReboot = 0; Klaidos = 0; ActionRequired = 0 } } } } } } sugauti { } break # Use first found } }
jei ($dailyData.Count -gt 0) { $historyData = @($dailyData.GetEnumerator() | Sort-Object raktas | ForEach-Object { $_. Reikšmė }) Write-Host " Bootstrapped $($historyData.Count) duomenų taškai iš istorinių suvestinių" -ForegroundColor Green } }
# Įtraukti dabartinį duomenų tašką (dubliuoti pagal dieną – saugoti naujausią per dieną) $todayKey = (Gavimo data). ToString("yyyy-MM-dd") $existingToday = $historyData | Where-Object { "$($_. Date)" -like "$todayKey*" } jei ($existingToday) { # Pakeisti šiandienos įrašą $historyData = @($historyData | Where-Object { "$($_. Data) - nepatinka "$todayKey*" }) } $historyData += [PSCustomObject]@{ Data = $todayKey Iš viso = $c.Total Atnaujinta = $c.Updated NotUpdated = $stNotUptodate NeedsReboot = $c.NeedsReboot Klaidos = $c.WithErrors ActionRequired = $c.ActionReq } # Pašalinti blogus duomenų taškus (iš viso 0) ir išsaugoti 90 paskutinių $historyData = @($historyData | Where-Object { [int]$_. Iš viso -gt 0 }) # Be dangtelio – tendencijos duomenys yra ~100 baitų / įrašas, visi metai = ~36 KB $historyData | ConvertTo-Json gylis 3 | Set-Content $historyPath – utf8 kodavimas Write-Host " Tendencijos retrospektyva: $($historyData.Count) duomenų taškai" -Priekinio planopalva DarkGray # Html krypties diagramos duomenų kūrimas $trendLabels = ($historyData | ForEach-Object { "'$($_. Date)'" }) -join "," $trendUpdated = ($historyData | ForEach-Object { $_. Atnaujinta }) -join "," $trendNotUpdated = ($historyData | ForEach-Object { $_. NotUpdated }) - prisijungti "," $trendTotal = ($historyData | ForEach-Object { $_. Iš viso }) -join "," # Projekcija: plėsti tendencijos liniją naudojant eksponentinį padvigubėjimą (2,4,8,16...) # Gauna bangos dydį ir stebėjimo laikotarpį iš faktinių tendencijų istorijos duomenų.# - Bangos dydis = didžiausias vieno laikotarpio padidėjimas matomas istorijoje (naujausia banga įdiegta) # - Stebėjimo dienos = vidutinis kalendorinių dienų skaičius tarp tendencijos duomenų taškų (kaip dažnai naudojame) # Tada dvigubinti bangos dydis kiekvieną laikotarpį, atitinkančių orchestrator 2x augimo strategiją.$projLabels = ""; $projUpdated = ""; $projNotUpdated = ""; $hasProjection = $false jei ($historyData.Count -ge 2) { $lastUpdated = $c.Updated $remaining = $stNotUptodate # Tik SB-ON neatnaujintai įrenginiai (neįtraukia SecureBoot OFF) $projDates = @(); $projValues = @(); $projNotUpdValues = @() $projDate = Get-Date
# Iš tendencijos istorijos gauti bangos dydį ir stebėjimo laikotarpį $increments = @() $dayGaps = @() skirta ($hi = 1; $hi -lt $historyData.Count; $hi++) { $inc = $historyData[$hi]. Atnaujinta – $historyData[$hi-1]. Atnaujinta jei ($inc -gt 0) { $increments += $inc } išbandykite { $d 1 = [datetime]::P arse($historyData[$hi-1]. Data) $d 2 = [datetime]::P arse($historyData[$hi]. Data) $gap = ($d 2 – $d 1). TotalDays jei ($gap -gt 0) { $dayGaps += $gap } } sugauti {} } # Bangos dydis = naujausias teigiamas pokytis (dabartinė banga), atsarginis vidurkis, minimalus 2 $waveSize = if ($increments. Skaičiavimas -gt 0) { [matematika]:Max(2, $increments[-1]) } dar { 2 } # Stebėjimo laikotarpis = vidutinis tarpas tarp duomenų taškų (kalendorinės dienos bangai), mažiausiai 1 $waveDays = if ($dayGaps.Count -gt 0) { [matematika]::Max(1, [matematika]::Round(($dayGaps | Measure-Object -Average). Vidurkis, 0)) } dar { 1 }
Write-Host " Projekcija: waveSize=$waveSize (nuo paskutinio pokyčio), waveDays=$waveDays (avg gap from history)" -ForegroundColor DarkGray
$dayCounter = 0 # Rodyti projekciją, kol bus atnaujinti visi įrenginiai arba ne daugiau kaip 365 dienos skirta ($pi = 1; $pi -le 365; $pi++) { $projDate = $projDate.AddDays(1) $dayCounter++ # Kiekviename stebėjimo laikotarpio ribose įdiekite bangą, tada jei ($dayCounter -ge $waveDays) { $devicesThisWave = [matematika]:Min($waveSize, $remaining) $lastUpdated += $devicesThisWave $remaining – $devicesThisWave if ($lastUpdated -gt ($c.Updated + $stNotUptodate)) { $lastUpdated = $c.Updated + $stNotUptodate; $remaining = 0 } # Dvigubos bangos dydis kitą laikotarpį (orchestrator 2x strategija) $waveSize = $waveSize * 2 $dayCounter = 0 } $projDates += "'$($projDate.ToString("yyyy-MM-dd")") $projValues += $lastUpdated $projNotUpdValues += [matematika]::Maks.(0, $remaining) jei ($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 " Projekcija: reikia bent 2 tendencijos duomenų taškų, kad būtų galima gauti bangų laiką" - "ForegroundColor DarkGray" } # Kurkite sujungtas diagramos duomenų eilutes, skirtas čia esančiai eilutei $allChartLabels = if ($hasProjection) { "$trendLabels,$projLabels" } else { $trendLabels } $projDataJS = jei ($hasProjection) { $projUpdated } else { "" } $projNotUpdJS = jei ($hasProjection) { $projNotUpdated } else { "" } $histCount = ($historyData | Objektas Measure). Skaičius $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } -Descending | ForEach-Object { @{ vardas=$_. Raktas; iš viso = $_. Value.Total; atnaujinta= $_. Value.Updated; highConf = $_. Value.HighConf; actionReq = $_. Value.ActionReq } } | ConvertTo-Json gylis 3 | Set-Content (join-path $dataDir "manufacturers.json") – utf8 kodavimas # Konvertuoti JSON duomenų failus į CSV žmonėms skaitomuose "Excel" atsisiuntimuose Write-Host "Įrenginio duomenų konvertavimas į CSV, skirtą "Excel" atsisiųsti..." -Priekinio planopalva Pilka foreach ($dfName in $stDeviceFiles) { $jsonFile = Join-Path $dataDir "$dfName.json" $csvFile = Join-Path $OutputPath "SecureBoot_${dfName}_$timestamp.csv" if (Test-Path $jsonFile) { išbandykite { $jsonData = Get-Content $jsonFile -Žalias | KonvertuotiFrom-Json jei ($jsonData.Count -gt 0) { # Įtraukti papildomų stulpelių, skirtų update_pending CSV $selectProps = if ($dfName -eq "update_pending") { @("HostName", "WMI_Manufacturer", "WMI_Model", "BucketId", "ConfidenceLevel", "IsUpdated", "UEFICA2023Status", "UEFICA2023Error", "AvailableUpdatesPolicy", "WinCSKeyApplied", "SecureBootTaskStatus") } dar { @('HostName', 'WMI_Manufacturer', 'WMI_Model', 'BucketId', 'ConfidenceLevel', 'IsUpdated', 'UEFICA2023Error', 'SecureBootTaskStatus', 'KnownIssueId', 'SkipReasonKnownIssue') } $jsonData | Select-Object $selectProps | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8 Write-Host " $dfName - > $($jsonData.Count) eilutės -> CSV" -Priekinio planocolor DarkGray } } pagauti { Write-Host " $dfName - praleista" -Priekinio plano spalva DarkYellow } } } # Generuoti savarankišką HTML ataskaitų sritį $htmlPath = Join-Path $OutputPath "SecureBoot_Dashboard_$timestamp.html" Write-Host "Generuojamas vidinis HTML ataskaitų sritis..." - Priekinio planopalva Geltona # GREIČIO PROJEKCIJOS: skaičiavimas pagal nuskaitymo retrospektyvą arba ankstesnę suvestinę $stDeadline = [datetime]"2026-06-24" # KEK sertifikato galiojimo laikas $stDaysToDeadline = [matematika]:Max(0, ($stDeadline - (Get-Date)). Dienos) $stDevicesPerDay = 0 $stProjectedDate = $null $stVelocitySource = "N/A" $stWorkingDays = 0 $stCalendarDays = 0 # Pirmiausia pabandykite tendencijų istoriją (lengvas, jau prižiūrimas agreguotuvo – pakeičia ištinęs ScanHistory.json) jei ($historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Iš viso -gt 0 -and [int]$_. Atnaujinta -ge 0 }) jei ($validHistory.Count -ge 2) { $prev = $validHistory[-2]; $curr = $validHistory[-1] $prevDate = [datetime]::P arse($prev. Date.Substring(0, [Math]::Min(10, $prev. Data.ilgis))) $currDate = [datetime]::P arse($curr. Date.Substring(0, [Math]::Min(10, $curr. Data.ilgis))) $daysDiff = ($currDate – $prevDate). TotalDays jei ($daysDiff -gt 0) { $updDiff = [sveikasis skaičius]$curr. Updated – [int]$prev. Atnaujinta jei ($updDiff -gt 0) { $stDevicesPerDay = [matematika]::Round($updDiff / $daysDiff, 0) $stVelocitySource = "TrendHistory" } } } } # Pabandykite valdymo modulio naujinimo suvestinę (turi iš anksto apskaičiuojamo greičio) if ($stVelocitySource -eq "N/A" -and $RolloutSummaryPath -and (Test-Path $RolloutSummaryPath)) { išbandykite { $rolloutSummary = Get-Content $RolloutSummaryPath -Neapdorota | KonvertuotiFrom-Json jei ($rolloutSummary.DevicesPerDay -and [double]$rolloutSummary.DevicesPerDay -gt 0) { $stDevicesPerDay = [matematika]::Round([dvigubas]$rolloutSummary.DevicesPerDay, 1) $stVelocitySource = "Orchestrator" jei ($rolloutSummary.ProjectedCompletionDate) { $stProjectedDate = $rolloutSummary.ProjectedCompletionDate } jei ($rolloutSummary.WorkingDaysRemaining) { $stWorkingDays = [int]$rolloutSummary.WorkingDaysRemaining } jei ($rolloutSummary.CalendarDaysRemaining) { $stCalendarDays = [int]$rolloutSummary.CalendarDaysRemaining } } } sugauti { } } # Atsarginė: išbandykite ankstesnę suvestinę CSV (ieškoti dabartiniame aplanke IR pirminių / dukterinių elementų agregavimo aplankuose) jei ($stVelocitySource -eq "N/A") { $searchPaths = @( (Join-Path $OutputPath "SecureBoot_Summary_*.csv") ) # Taip pat ieškoti dukterinių agregavimo aplankų (valdymo modulis sukuria naują aplanką kiekvienam paleidimui) $parentPath = pirminis Split-Path $OutputPath jei ($parentPath) { $searchPaths += (sujungimo kelio $parentPath "Aggregation_*\SecureBoot_Summary_*.csv") $searchPaths += (sujungimo kelio $parentPath "SecureBoot_Summary_*.csv") } $prevSummary = $searchPaths | ForEach-Object { Get-ChildItem $_ -EA SilentlyContinue } | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 jei ($prevSummary) { išbandykite { $prevStats = Get-Content $prevSummary.FullName | ConvertFrom-Csv $prevDate = [datetime]$prevStats.ReportGeneratedAt $daysSinceLast = ((Gavimo data) – $prevDate). TotalDays jei ($daysSinceLast -gt 0,01) { $prevUpdated = [sveikasis skaičius]$prevStats.Updated $updDelta = $c.Updated – $prevUpdated jei ($updDelta -gt 0) { $stDevicesPerDay = [matematika]::Round($updDelta / $daysSinceLast, 0) $stVelocitySource = "PreviousReport" } } } sugauti { } } } # Atsarginė: apskaičiuoti greitį iš visos tendencijos istorijos span (pirmasis ir vėliausi duomenų taškas) jei ($stVelocitySource -eq "N/A" - ir $historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Iš viso -gt 0 -and [int]$_. Atnaujinta -ge 0 }) jei ($validHistory.Count -ge 2) { $first = $validHistory[0] $last = $validHistory[-1] $firstDate = [datetime]::P arse($first. Date.Substring(0, [Math]:Min(10, $first. Data.ilgis))) $lastDate = [datetime]::P arse($last. Date.Substring(0, [Math]::Min(10, $last. Data.ilgis))) $daysDiff = ($lastDate – $firstDate). TotalDays jei ($daysDiff -gt 0) { $updDiff = [sveikasis skaičius]$last. Atnaujinta – [sveikasis skaičius]$first. Atnaujinta jei ($updDiff -gt 0) { $stDevicesPerDay = [matematika]::Round($updDiff / $daysDiff, 1) $stVelocitySource = "TrendHistory" } } } } # Apskaičiuoti projekciją naudojant eksponentinį dvigubai daugiau (atitinka tendencijos diagramą) # Pakartotinai naudokite projekcijos duomenis, kurie jau yra apskaičiuoti diagramoje, jei yra jei ($hasProjection -and $projDates.Count -gt 0) { # Naudoti paskutinę projektuotą datą (kai visi įrenginiai atnaujinami) $lastProjDateStr = $projDates[-1] -replace "'", "" $stProjectedDate = ([datetime]::P arse($lastProjDateStr)). ToString("MMM dd, yyyy") $stCalendarDays = ([datetime]::P arse($lastProjDateStr) - (Get-Date)). Dienos $stWorkingDays = 0 $d = Gavimo data skirta ($i = 0; $i -lt $stCalendarDays; $i++) { $d = $d.AddDays(1) jei ($d.DayOfWeek -ne "Šeštadienis" - ir $d.DayOfWeek -ne "Sekmadienis") { $stWorkingDays++ } } } elseif ($stDevicesPerDay -gt 0 -and $stNotUptodate -gt 0) { # Atsarginė: linijinė projekcija, jei nėra eksponentinių duomenų $daysNeeded = [matematika]::Ceiling($stNotUptodate / $stDevicesPerDay) $stProjectedDate = (Gavimo data). AddDays($daysNeeded). ToString("MMM dd, yyyy") $stWorkingDays = 0; $stCalendarDays = $daysNeeded $d = Gavimo data skirta ($i = 0; $i -lt $daysNeeded; $i++) { $d = $d.AddDays(1) jei ($d.DayOfWeek -ne "Šeštadienis" - ir $d.DayOfWeek -ne "Sekmadienis") { $stWorkingDays++ } } } # Komponavimo versijos greičio HTML $velocityHtml = jei ($stDevicesPerDay -gt 0) { "<div><strong>🚀 Įrenginiai/Diena:</strong> $($stDevicesPerDay.ToString('N0')) (šaltinis: $stVelocitySource)</div>" + "<div><strong>📅 Numatomas užbaigimas:</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:'color:#28a745'>✓ Before deadline</span>" }) + "</div>" + "<div><strong>🕐 Darbo dienos:</strong> $stWorkingDays | <stiprios>Calendar dienos:</strong> $stCalendarDays</div>" + "<div style='font-size:.8em; color:#888'>Terminas: 2026 m. birželio 24 d. (KEK sertifikato galiojimo pabaiga) | Liko dienų: $stDaysToDeadline</ div>" } dar { "<div style='padding:8px; fonas:#fff3cd; kraštinės spindulys:4px; kraštinė kairėje:3px vientisas #ffc107'>" + "<stiprios>📅 Numatomas užbaigimas:</strong> Nepakanka duomenų greičio skaičiavimui. " + "Vykdykite agregavimą bent du kartus su duomenų pakeitimais, kad nustatytumėte normą.<br/>" + "<stiprus>Terminas:</strong> 2026 m. birželio 24 d. (KEK sertifikato galiojimo pabaiga) | <stipri>likusių dienų:</strong> $stDaysToDeadline</div>" } # Sertifikato galiojimo pabaigos skaičiavimas $certToday = Gavimo data $certKekExpiry = [datetime]"2026-06-24" $certUefiExpiry = [datetime]"2026-06-27" $certPcaExpiry = [datetime]"2026-10-19" $daysToKek = [matematika]:Max(0, ($certKekExpiry - $certToday). Dienos) $daysToUefi = [matematika]:Max(0, ($certUefiExpiry – $certToday). Dienos) $daysToPca = [matematika]:Max(0, ($certPcaExpiry – $certToday). Dienos) $certUrgency = if ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # padėjėjas: skaitykite įrašus iš JSON, komponavimo versijos talpyklos suvestinė + pirmos N įrenginio eilutės $maxInlineRows = 200 funkcija Build-InlineTable { param([eilutė]$JsonPath, [sveikasis skaičius]$MaxRows = 200, [eilutė]$CsvFileName = "") $bucketSummary = "" $deviceRows = "" $totalCount = 0 if (Test-Path $JsonPath) { išbandykite { $data = Get-Content $JsonPath -Neapdorota | KonvertuotiFrom-Json $totalCount = $data. Skaičius # BUCKET SUMMARY: Group by BucketId, show counts per bucket with Updated from global bucket stats jei ($totalCount -gt 0) { $buckets = $data | Group-Object BucketId | Sort-Object skaičius mažėjimo tvarka $bucketSummary = "><2 h3 style='font-size:.95em; spalva:#333; margin:10px 0 5px'><3 By Hardware Bucket ($($buckets. Skaičius) talpyklos)><4 /h3>" $bucketSummary += "><6 div style='max-height:300px; perpilda-y:automatinis; margin-bottom:15px'><table><thead><tr><th><5 BucketID><6 /th><th style='text-align:right'>Total</th><th style='text-align:right; color:#28a745'>Atnaujinta</th><th style='text-align:right; color:#dc3545'>Not Updated</th><th><1 Manufacturer><2 /th></tr></thead><tbody>" foreach ($b in $buckets) { $bid = if ($b.Name) { $b.Name } else { "(empty)" } $mfr = ($b.Group | Select-Object -First 1). WMI_Manufacturer # Gauti atnaujintą skaičių iš visuotinės talpyklos statistikos (visi šioje talpykloje esantys įrenginiai visame duomenų rinkinyje) $lookupKey = $bid $globalBucket = jei ($stAllBuckets.ContainsKey($lookupKey)) { $stAllBuckets[$lookupKey] } dar { $null } $bUpdatedGlobal = jei ($globalBucket) { $globalBucket.Updated } dar { 0 } $bTotalGlobal = jei ($globalBucket) { $globalBucket.Count } dar { $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; spalva: #28a745; font-weight:bold'>$bUpdatedGlobal><2 /td><td style='text-align:right; spalva: #dc3545; font-weight:bold'>$bNotUpdatedGlobal><6 /td><td><9 $mfr</td></tr>"n" } $bucketSummary += "</tbody></table></div>" } # ĮRENGINIO IŠSAMI INFORMACIJA: pirmos N eilutės kaip bazinis sąrašas $slice = $data | Select-Object – pirmasis $MaxRows foreach ($d in $slice) { $conf = $d.ConfidenceLevel $confBadge = if ($conf -match "High") { '<span class="badge badge-success">High Conf><2 /span>' } elseif ($conf -match "Not Sup") { '<span class="badge badge-danger">Not Supported><6 /span>' } elseif ($conf -match "Under") { '<span class="badge-info">Dalyje Obs><0 /span>' } elseif ($conf -match "Paused") { '<span class="badge badge-warning">Paused><4 /span>' } else { '<span class="badge badge-warning">Action Req><8 /span>' } $statusBadge = if ($d.IsUpdated) { '><00 span class="badge badge-success"><01 Updated</span>' } elseif ($d.UEFICA2023Error) { '><04 span class="badge badge-danger"><05 Error</span>' } else { '><08 span class="badge-warning"><09 Pending><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><td><5 $(if($d.UEFICA2023Error){$d.UEFICA2023Error}else{'-'})><36 /td><td style='font-size:.75em'><39 $($d.BucketId)><40 /td></tr><3 "n" } } sugauti { } } jei ($totalCount -eq 0) { return "><44 div style='padding:20px; spalva:#888; font-style:italic'><45 Šioje kategorijoje įrenginių nėra.><46 /div>" } $showing = [matematika]:Min($MaxRows, $totalCount) $header = "><48 div style='margin:5px 0; font-size:.85em; color:#666'><49 Iš viso: $($totalCount.ToString("N0")) įrenginiai" jei ($CsvFileName) { $header += " | ><50 a href='$CsvFileName' style='color:#1a237e; font-weight:bold'>📄 Atsisiųsti visą CSV, skirtą "Excel"><3 /a>" } $header += "><55 /div>" $deviceHeader = "><57 h3 style='font-size:.95em; spalva:#333; margin:10px 0 5px'><58 Device Details (showing first $showing)><59 /h3>" $deviceTable = "><61 div style='max-height:500px; perpilda-y:auto'><table><thead><tr><th th><0 HostName><1 /th><th><4 Manufacturer><5 /th><th><8 Model><9 /th><th><2 Confidence><3 /th><th><6 būsenos><7 /th><0><><1 /th><><4 BucketId><5 /th></tr></thead><tbody><2 $deviceRows><3 /tbody></table></div>" return "$header$bucketSummary$deviceHeader$deviceTable" } # Build inline tables from the JSON files already on disk, linking to CSV $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" # Laukiančių naujinimų pasirinktinė lentelė, įskaitant stulpelius UEFICA2023Status ir UEFICA2023Error $tblUpdatePending = "" $upJsonPath = Join-Path $dataDir "update_pending.json" if (Test-Path $upJsonPath) { išbandykite { $upData = Get-Content $upJsonPath -Raw | KonvertuotiFrom-Json $upCount = $upData.Count jei ($upCount -gt 0) { $upHeader = "<div style='margin:5px 0; font-size:.85em; color:#666'>Iš viso: $($upCount.ToString("N0")) įrenginiai | <a href='SecureBoot_update_pending_$timestamp.csv' style='color:#1a237e; font-weight:bold'>📄 Atsisiųsti visą CSV, skirtą "Excel"><4 /a></div>" $upRows = "" $upSlice = $upData | Select-Object – pirmasis $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:'color:#dc3545'>$($d.UEFICA2023Error)</span>" } else { '-' } $policyVal = jei ($d.AvailableUpdatesPolicy) { $d.AvailableUpdatesPolicy } dar { '-' } $wincsVal = if ($d.WinCSKeyApplied) { '<span class="badge badge-success">Taip><8 /span>' } dar { '-' } $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 = [matematika]:Min($maxInlineRows, $upCount) $upDevHeader = "<h3 style='font-size:.95em; spalva:#333; margin:10px 0 5px'>Device Details (showing first $upShowing)</h3>" $upTable = "<div style='max-height:500px; perpilda-y:auto'><lentelė><thead><tr><th><9 HostName><0 /th><><3 Manufacturer><4 /th><><7 Model><8 /th><th><1 UEFICA2023Status><2 /th><th><5 UEFICA 2023Error><6 /th><><9 strategijos</th><>WinCS key</th><th>BucketId</th></tr></thead><tbody><5 $upRows><6 /tbody></table></div>" $tblUpdatePending = "$upHeader$upDevHeader$upTable" } dar { $tblUpdatePending = "<div style='padding:20px; spalva:#888; font-style:italic'>Šioje kategorijoje įrenginių nėra.</div>" } } sugauti { $tblUpdatePending = "<div style='padding:20px; spalva:#888; font-style:italic'>Šioje kategorijoje įrenginių nėra.</div>" } } dar { $tblUpdatePending = "<div style='padding:20px; spalva:#888; font-style:italic'>Šioje kategorijoje įrenginių nėra.</div>" } # Sertifikato galiojimo pabaigos skaičiavimas $certToday = Gavimo data $certKekExpiry = [datetime]"2026-06-24" $certUefiExpiry = [datetime]"2026-06-27" $certPcaExpiry = [datetime]"2026-10-19" $daysToKek = [matematika]:Max(0, ($certKekExpiry - $certToday). Dienos) $daysToUefi = [matematika]:Max(0, ($certUefiExpiry – $certToday). Dienos) $daysToPca = [matematika]:Max(0, ($certPcaExpiry – $certToday). Dienos) $certUrgency = jei ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # Komponavimo versijos gamintojo diagramos duomenys įdėtieji (10 viršutinių pagal įrenginius skaičius) $mfrSorted = $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } -Descending | Select-Object – pirmas 10 $mfrChartTitle = if ($stMfrCounts.Count -le 10) { "By Manufacturer" } else { "10 geriausių gamintojų" } $mfrLabels = ($mfrSorted | ForEach-Object { "'$($_. Key)'" }) -join "," $mfrUpdated = ($mfrSorted | ForEach-Object { $_. Value.Updated }) - join "," $mfrUpdatePending = ($mfrSorted | ForEach-Object { $_. Value.UpdatePending }) - join "," $mfrHighConf = ($mfrSorted | ForEach-Object { $_. Value.HighConf }) - join "," $mfrUnderObs = ($mfrSorted | ForEach-Object { $_. Value.UnderObs }) - join "," $mfrActionReq = ($mfrSorted | ForEach-Object { $_. Value.ActionReq }) - join "," $mfrTempPaused = ($mfrSorted | ForEach-Object { $_. Value.TempPaused }) - join "," $mfrNotSupported = ($mfrSorted | ForEach-Object { $_. Value.NotSupported }) -join "," $mfrSBOff = ($mfrSorted | ForEach-Object { $_. Value.SBOff }) - join "," $mfrWithErrors = ($mfrSorted | ForEach-Object { $_. Value.WithErrors }) - join "," # Komponavimo versijos gamintojo lentelė $mfrTableRows = "" $stMfrCounts.GetEnumerator() | Sort-Object { $_. Value.Total } -Descending | ForEach-Object { $mfrTableRows += "<tr><td><7 $($_. Kodas)</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" } $htmlContent = @" <! DOCTYPE html> <html lang="en"> <galvos><3 <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <pavadinimas><9 saugiosios įkrovos sertifikato būsenos ataskaitų sritis><0 /title><1 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script><5 <stiliaus><7 *{box-sizing:border-box; paraštė:0; užpildymas:0} body{font-family:'Segoe UI',Tahoma,sans-serif; fonas:#f0f2f5; spalva:#333} .header{background:linear-gradient(135deg,#1a237e,#0d47a1); spalva: #fff; užpildymas:20px 30px} .header h1{font-size:1.6em; paraščių apačioje:5px} .header .meta{font-size:.85em; nepermatomumas:.9} .container{max-width:1400px; paraštė:0 automatinis; užpildymas:20px} .cards{display:grid; grid-template-columns:repeat(auto-fill,minmax(170px,1fr)); tarpas:12 px; paraštė:20px 0} .card{background:#fff; kraštinės spindulys:10px; užpildymas:15 px; lauko šešėlis: 0 2px 8px rgba(0,0,0,.08); kraštinė kairėje:4px vientisas #ccc;perėjimas:transformuoti .2s} .card:hover{transform:translateY(-2px); lauko šešėlis:0 4px 15px rgba(0,0,0,.12)} .card .value{font-size:1.8em; font-weight:700} .card .label{font-size:.8em; spalva:#666; margin-top:4px} .card .pct{font-size:.75em; spalva:#888} .section{background:#fff; kraštinės spindulys:10px; užpildymas:20px; paraštė:15px 0; lauko šešėlis:0 2px 8px rgba(0,0,0,.08)} .section h2{font-size:1.2em; spalva: #1a237e; margin-bottom:10px; žymiklis:žymiklis; user-select:none} .section h2:hover{text-decoration:underline} .section-body{display:none} .section-body.open{display:block} .charts{display:grid; grid-template-columns:1fr 1fr; tarpas: 20 px; paraštė:20px 0} .chart-box{background:#fff; kraštinės spindulys:10px; užpildymas:20px; lauko šešėlis:0 2px 8px rgba(0,0,0,.08)} lentelė{plotis:100 %; border-collapse:collapse; font-size:.85em} th{background:#e8eaf6; padding:8px 10px; teksto lygiuotė:kairėje; vieta:priklijuojamas; viršuje:0; z indeksas:1} td{padding:6px 10px; kraštinės apačioje:1px vientisas #eee} tr:hover{background:#f5f5f5} .badge{display:inline-block; padding:2px 8px;border-radius:10px; font-size:.75em; font-weight:700} .badge-success{background:#d4edda; spalva:#155724} .badge-danger{background:#f8d7da; spalva: #721c24} .badge-warning{background:#fff3cd; spalva:#856404} .badge-info{background:#d1ecf1; spalva: #0c5460} .top-link{float:right; font-size:.8em; spalva: #1a237e; text-decoration:none} .footer{text-align:center; užpildymas:20px; spalva:#999; font-size:.8em} {color:#1a237e} </style><9 </head> <kūno> <div class="header"> <h1>Secure Boot Certificate Status Dashboard</h1> <div class="meta">Sugeneruotas: $($stats. ReportGeneratedAt) | Iš viso įrenginių: $($c.Total.ToString("N0")) | Unikalios talpyklos: $($stAllBuckets.Count)</div><3 </div><5 <div class="container">
<!-- KPI kortelės – spustelėjamos, susietos su sekcijomis – > <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; viršuje: 8 px; dešinėje:8px; fonas:#dc3545; spalva: #fff; padding:1px 6px; kraštinės spindulys: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)% – NEEDS ACTION><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; viršuje: 8 px; dešinėje:8px; fonas:#28a745; spalva: #fff; padding:1px 6px; kraštinės spindulys:8px; font-size:.65em; font-weight:700">PRIMARY><8 /div><div class="value" style="color:#28a745">$($c.Updated.ToString("N0")</div><div class="label">Atnaujinta><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; viršuje: 8 px; dešinėje:8px; fonas:#6c757d; spalva: #fff; padding:1px 6px; kraštinės spindulys: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){[math]::Round(($c.SBOff/$c.Total)*100,1)}else{0})% – nepatenka į aprėptį><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){[math]::Round(($c.NeedsReboot/$c.Total)*100,1)}else{0})% – laukiama perkrovimo><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">Laukiama naujinimo</div >><div class="pct">$(if($c.Total -gt 0){[math]::Round(($c.UpdatePending/$c.Total)*100,1)}else{0})% – taikoma strategija / "WinCS", laukia atnaujinimo><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">(if($c.Total -gt 0){[math]::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)% – saugus><24 /div></a><27 <a 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 Under Observation><36 /div><div class ="pct"><9 $(if($c.Total -gt 0){[math]::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 Required><2 /div><div class="pct">$($stats. PercentActionRequired)% – turi išbandyti><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)% – panašus į nepavykusį><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){[math]::Round(($c.TaskDisabled/$c.Total)*100,1)}else{0})% – užblokuota><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. Pristabdyta</div><div class="pct">$(if($c.Total -gt 0){[math]::Round(($c.TempPaused/$c.Total)*100,1)}else{0})%</div></a> <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">Žinomos problemos><6 /div><div class="pct">$(if($c.Total -gt 0){[math]::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">Missing KEK</div><div class="pct">$(if($c.Total -gt 0){[math]::Round(($c.WithMissingKEK/$c.Total)*100,1)}else{0})%</div></a> <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 klaidos</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. Klaidos</div><div class="pct">$(if($c.Total -gt 0){[math]::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){[math]::Round(($c.PermFailures/$c.Total)*100,1)}else{0})%</div></a><3 </div>
<!-- diegimo greičio & sertifikato galiojimo pabaigos > <div id="s-velocity" style="display:grid; grid-template-columns:1fr 1fr; tarpas: 20 px; margin:15px 0"> <div class="section" style="margin:0"> <h2>📅 Diegimo greičio</h2> <div class="section-body open"> <div style="font-size:2.5em; font-weight:700; color:#28a745">$($c.Updated.ToString("N0"))</div> <div style="color:#666">įrenginiai atnaujinti iš $($c.Total.ToString("N0"))</div> <div style="margin:10px 0; fonas:#e8eaf6; aukštis: 20 px; kraštinės spindulys:10px; perpilda:paslėpta"><div style="background:#28a745; aukštis: 100 %; width:$($stats. PercentCertUpdated)%; border-radius:10px"></div></div> <div style="font-size:.8em; color:#888">$($stats. PercentCertUpdated)% complete</div> <div style="margin-top:10px; užpildymas:10px; fonas:#f8f9fa; kraštinės spindulys:8px; font-size:.85em"> <div><strong>Remaining:</strong> $($stNotUptodate.ToString("N0")) įrenginiams reikia imtis veiksmų</div> <div><strong>Blocking:</strong> $($c.WithErrors + $c.PermFailures + $c.TaskDisabledNotUpdated) įrenginiai (klaidos + nuolatinis + užduotis išjungta)</div> <div><strong>Safe to deploy:</strong> $($stSafeList.ToString("N0")) įrenginiai (ta pati talpykla kaip sėkminga)</div> $velocityHtml </div> </div> </div> <div class="section" style="margin:0; rėmelis kairėje:4px vientisas #dc3545"> <h2 style="color:#dc3545">⚠ Certificate Expiry Countdown</h2> <div class="section-body open"> <div style="display:flex; tarpas: 15 px; margin-top:10px"> <div style="text-align:center; užpildymas:15 px; kraštinės spindulys:8px; min-width:120px; fonas:linijinis gradientas(135deg,#fff5f5,#ffe0e0); kraštinė:2px vientisas #dc3545; lankstusis:1"> <div style="font-size:.65em; spalva: #721c24; teksto transformacija:didžiosios raidės; font-weight:bold">⚠ FIRST TO EXPIRE</div> ><4 div style="font-size:.85em; font-weight:bold; spalva: #dc3545; margin:3px 0"><5 KEK CA 2011</div> ><8 div id="daysKek" style="font-size:2.5em; font-weight:700; spalva: #dc3545; line-height:1"><9 $daysToKek</div> ><2 div style="font-size:.8em; color:#721c24"><3 d. (2026 m. birželio 24 d.)><4 /div> ><6 /div> ><8 div style="text-align:center; užpildymas:15 px; kraštinės spindulys:8px; min-width:120px; fonas:linijinis gradientas(135deg,#fffef5,#fff3cd); kraštinė:2px vientisas #ffc107; lankstusis:1"><9 <div style="font-size:.65em; spalva:#856404; teksto transformacija:didžiosios raidės; font-weight:bold">UEFI CA 2011</div> <div id="daysUefi" style="font-size:2.2em; font-weight:700; spalva:#856404; eilutės aukštis:1; margin:5px 0">$daysToUefi</div> <div style="font-size:.8em; color:#856404">d. (2026 m. birželio 27 d.)</div> </div> <div style="text-align:center; užpildymas:15 px; kraštinės spindulys:8px; min-width:120px; fonas:linijinis gradientas(135deg,#f0f8ff,#d4edff); kraštinė:2px vientisas #0078d4; nukrypimas:1"> <div style="font-size:.65em; spalva: #0078d4; teksto transformacija:didžiosios raidės; font-weight:bold">Windows PCA</div> <div id="daysPca" style="font-size:2.2em; font-weight:700; spalva: #0078d4; eilutės aukštis:1; margin:5px 0">$daysToPca><2 /div><3 <div style="font-size:.8em; color:#0078d4">d. (2026 m. spalio 19 d.)</div><7 </div><9 </div><1 <div style="margin-top:15px; užpildymas:10px; fonas:#f8d7da; kraštinės spindulys:8px; font-size:.85em; rėmelis kairėje:4px vientisas #dc3545"> <stiprios>⚠ CRITICAL:</strong> Visi įrenginiai turi būti atnaujinti prieš pasibaigiant sertifikato galiojimui. Įrenginiai, kurie iki nustatyto termino neatnaujinami, negali taikyti būsimų įkrovos tvarkytuvo ir saugiosios įkrovos saugos naujinimų pasibaigus galiojimo laikui.</div> </div> </div> </div>
<!-- diagramos --> <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>
$(jei ($historyData.Count -ge 1) { "<!-- retrospektyvinė tendencijos diagrama - > <div class='section'> <h2 onclick='"toggle('d-trend')'">📈 Naujinimo eiga per tam tikrą laiką <class='top-link' href='#'>↑ Top</a></h2> <div id='d-trend' class='section-body open'> <drobės id='trendChart' height='120'></canvas> <div style='font-size:.75em; spalva:#888; margin-top:5px'>Solid lines = actual data$(if ($historyData.Count -ge 2) { " | Brūkšninė linija = projekcijos taškas (eksponentinis dvigubinimas: 2→4→8→16... įrenginiai bangoje)" } dar { " | Dar kartą agregavimas bus vykdomas rytoj, kad pamatytumėte krypties linijas ir projekciją" })</div> </div> </div>" })
CSV<!-- atsisiuntimai – > <div class="section"> <h2 onclick="toggle('dl-csv')">📥 Atsisiųsti visus duomenis (CSV, skirtą "Excel") <a class="top-link" href="#">Top</a></h2><2 <div id="dl-csv" class="section-body open" style="display:flex; flex-wrap:wrap; tarpas:5px"> <a href="SecureBoot_not_updated_$timestamp.csv" style="display:inline-block; fonas:#dc3545; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">Not Updated ($($stNotUptodate.ToString("N0"))))</a><8 <a href="SecureBoot_errors_$timestamp.csv" style="display:inline-block; fonas:#dc3545; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">klaidos ($($c.WithErrors.ToString("N0"))))</a> <a href="SecureBoot_action_required_$timestamp.csv" style="display:inline-block; fonas:#fd7e14; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">Būtinas veiksmas ($($c.ActionReq.ToString("N0"))))</a> <a href="SecureBoot_known_issues_$timestamp.csv" style="display:inline-block; fonas:#dc3545; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">žinomos problemos ($($c.WithKnownIssues.ToString("N0"))</a> <a href="SecureBoot_task_disabled_$timestamp.csv" style="display:inline-block; fonas:#dc3545; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">Užduotis išjungta ($($c.TaskDisabled.ToString("N0"))))</a> <a href="SecureBoot_updated_devices_$timestamp.csv" style="display:inline-block; fonas:#28a745; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">Updated ($($c.Updated.ToString("N0"))))</a> <a href="SecureBoot_Summary_$timestamp.csv" style="display:inline-block; fonas:#6c757d; spalva: #fff; padding:6px 14px; kraštinės spindulys:5px; text-decoration:none; font-size:.8em">Suvestinės</a> <div style="width:100%; font-size:.75em; spalva:#888; margin-top:5px">CSV failus atidaryti programoje "Excel". Pasiekiama, kai išteklius nuomoja žiniatinklio serveris.</div> </div> </div>
<!-- gamintojo suskirstymas --> <div class="section"> <h2 onclick="toggle('mfr')">By Manufacturer <a class="top-link" href="#">Top</a></h2><1 <div id="mfr" class="section-body open"> <lentelė><><><1><gamintojas><2><><5 iš viso><6 /th><><9 atnaujinti><0><9 /th><th><3 High Confidence><4 /th><th><7 Action Required><8 /th></tr></thead><3 <tbody><5 $mfrTableRows><6 /tbody></table><9 </div><1 </div>
<!-- Įrenginių sekcijos (pirmieji 200 įdėtųjų ir CSV atsisiuntimų) --> <div class="section" id="s-err"> <h2 onclick="toggle('d-err')">🔴 Įrenginiai su klaidomis ($($c.WithErrors.ToString("N0"))) <a 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">🔴 Žinomos problemos ($($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')">🟠 Trūksta KEK – įvykis 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">🟠 Būtinas veiksmas ($($c.ActionReq.ToString("N0"))) <class="top-link" href="#">↑ Top><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">🔵 Dalyje Stebėjimas ($($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">🔴 Neatnaujinta ($($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">🔴 Užduotis išjungta ($($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">🔴 Laikinos klaidos ($($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">🔴 Nuolatinės triktys / nepalaikomos ($($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">⏳ Laukiančių naujinimų ($($c.UpdatePending.ToString("N0"))) – taikoma strategija / "WinCS", Laukiama naujinimo <class="top-link" href="#">↑ Top</a></h2> <div id="d-upd-pend" class="section-body"><p style="color:#666; margin-bottom:10px">Įrenginiai, kuriuose taikomas "AvailableUpdatesPolicy" arba "WinCS" raktas, bet UEFICA2023Status vis dar nepradėtas, "InProgress" arba neapibrėžtas.</p>$tblUpdatePending</div> </div> <div class="section" id="s-rip"> <h2 onclick="toggle('d-rip')" style="color:#17a2b8">🔵 Vykdomas diegimas ($($c.RolloutInProgress.ToString("N0"))) <a 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"))) - Nepatenka į aprėptį <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">🟢 Atnaujinti įrenginiai ($($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">🔄 Updated – reikia perkrauti ($($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 | Sugeneruota $($stats. ReportGeneratedAt) | StreamingMode | Peak Memory: ${stPeakMemMB} MB</div> </div><!-- /container -->
<scenarijaus> function toggle(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. Pristabdyta,'Nepalaikoma','SecureBoot OFF','Su klaidomis'],duomenų rinkiniai:[{data:[$($c.Updated),$($c.UpdatePending),$($c.HighConf ),$($c.UnderObs),$($c.ActionReq),$($c.TempPaused),$($c.NotSupported),$($c.SBOff),$($c.WithErrors)],backgroundColor:['#28a745','#6f42c1','#20c997','#17a2b8','#fd7e14','#6c757d','#721c24','#adb5bd','#dc3545']}},options:{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'},{ label:'Temp. Pristabdyta',duomenys:[$mfrTempPaused],backgroundColor:'#6c757d'},{label:'Not Supported',data:[$mfrNotSupported],backgroundColor:'#721c24'},{label:'SecureBoot OFF',data:[$mfrSBOff],backgroundColor:'#adb5bd'},{label:'With Errors',data:[$mfrWithErrors],backgroundColor:'#dc3545'}]},options:{responsive:true,scales:{x:{stacked:true},y:{stacked:true}},plugins:{legend:{position:'top'}}}); Retrospektyvinė tendencijos diagrama jei (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); jei (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} ]; Tarpai tarp jei (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'}); } nauja diagrama(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'}}},plugins:{legend:{position:'top'},title:{display:true,text:'Secure Boot Update Progress over Time'}}}}); } Dinaminis skaičiavimas (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,Math.ceil((k-t)/864e5)); if(du)du.textContent=Math.max(0,Math.ceil((u-t)/864e5)); if(dp)dp.textContent=Math.max(0,Math.ceil((p-t)/864e5))}))(); </script> </ kūno> </html> "@ [System.IO.File]::WriteAllText($htmlPath, $htmlContent, [System.Text.UTF8Encoding]::new($false)) # Visada laikykite stabilią "Naujausią" kopiją, kad administratoriai nereikėtų sekti laiko žymių $latestPath = Join-Path $OutputPath "SecureBoot_Dashboard_Latest.html" Copy-Item $htmlPath $latestPath –Force $stTotal = $streamSw.Elapsed.TotalSeconds # Įrašyti failo deklaraciją papildančiojo režimo režimui (spartusis be pakeitimų aptikimas kito vykdymo metu) jei ($IncrementalMode arba $StreamingMode) { $stManifestDir = Join-Path $OutputPath ".cache" jei (-not (testo kelias $stManifestDir)) { New-Item -ItemType Directory -Path $stManifestDir -Force | Out-Null } $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" $stNewManifest = @{} Write-Host "Įrašoma papildančiojo režimo failo deklaracija..." - Priekinio planopalva Pilka foreach ($jf in $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o") Dydis = $jf. Ilgis } } Save-FileManifest -Manifest $stNewManifest -Path $stManifestPath Write-Host " Įrašyta $($stNewManifest.Count) failų deklaracija" -ForegroundColor DarkGray } # SAUGOJIMO VALYMAS # Orchestrator pakartotinai naudojamas aplankas (Aggregation_Current): išsaugoti tik naujausią paleisti (1) # Administratorius rankiniai paleidimai / kiti aplankai: išsaugoti pastaruosius 7 veikia # Suvestinė CSV niekada nepanaikinami – jie mažyčiai (~1 KB) ir yra atsarginių kopijų šaltinis tendencijų retrospektyvai $outputLeaf = Split-Path $OutputPath -Leaf $retentionCount = jei ($outputLeaf -eq 'Aggregation_Current') { 1 } dar { 7 } # Failų priešdėliai saugūs valyti (efemeriškas momentines kopijas) $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_" ) # Raskite visas unikalias laiko žymas tik iš išvalytų failų $cleanableFiles = Get-ChildItem $OutputPath -File -EA SilentlyContinue | Where-Object { $f = $_. Vardas, pavadinimas; ($cleanupPrefixes | Where-Object { $f.StartsWith($_) }). Skaičiavimas -gt 0 } $allTimestamps = @($cleanableFiles | ForEach-Object { jei ($_. Name -match '(\d{8}-\d{6})') { $Matches[1] } } | Sort-Object -Unique -Descending) jei ($allTimestamps.Count -gt $retentionCount) { $oldTimestamps = $allTimestamps | Select-Object – praleisti $retentionCount $removedFiles = 0; $freedBytes = 0 foreach ($oldTs in $oldTimestamps) { foreach ($prefix in $cleanupPrefixes) { $oldFiles = Get-ChildItem $OutputPath -Failas -Filtras "${prefix}${oldTs}*" -EA SilentlyContinue foreach ($f in $oldFiles) { $freedBytes += $f.Ilgis Remove-Item $f.FullName -Force -EA SilentlyContinue $removedFiles++ } } } $freedMB = [matematika]::Round($freedBytes / 1MB, 1) Write-Host "Saugojimo valymas: pašalintas $removedFiles failų iš $($oldTimestamps.Count) senų paleidimų, atlaisvintas ${freedMB} MB (palikti paskutinį $retentionCount + visas suvestinės / NotUptodate CSV)" -Priekinio planocolor DarkGray } Write-Host "'n$("=" * 60)" -ForegroundColor Cyan Write-Host "STREAMING AGGREGATION COMPLETE" –priekinio planopalva žalia Write-Host ("=" * 60) - Priekinio planopalva žydra Write-Host " Iš viso įrenginių: $($c.Total.ToString("N0"))" -Priekinio planopalva balta Write-Host " NEATNAUJINTA: $($stNotUptodate.ToString("N0")) ($($stats. PercentNotUptodate)%)" -ForegroundColor $(if ($stNotUptodate -gt 0) { "Geltona" } dar { "Žalia" }) Write-Host " Atnaujinta: $($c.Updated.ToString("N0")) ($($stats. PercentCertUpdated)%)" - Priekinio planopalva Žalia Write-Host " Su klaidomis: $($c.WithErrors.ToString("N0"))" -ForegroundColor $(if ($c.WithErrors -gt 0) { "Red" } dar { "Žalia" }) Write-Host "Peak Memory: ${stPeakMemMB} MB" -ForegroundColor Cyan Write-Host " Laikas: $([matematika]::Round($stTotal/60,1)) min" -Priekinio planopalva balta Write-Host " Dashboard: $htmlPath" -ForegroundColor White return [PSCustomObject]$stats } #endregion SRAUTINIO PERDAVIMO REŽIMAS } dar { Write-Error "Įvesties kelias nerastas: $InputPath" išeiti iš 1 }