SVARĪGI Šis raksts, kurā ir ietverts šis skripta paraugs, ir novecojis. Sākot ar Windows atjauninājumiem, kas izlaisti 2026. gada 12. maijā un vēlāk, skripta paraugs atrodas jūsu ierīces mapē %systemroot%\SecureBoot\ExampleRolloutScripts .
Nokopējiet un ielīmējiet šo skripta paraugu un modificējiet pēc nepieciešamības savai videi:
<# . KOPSAVILKUMS Apkopo drošās palaišanas statusa JSON datus no vairākām ierīcēm kopsavilkuma atskaitēs.
. APRAKSTS Nolasa savāktos drošās palaišanas statusa JSON failus un ģenerē: - HTML informācijas panelis ar diagrammām un filtrēšanu - Kopsavilkums pēc ticamības līmeņa - Unikāla ierīces spaiņa analīze testēšanas stratēģijai Atbalsta: - Datora faili: HOSTNAME_latest.json (ieteicams) - Viens JSON fails Automātiski noņem dublikātus pēc resursdatora nosaukuma, saglabājot jaunāko CollectionTime. Pēc noklusējuma tiek iekļautas tikai ierīces ar ticamību "Darbības pieprasījums" vai "Augsta" lai koncentrētos uz izmantojamiem intervāliem. Izmantojiet -IncludeAllConfidenceLevels, lai ignorētu.
. PARAMETER InputPath Ceļš uz JSON failu(iem): - Mape: lasa visus *_latest.json failus (vai *.json, ja nav _latest failu) - Fails: Lasa vienu JSON failu
. PARAMETER OutputPath Ģenerēto atskaišu ceļš (noklusējums: .\SecureBootReports)
. PIEMĒRS # Apkopošana no mapes ar mašīnas failiem (ieteicams) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # Lasa: \\contoso\SecureBootLogs$\*_latest.json
. PIEMĒRS # Pielāgota izvades vieta .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -OutputPath "C:\Reports\SecureBoot"
. PIEMĒRS # Iekļaut tikai darbības rekvizītus un augstu ticamību (noklusējuma darbība) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" # Neietver: novērošana, pauzēts, netiek atbalstīts
. PIEMĒRS # Iekļaut visus ticamības līmeņus (ignorēt filtru) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeAllConfidenceLevels
. PIEMĒRS # Pielāgots ticamības līmeņa filtrs .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncludeConfidenceLevels @("Action Req", "High", "Observation")
. PIEMĒRS # UZŅĒMUMA MĒROGS: Inkrementālais režīms - tikai apstrādāt mainītos failus (ātri turpmākie darbi) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode # Pirmā palaišana: pilna slodze ~ 2 stundas 500K ierīcēm # Turpmākie braucieni: sekundes, ja nav izmaiņu, minūtes deltas
. PIEMĒRS # Izlaist HTML, ja nekas nav mainījies (ātrākais uzraudzībai) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -IncrementalMode -SkipReportIfUnchanged # Ja kopš pēdējās palaišanas neviens fails nav mainīts: ~5 sekundes
. PIEMĒRS # Tikai kopsavilkuma režīms - izlaidiet lielas ierīču tabulas (1-2 minūtes pret 20+ minūtēm) .\Aggregate-SecureBootData.ps1 -InputPath "\\contoso\SecureBootLogs$" -SummaryOnly # Ģenerē CSV, bet izlaiž HTML informācijas paneli ar pilnām ierīču tabulām
. PIEZĪMES Savienojiet pārī ar Detect-SecureBootCertUpdateStatus.ps1 izvietošanai uzņēmumos.Pilnu izvietošanas ceļvedi skatiet GPO-DEPLOYMENT-GUIDE.md. Noklusējuma darbība neietver novērošanas, pauzētas un neatbalstītas ierīces lai koncentrētos tikai uz izmantojamo ierīču kopām.#>
parametrs( [Parametrs(obligāts = $true)] [virkne]$InputPath, [Parametrs(obligāts = $false)] [virkne]$OutputPath = ".\SecureBootReports", [Parametrs(obligāts = $false)] [virkne]$ScanHistoryPath = ".\SecureBootReports\ScanHistory.json", [Parametrs(obligāts = $false)] [virkne]$RolloutStatePath, # Ceļš uz RolloutState.json, lai identificētu nepabeigtas ierīces [Parametrs(obligāts = $false)] [virkne]$RolloutSummaryPath, # ceļš uz SecureBootRolloutSummary.json no vadības moduļa (satur projekcijas datus) [Parametrs(obligāts = $false)] [virkne[]]$IncludeConfidenceLevels = @("Nepieciešama darbība", "Augsta ticamība"), # Iekļaut tikai šos ticamības līmeņus (noklusējums: tikai veicamie intervāli) [Parametrs(obligāts = $false)] [pārslēgt]$IncludeAllConfidenceLevels, # Pārrakstīt filtru, lai iekļautu visus ticamības līmeņus [Parametrs(obligāts = $false)] [pārslēgt]$SkipHistoryTracking, [Parametrs(obligāts = $false)] [slēdzis]$IncrementalMode, # Iespējot delta apstrādi — ielādējiet tikai mainītos failus kopš pēdējās palaišanas [Parametrs(obligāts = $false)] [virkne]$CachePath, # ceļš uz kešatmiņas direktoriju (noklusējums: OutputPath\.cache) [Parametrs(obligāts = $false)] [int]$ParallelThreads = 8, # Failu ielādes paralēlo pavedienu skaits (PS7+) [Parametrs(obligāts = $false)] [switch]$ForceFullRefresh, # Force full reload even in incremental mode [Parametrs(obligāts = $false)] [switch]$SkipReportIfUnchanged, # Izlaist HTML/CSV ģenerēšanu, ja faili nav mainīti (tikai izvades statistika) [Parametrs(obligāts = $false)] [switch]$SummaryOnly, # Ģenerējiet tikai kopsavilkuma statistiku (bez lielām ierīču tabulām) — daudz ātrāk [Parametrs(obligāts = $false)] [pārslēgt]$StreamingMode # Atmiņas efektīvais režīms: apstrādājiet gabalus, rakstiet CSV pakāpeniski, saglabājiet atmiņā tikai kopsavilkumus )
# Pašlabošana: noņemiet neredzamās unikoda rakstzīmes, ko injicē tīmekļa CMS, kopējot-ielīmējot no HTML rakstiem.# support.microsoft.com CMS ievada nulles platuma atstarpes (U+200B), nedalāmās atstarpes (U+00A0) un citas # Neredzamas rakstzīmes ap HTML tagiem šeit virknēs, izraisot PowerShell parsēšanas kļūdas.ja ($MyInvocation.MyCommand.Path) { $rawScript = [System.IO.File]::ReadAllText($MyInvocation.MyCommand.Path) if ($rawScript -match '[\u200B-\u200F\uFEFF]' -or $rawScript -match '\xA0') { Write-Host "BRĪDINĀJUMS: atrastas neredzamas unikoda rakstzīmes (iespējams, no tīmekļa kopēšanas un ielīmēšanas) - automātiskās tīrīšanas skripts..." -ForegroundColor dzeltena $cleaned = $rawScript -replace '[\u200B-\u200F\uFEFF]', '' $cleaned = $cleaned -replace '\xA0', ' [System.IO.File]::WriteAllText($MyInvocation.MyCommand.Path, $cleaned, [System.Text.UTF8Encoding]::new($false)) Write-Host "Skripts sekmīgi notīrīts. Atkārtota palaišana..." -Priekšplāna krāsa zaļa & $MyInvocation.MyCommand.Path @PSBoundParameters Iziet no $LASTEXITCODE } }
# Automātiski paaugstināt uz PowerShell 7, ja pieejams (6x ātrāk lielām datu kopām) ja ($PSVersionTable.PSVersion.Major -lt 7) { $pwshPath = Get-Command pwsh -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source Ja ($pwshPath) { Write-Host "Konstatēts PowerShell $($PSVersionTable.PSVersion) — atkārtota palaišana ar PowerShell 7 ātrākai apstrādei..." -ForegroundColor Dzeltena # Pārbūvēt argumentu sarakstu no saistītajiem parametriem $relaunchArgs = @('-NoProfile', '-ExecutionPolicy', 'Apeja', '-File', $MyInvocation.MyCommand.Path) foreach ($key $PSBoundParameters.Keys) { $val = $PSBoundParameters[$key] if ($val -is [slēdzis]) { Ja ($val. IsPresent) { $relaunchArgs += "-$key" } } elseif ($val -is [masīvs]) { $relaunchArgs += "-$key" $relaunchArgs += ($val -join ',') } citādi { $relaunchArgs += "-$key" $relaunchArgs += "$val" } } & $pwshPath @relaunchArgs Iziet no $LASTEXITCODE } }
$ErrorActionPreference = "Turpināt" $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $scanTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Deployment and monitoring samples"
# Piezīme: Šim skriptam nav atkarības no citiem skriptiem.# Lai iegūtu pilnu rīku komplektu, lejupielādējiet no: $DownloadUrl -> $DownloadSubPage
#region iestatīšana Write-Host "=" * 60 -ForegroundColor Cyan Write-Host "Secure Boot Data Aggregation" -ForegroundColor Cyan Write-Host "=" * 60 -ForegroundColor Cyan
# Izveidot izvades direktoriju if (-not (testa ceļš $OutputPath)) { New-Item -itemType directory -path $OutputPath -force | Out-Null }
# Load data - atbalsta CSV (mantoto) un JSON (vietējos) formātus Write-Host "'nDatu ielāde no: $InputPath" -ForegroundColor Yellow
# Palīga funkcija ierīces objekta normalizēšanai (roktura lauka nosaukumu atšķirības) Funkcija Normalize-DeviceRecord { PARAM($device) # Handle Hostname vs HostName (JSON izmanto Hostname, CSV izmanto HostName) Ja ($device. PSObject.Properties['Resursdatora nosaukums'] -and -not $device. PSObject.Properties['Resursdatora_nosaukums']) { $device | Add-Member -NotePropertyName 'Resursdatora_nosaukums' -NotePropertyValue $device. Resursdatora nosaukums -Force } # Handle Confidence vs ConfidenceLevel (JSON izmanto Confidence, CSV izmanto ConfidenceLevel) # ConfidenceLevel ir oficiālais lauka nosaukums - karte Confidence to Ja ($device. PSObject.Properties['Confidence'] -and -not $device. PSObject.Properties['ConfidenceLevel']) { $device | Add-Member -NotePropertyName 'ConfidenceLevel' -NotePropertyValue $device. Confidence -Force } # Atjauninājuma statusa izsekošana, izmantojot Event1808Count VAI UEFICA2023Status="Atjaunināts" # Tas ļauj izsekot, cik ierīču katrā ticamības intervālā ir atjauninātas $event 1808 = 0 Ja ($device. PSObject.Properties['Event1808Count']) { $event 1808 = [int]$device. Event1808Count } $uefiCaUpdated = $false Ja ($device. PSObject.Properties['UEFICA2023Status'] -un $device. UEFICA2023Status -eq "Atjaunināts") { $uefiCaUpdated = $true } IF ($event 1808 -gt; 0 -vai $uefiCaUpdated) { # Atzīmēt kā atjauninātu informācijas paneļa/izvēršanas loģikai, bet NEIGNORĒT ConfidenceLevel $device | Add-Member -NotePropertyName 'IsUpdated' -NotePropertyValue $true -Force } citādi { $device | Add-Member -NotePropertyName 'IsUpdated' -NotePropertyValue $false -force # ConfidenceLevel klasifikācija: # - "Augsta ticamība", "Novērošanā...", "Īslaicīgi apturēts...", "Netiek atbalstīts..." = lietot tādu, kāds tas ir # - Viss pārējais (null, tukšs, "UpdateType:...", "Nezināms", "N/A") = ietilpst darbībā Obligāts skaitītājos # Normalizēšana nav nepieciešama — to apstrādā straumēšanas skaitītāja else zars } # Handle OEMManufacturerName vs WMI_Manufacturer (JSON izmanto OEM*, mantotie lietojumi WMI_*) Ja ($device. PSObject.Properties['OEMManufacturerName'] -and -not $device. PSObject.Properties['WMI_Manufacturer']) { $device | Add-Member -NotePropertyName 'WMI_Manufacturer' -NotePropertyValue $device. OEMManufacturerName -Force } # Handle OEMModelNumber vs WMI_Model Ja ($device. PSObject.Properties['OEMModelNumber'] -and -not $device. PSObject.Properties['WMI_Model']) { $device | Add-Member -NotePropertyName 'WMI_Model' -NotePropertyValue $device. OEMModelNumber -Force } # Handle FirmwareVersion vs BIOSDescription Ja ($device. PSObject.Properties['FirmwareVersion'] -and -not $device. PSObject.Properties['BIOSDescription']) { $device | Add-Member -NotePropertyName 'BIOSDescription' -NotePropertyValue $device. FirmwareVersion -Force } atgriešanās $device }
#region Inkrementālā apstrāde / kešatmiņas pārvaldība # Kešatmiņas ceļu iestatīšana Ja (-not $CachePath) { $CachePath = Join-Path $OutputPath ".cache" } $manifestPath = Join-Path $CachePath "FileManifest.json" $deviceCachePath = Join-Path $CachePath "DeviceCache.json"
# Kešatmiņas pārvaldības funkcijas Funkcija Get-FileManifest { param([virkne]$Path) Ja (testa ceļš $Path) { Izmēģiniet { $json = Get-Content $Path -Raw | ConvertFrom-JSON # Konvertējiet PSObject uz hashtable (saderīgs ar PS5.1 - PS7 ir -AsHashtable) $ht = @{} $json. PSObject.Rekvizīti | ForEach-Object { $ht[$_. Vārds] = $_. Vērtība } atgriezties $ht } noķert { atgriezt @{} } } atgriezt @{} }
funkcija Save-FileManifest { param([hashtable]$Manifest, [virkne]$Path) $dir = Split-Path $Path -Parent if (-not (testa ceļš $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $Manifest | ConvertTo-Json - dziļums 3 - saspiešana | Set-Content $Path — spēks }
funkcija Get-DeviceCache { param([virkne]$Path) IF (Testa ceļš $Path) { Izmēģiniet { $cacheData = Get-Content $Path -Raw | ConvertFrom-JSON Write-Host " Ielādēta ierīces kešatmiņa: $($cacheData.Count) devices" -ForegroundColor DarkGray Atgriešanās $cacheData } noķert { Write-Host " Kešatmiņa bojāta, tiks pārbūvēta" -ForegroundColor Yellow atgriezt @() } } atgriezt @() }
funkcija Save-DeviceCache { parametrs($Devices, [virkne]$Path) $dir = Split-Path $Path -Parent if (-not (testa ceļš $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } # Konvertēt par masīvu un saglabāt $deviceArray = @($Devices) $deviceArray | ConvertTo-Json - dziļums 10 - saspiešana | Set-Content $Path — spēks Write-Host " Saglabātā ierīces kešatmiņa: $($deviceArray.Count) devices" -ForegroundColor DarkGray }
funkcija Get-ChangedFiles { parametrs( [System.IO.FileInfo[]]$AllFiles, [jaucējtabula]$Manifest ) $changed = [System.Collections.ArrayList]::new() $unchanged = [System.Collections.ArrayList]::new() $newManifest = @{} # Izveidot reģistrjutīgu uzmeklēšanu no manifesta (normalizēt uz mazajiem burtiem) $manifestLookup = @{} foreach ($mk $Manifest.Keys) { $manifestLookup[$mk. ToLowerInvariant()] = $Manifest[$mk] } foreach ($file $AllFiles) { $key = $file. FullName.ToLowerInvariant() # Normalizēt ceļu uz mazajiem burtiem $lwt = $file. LastWriteTimeUtc.ToString("o") $newManifest[$key] = @{ LastWriteTimeUtc = $lwt Lielums = $file. Garums } ja ($manifestLookup.ContainsKey($key)) { $cached = $manifestLookup[$key] Ja ($cached. LastWriteTimeUtc -eq $lwt -and $cached. Izmērs -eq $file. garums) { [Tukšs]$unchanged. Add($file) turpināt } } [Anulēts]$changed. Add($file) } atgriezt @{ Mainīts = $changed Nemainīts = $unchanged NewManifest = $newManifest } }
# Īpaši ātra paralēla failu ielāde, izmantojot pakešu apstrādi Funkcija Load-FilesParallel { parametrs( [System.IO.FileInfo[]]$Files, [int]$Threads = 8 )
$totalFiles = Files $. Skaits # Izmantojiet ~ 1000 failu partijas, lai labāk kontrolētu atmiņu $batchSize = [math]::Min(1000, [math]::Ceiling($totalFiles / [math]::Max(1, $Threads))) $batches = [System.Collections.Generic.List[object]]::new()
for ($i = 0; $i -lt $totalFiles; $i += $batchSize) { $end = [matemātika]::Min($i + $batchSize, $totalFiles) $batch = $Files[$i.. ($end-1)] $batches. Add($batch) } Write-Host " ($($batches. Count) ~$batchSize failu paketes)" -NoNewline -ForegroundColor DarkGray $flatResults = [System.Collections.Generic.List[object]]::new() # Pārbaudiet, vai ir pieejams PowerShell 7+ paralēlais $canParallel = $PSVersionTable.PSVersion.Major -ge 7 IF ($canParallel -and $Threads -gt; 1) { # PS7+: apstrādājiet partijas paralēli $results = $batches | ForEach-Object -ThrottleLimit $Threads -parallel { $batchFiles = $_ $batchResults = [System.Collections.Generic.List[object]]::new() foreach ($file $batchFiles) { Izmēģiniet { $content = [System.IO.File]::ReadAllText($file. Vārds) | ConvertFrom-JSON $batchResults.Add($content) } nozveja { } } $batchResults.ToArray() } foreach ($batch $results) { if ($batch) { foreach ($item in $batch) { $flatResults.Add($item) } } } } citādi { # PS5.1 atkāpšanās: secīga apstrāde (joprojām ātra <10K failiem) foreach ($file Files dolāros) { Izmēģiniet { $content = [System.IO.File]::ReadAllText($file. Vārds) | ConvertFrom-JSON $flatResults.Add($content) } nozveja { } } } return $flatResults.ToArray() } #endregion
$allDevices = @() IF (Testa ceļa $InputPath CeļaTipa lapa) { # Viens JSON fails IF ($InputPath -like "*.json") { $jsonContent = Get-Content -Path $InputPath -Raw | ConvertFrom-JSON $allDevices = @($jsonContent) | ForEach-Object { Normalize-DeviceRecord $_ } Write-Host "Ielādēti $($allDevices.Count) ieraksti no faila" } citādi { Write-Error "Tiek atbalstīts tikai JSON formāts. Failam ir jābūt .json paplašinājumam." Izeja 1 } } elseif (testa ceļa $InputPath konteiners) { # Mape — tikai JSON $jsonFiles = @(Get-ChildItem -Path $InputPath -filter "*.json" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_. Nosaukums -notmatch "ScanHistory|Izvēršanas valsts|RolloutPlan" }) # Dod priekšroku *_latest.json failiem, ja tādi pastāv (datora režīms) $latestJson = $jsonFiles | Where-Object { $_. Vārds, līdzīgs "*_latest.json" } if ($latestJson.Count -gt; 0) { $jsonFiles = $latestJson } $totalFiles = $jsonFiles.Count IF ($totalFiles -EQ 0) { Write-Error "Nav atrasts neviens JSON fails: $InputPath" Izeja 1 } Write-Host "Atrasts $totalFiles JSON failiem" -ForegroundColor Grey # Palīga funkcija, lai atbilstu ticamības līmeņiem (apstrādā gan īsās, gan pilnās formas) # Definēts agrīnā stadijā, lai to varētu izmantot gan StreamingMode, gan parastie ceļi Funkcija Test-ConfidenceLevel { parametrs([virkne]$Value, [virkne]$Match) if ([virkne]::IsNullOrEmpty($Value)) { return $false } slēdzis ($Match) { "HighConfidence" { return $Value -eq "Augsta ticamība" } "UnderObservation" { return $Value -like "Under Observation*" } "ActionRequired" { return ($Value -like "*Action Required*" -or $Value -eq "Nepieciešamā darbība") } "Īslaicīgi pauzēts" { return $Value -like "Īslaicīgi pauzēts*" } "NotSupported" { return ($Value -like "Not Supported*" -or $Value -eq "Not Supported") } noklusējums { return $false } } } #region STRAUMĒŠANAS REŽĪMS — atmiņas efektīva apstrāde lielām datu kopām # Vienmēr izmantojiet StreamingMode atmiņas efektīvai apstrādei un jauna stila informācijas panelim Ja (-not $StreamingMode) { Write-Host "StreamingMode automātiska iespējošana (jaunā stila informācijas panelis)" -ForegroundColor Yellow $StreamingMode = $true if (-not $IncrementalMode) { $IncrementalMode = $true } } # Ja ir iespējots -StreamingMode, apstrādājiet failus pa gabaliem, saglabājot atmiņā tikai skaitītājus.# Ierīces līmeņa dati tiek ierakstīti JSON failos katru gabalu, lai tos pēc pieprasījuma ielādētu informācijas panelī.# Atmiņas lietojums: ~1,5 GB neatkarīgi no datu kopas lieluma (salīdzinājumā ar 10-20 GB bez straumēšanas).Ja ($StreamingMode) { Write-Host "STRAUMĒŠANAS REŽĪMS iespējots — efektīva apstrāde atmiņā" — ForegroundColor Green $streamSw = [System.Diagnostics.Chronowatch]::StartNew() # PAKĀPENISKA PĀRBAUDE: ja kopš pēdējās palaišanas neviens fails nav mainīts, pilnībā izlaidiet apstrādi IF ($IncrementalMode -and -not $ForceFullRefresh) { $stManifestDir = Join-Path $OutputPath ".cache" $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" IF (Testa ceļš $stManifestPath) { Write-Host "Pārbaude, vai nav izmaiņu kopš pēdējās straumēšanas palaišanas..." -ForegroundColor Cyan $stOldManifest = Get-FileManifest -Path $stManifestPath IF ($stOldManifest.Count, -gt; 0) { $stChanged = $false # Ātrā pārbaude: vienāds failu skaits? ja ($stOldManifest.Count -eq $totalFiles) { # Pārbaudiet 100 jaunākos failus (sakārtoti pēc LastWriteTime dilstošā secībā) # Ja kāds fails ir mainīts, tam būs visjaunākais laikspiedols un tas tiks rādīts pirmais $sampleSize = [matemātika]::Min(100, $totalFiles) $sampleFiles = $jsonFiles | Sort-Object LastWriteTimeUtc -dilstošā secībā | Select-Object -First $sampleSize foreach ($sf $sampleFiles) { $sfKey = $sf. FullName.ToLowerInvariant() if (-not $stOldManifest.ContainsKey($sfKey)) { $stChanged = $true pārtraukums } # Salīdzināt laikspiedolus — kešatmiņa var būt DateTime vai virkne pēc JSON turp un atpakaļ $cachedLWT = $stOldManifest[$sfKey]. LastWriteTimeUtc $fileDT = $sf. LastWriteTimeUtc Izmēģiniet { # Ja kešatmiņā jau ir DateTime (ConvertFrom-Json automātiskie konvertējumi), izmantojiet tieši if ($cachedLWT -is [DateTime]) { $cachedDT = $cachedLWT.ToUniversalTime() } citādi { $cachedDT = [DateTimeOffset]::P arse("$cachedLWT"). UtcDateTime } if ([math]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt; 1) { $stChanged = $true pārtraukums } } noķert { $stChanged = $true pārtraukums } } } citādi { $stChanged = $true } Ja (-not $stChanged) { # Pārbaudiet, vai ir izvades faili $stSummaryExists = Get-ChildItem (pievienošanās ceļš $OutputPath "SecureBoot_Summary_*.csv") -EA SilentlyContinue | Select-Object -Pirmais 1 $stDashExists = Get-ChildItem (pievienošanās ceļš $OutputPath "SecureBoot_Dashboard_*.html") -EA SilentlyContinue | Select-Object -Pirmais 1 IF ($stSummaryExists -and $stDashExists) { Write-Host " Izmaiņas nav konstatētas ($totalFiles faili netiek mainīti) - izlaiž apstrādi" - ForegroundColor Green Write-Host " Pēdējais informācijas panelis: $($stDashExists.FullName)" -ForegroundColor White $cachedStats = Get-Content $stSummaryExists.Vārds_Uzvārds | ConvertFrom-CSV Write-Host " Ierīces: $($cachedStats.TotalDevices) | Atjaunināts: $($cachedStats.Atjaunināts) | Kļūdas: $($cachedStats.WithErrors)" -ForegroundColor Grey Write-Host " Completed in $([math]::Round($streamSw.Elapsed.TotalSeconds, 1))s (no processing needed)" -ForegroundColor Green Atgriešanās $cachedStats } } citādi { # DELTA PATCH: Atrodiet, kuri faili ir mainīti Write-Host " Noteiktas izmaiņas — veikto failu identificēšana..." -ForegroundColor Yellow $changedFiles = [System.Collections.ArrayList]::new() $newFiles = [System.Collections.ArrayList]::new() foreach ($jf $jsonFiles) { $jfKey = $jf. FullName.ToLowerInvariant() if (-not $stOldManifest.ContainsKey($jfKey)) { [void]$newFiles.Add($jf) } citādi { $cachedLWT = $stOldManifest[$jfKey]. LastWriteTimeUtc $fileDT = $jf. LastWriteTimeUtc Izmēģiniet { $cachedDT = if ($cachedLWT -is [DateTime]) { $cachedLWT.ToUniversalTime() } else { [DateTimeOffset]::P arse("$cachedLWT"). UtcDateTime } if ([math]::Abs(($cachedDT - $fileDT). TotalSeconds) -gt; 1) { [void]$changedFiles.Add($jf) } } catch { [void]$changedFiles.Add($jf) } } } $totalChanged = $changedFiles.Count + $newFiles.Count $changePct = [matemātika]::Round(($totalChanged / $totalFiles) * 100, 1) Write-Host " Mainīts: $($changedFiles.Count) | Jauns: $($newFiles.Count) | Kopsumma: $totalChanged ($changePct%)" -ForegroundColor Yellow Ja ($totalChanged -gt; 0 -and $changePct -lt, 10) { # DELTA IELĀPU REŽĪMS: <10% mainīts, ielāpēt esošos datus Write-Host " Delta ielāpu režīms ($changePct% < 10%) - $totalChanged failu ielāpi..." -ForegroundColor Green $dataDir = Join-Path $OutputPath "dati" # Ielādēt mainītas/jaunas ierīces ierakstus $deltaDevices = @{} $allDeltaFiles = @($changedFiles) + @($newFiles) foreach ($df $allDeltaFiles) { Izmēģiniet { $devData = Get-Content $df. Vārds -Neapstrādāts | ConvertFrom-JSON $dev = Normalize-DeviceRecord $devData Ja ($dev. Resursdatora nosaukums) { $deltaDevices[$dev. Resursdatora nosaukums] = $dev } } nozveja { } } Write-Host " Ielādēti $($deltaDevices.Count) mainīti ierīču ieraksti" -ForegroundColor Grey # Katrai kategorijai JSON: dzēst vecos ierakstus mainītajiem resursdatoru nosaukumiem, pievienot jaunus ierakstus $categoryFiles = @("kļūdas", "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[virkne]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($hn $deltaDevices.Keys) { [void]$changedHostnames.Add($hn) } foreach ($cat $categoryFiles) { $catPath = Join-Path $dataDir "$cat.json" ja (testa ceļš $catPath) { Izmēģiniet { $catData = Get-Content $catPath -Raw | ConvertFrom-JSON # Veco ierakstu noņemšana mainītajiem resursdatoru nosaukumiem $catData = @($catData | Where-Object { -not $changedHostnames.Contains($_. Resursdatora nosaukums) }) # Pārklasificējiet katru mainīto ierīci kategorijās # (tiks pievienots zemāk pēc klasifikācijas) $catData | ConvertTo-Json — 5. dziļums | Set-Content $catPath — UTF8 kodēšana } nozveja { } } } # Klasificējiet katru mainīto ierīci un pievienojiet pareizās kategorijas failus foreach ($dev $deltaDevices.Values) { $slim = [pasūtīts]@{ Resursdatora nosaukums = $dev. Resursdatora nosaukums WMI_Manufacturer = ja ($dev. PSObject.Properties['WMI_Manufacturer']) { $dev. WMI_Manufacturer } else { "" } WMI_Model = ja ($dev. PSObject.Properties['WMI_Model']) { $dev. WMI_Model } else { "" } BucketId = ja ($dev. PSObject.Properties['BucketId']) { $dev. BucketId } else { "" } ConfidenceLevel = ja ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } else { "" } Atjaunināts = $dev. Atjaunināts UEFICA2023Error = ja ($dev. PSObject.Properties['UEFICA2023Error']) { $dev. UEFICA2023Error } else { $null } SecureBootTaskStatus = ja ($dev. PSObject.Properties['SecureBootTaskStatus']) { $dev. SecureBootTaskStatus } citādi { "" } KnownIssueId = ja ($dev. PSObject.Properties['KnownIssueId']) { $dev. KnownIssueId } else { $null } SkipReasonKnownIssue = ja ($dev. PSObject.Properties['SkipReasonKnownIssue']) { $dev. SkipReasonKnownIssue } else { $null } } $isUpd = $dev. IsUpdated -eq $true $conf = ja ($dev. PSObject.Properties['ConfidenceLevel']) { $dev. ConfidenceLevel } else { "" } $hasErr = (-not [virkne]::IsNullOrEmpty($dev. UEFICA2023Error) un $dev. UEFICA2023Kļūda -ne "0" -and $dev. UEFICA2023Error -ne "") $tskDis = ($dev. SecureBootTaskEnabled -eq $false -vai $dev. SecureBootTaskStatus -eq 'Atspējots' -vai $dev. SecureBootTaskStatus -eq 'NotFound') $tskNF = ($dev. SecureBootTaskStatus -eq 'NotFound') $sbOn = ($dev. SecureBootEnabled -ne $false -and "$($dev. SecureBootEnabled)" -ne "Aplams") $e 1801 = ja ($dev. PSObject.Properties['Event1801Count']) { [int]$dev. Event1801Count } else { 0 } $e 1808 = ja ($dev. PSObject.Properties['Event1808Count']) { [int]$dev. Event1808Count } else { 0 } $e 1803 = ja ($dev. PSObject.Properties['Event1803Count']) { [int]$dev. Event1803Count } else { 0 } $mKEK = ($e 1803 -gt; 0 -vai $dev. MissingKEK -eq $true) $hKI = ((-not [virkne]::IsNullOrEmpty($dev. SkipReasonKnownIssue)) -or (-not [virkne]::IsNullOrEmpty($dev. KnownIssueId))) $rStat = ja ($dev. PSObject.Properties['RolloutStatus']) { $dev. RolloutStatus } else { "" } # Pievienošana atbilstošajiem kategoriju failiem $targets = @() IF ($isUpd) { $targets += "updated_devices" } IF ($hasErr) { $targets += "kļūdas" } IF ($hKI) { $targets += "known_issues" } IF ($mKEK) { $targets += "missing_kek" } if (-not $isUpd -and $sbOn) { $targets += "not_updated" } IF ($tskDis) { $targets += "task_disabled" } if (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPause'))) { $targets += "temp_failures" } if (-not $isUpd -and ((Test-ConfidenceLevel $conf 'NotSupported') -or ($tskNF -and $hasErr))) { $targets += "perm_failures" } if (-not $isUpd -and (Test-ConfidenceLevel $conf 'ActionRequired')) { $targets += "action_required" } if (-not $sbOn) { $targets += "secureboot_off" } ja ($e 1801 -gt; 0 -and $e 1808 -eq 0 -and -not $hasErr -and $rStat -eq "InProgress") { $targets += "rollout_inprogress" } foreach ($tgt $targets) { $tgtPath = Join-Path $dataDir "$tgt.json" ja (testa ceļš $tgtPath) { $existing = Get-Content $tgtPath -Raw | ConvertFrom-JSON $existing = @($existing) + @([PSCustomObject]$slim) $existing | ConvertTo-Json — dziļums 5 | Set-Content $tgtPath — UTF8 kodēšana } } } # CSV failu pārģenerēšana no ielāpētiem JSON Write-Host " Regenerating CSV from patched data..." -ForegroundColor Grey $newTimestamp = Get-Date -Format "yyyyMMdd-HHmmss" foreach ($cat $categoryFiles) { $catJsonPath = Join-Path $dataDir "$cat.json" $catCsvPath = Join-Path $OutputPath "SecureBoot_${cat}_$newTimestamp.csv" Ja (testa ceļš $catJsonPath) { Izmēģiniet { $catJsonData = Get-Content $catJsonPath -Raw | ConvertFrom-JSON ja ($catJsonData.Count -gt; 0) { $catJsonData | Export-Csv -Path $catCsvPath -NoTypeInformation -Encoding UTF8 } } nozveja { } } } # Uzskaitiet statistiku no ielāpētajiem JSON failiem Write-Host " Kopsavilkuma pārrēķināšana no ielāpotiem datiem..." -ForegroundColor Grey $patchedStats = [pasūtīts]@{ 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 $categoryFiles) { $catPath = Join-Path $dataDir "$cat.json" $cnt = 0 if (Testa ceļš $catPath) { try { $cnt = (Get-Content $catPath -Raw | ConvertFrom-Json). Count } nozveja { } } slēdzis ($cat) { "updated_devices" { $pUpdated = $cnt } "kļūdas" { $pErrors = $cnt } "known_issues" { $pKI = $cnt } "missing_kek" { $pKEK = $cnt } "not_updated" { } # aprēķināts "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 (pievienošanās ceļš $dataDir "not_updated.json") -Raw | ConvertFrom-Json). Skaits $pTotal = $pUpdated + $pNotUpdated + $pSBOff Write-Host " Delta ielāps pabeigts: $totalChanged ierīces atjauninātas" -ForegroundColor Green Write-Host " Kopā: $pTotal | Atjaunināts: $pUpdated | Nav atjaunināts: $pNotUpdated | Kļūdas: $pErrors" -ForegroundColor White # Atjaunināšanas manifests $stManifestDir = Join-Path $OutputPath ".cache" $stNewManifest = @{} foreach ($jf $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o"); Lielums = $jf. Garums } } Save-FileManifest -manifest $stNewManifest -path $stManifestPath Write-Host " Completed in $([math]::Round($streamSw.Elapsed.TotalSeconds, 1))s (delta patch - $totalChanged devices)" -ForegroundColor Green # Pāriet līdz pilnai straumēšanas pārstrādei, lai pārģenerētu HTML informācijas paneli # Datu faili jau ir ielāpi, tāpēc tas nodrošina informācijas paneļa pašreizējo izskatu Write-Host " Informācijas paneļa reģenerēšana no ielāpētiem datiem..." -ForegroundColor Yellow } citādi { Write-Host " $changePct% mainīti faili (>= 10%) - nepieciešama pilna straumēšanas atkārtota apstrāde" -ForegroundColor Yellow } } } } } # Datu apakšdirektorija izveide pieprasījuma ierīces JSON failiem $dataDir = Join-Path $OutputPath "dati" if (-not (testa ceļš $dataDir)) { New-Item -itemtype directory -path $dataDir -force | Out-Null } # Dedublikācija, izmantojot HashSet (O(1) katrai uzmeklēšanai, ~50MB 600K resursdatoru nosaukumiem) $seenHostnames = [System.Collections.Generic.HashSet[virkne]]::new([System.StringComparer]::OrdinalIgnoreCase) # Viegls kopsavilkuma skaitītājs (aizstāj $allDevices + $uniqueDevices atmiņā) $c = @{ Kopā = 0; SBEnabled = 0; SBOff = 0 Atjaunināts = 0; HighConf = 0; UnderObs = 0; ActionReq = 0; TempPaused = 0; Neatbalstīts = 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 intervāla izsekošana (vieglās kopas) $stFailedBuckets = [System.Collections.Generic.HashSet[virkne]]::new() $stSuccessBuckets = [System.Collections.Generic.HashSet[virkne]]::new() $stAllBuckets = @{} $stMfrCounts = @{} $stErrorCodeCounts = @{}; $stErrorCodeSamples = @{} $stKnownIssueCounts = @{} # Pakešveida režīma ierīces datu faili: uzkrājas par gabalu, izskalojiet pie bloku robežām $stDeviceFiles = @("kļūdas", "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 $stDeviceFiles) { $dfPath = Join-Path $dataDir "$dfName.json" [System.IO.File]::WriteAllText($dfPath, "['n", [System.Text.Encoding]::UTF8) $stDeviceFilePaths[$dfName] = $dfPath; $stDeviceFileCounts[$dfName] = 0 } # Slim ierīces ieraksts JSON izvadei (tikai svarīgākie lauki, ~200 baiti vs ~2KB pilni) Funkcija Get-SlimDevice { PARAM($Dev) return [ordered]@{ Resursdatora nosaukums = $Dev.Resursdatora nosaukums WMI_Manufacturer = if ($Dev.PSObject.Properties['WMI_Manufacturer']) { $Dev.WMI_Manufacturer } else { "" } WMI_Model = if ($Dev.PSObject.Properties['WMI_Model']) { $Dev.WMI_Model } else { "" } BucketId = if ($Dev.PSObject.Properties['BucketId']) { $Dev.BucketId } else { "" } ConfidenceLevel = if ($Dev.PSObject.Properties['ConfidenceLevel']) { $Dev.ConfidenceLevel } else { "" } IsUpdated = $Dev.IsUpdated UEFICA2023Error = if ($Dev.PSObject.Properties['UEFICA2023Error']) { $Dev.UEFICA2023Error } else { $null } SecureBootTaskStatus = if ($Dev.PSObject.Properties['SecureBootTaskStatus']) { $Dev.SecureBootTaskStatus } else { "" } KnownIssueId = if ($Dev.PSObject.Properties['KnownIssueId']) { $Dev.KnownIssueId } else { $null } SkipReasonKnownIssue = if ($Dev.PSObject.Properties['SkipReasonKnownIssue']) { $Dev.SkipReasonKnownIssue } else { $null } UEFICA2023Status = if ($Dev.PSObject.Properties['UEFICA2023Status']) { $Dev.UEFICA2023Status } else { $null } AvailableUpdatesPolicy = if ($Dev.PSObject.Properties['AvailableUpdatesPolicy']) { $Dev.AvailableUpdatesPolicy } else { $null } WinCSKeyApplied = if ($Dev.PSObject.Properties['WinCSKeyApplied']) { $Dev.WinCSKeyApplied } else { $null } } } # Iztīrīt paketi uz JSON failu (pievienošanas režīms) funkcija Flush-DeviceBatch { parametrs([virkne]$StreamName, [System.Collections.Generic.List[objekts]]$Batch) if ($Batch.Count -eq 0) { return } $fPath = $stDeviceFilePaths[$StreamName] $fSb = [System.Text.StringBuilder]::new() foreach ($fDev $Batch) { if ($stDeviceFileCounts[$StreamName] -gt; 0) { [void]$fSb.Append(",'n") } [void]$fSb.Append(($fDev | ConvertTo-Json -Compress)) $stDeviceFileCounts[$StreamName]++ } [System.IO.File]::AppendAllText($fPath, $fSb.ToString(), [System.Text.Encoding]::UTF8) } # GALVENĀ STRAUMĒŠANAS CILPA $stChunkSize = if ($totalFiles -le 10000) { $totalFiles } else { 10000 } $stTotalChunks = [math]::Ceiling($totalFiles / $stChunkSize) $stPeakMemMB = 0 IF ($stTotalChunks -gt; 1) { Write-Host "Failu apstrāde $totalFiles $stTotalChunks $stChunkSize gabalos (straumēšana, $ParallelThreads pavedieni):" -ForegroundColor Cyan } citādi { Write-Host "$totalFiles failu apstrāde (straumēšana, $ParallelThreads pavedieni):" -ForegroundColor Cyan } for ($ci = 0; $ci -lt $stTotalChunks; $ci++) { $cStart = $ci * $stChunkSize $cEnd = [matemātika]::Min($cStart + $stChunkSize, $totalFiles) - 1 $cFiles = $jsonFiles[$cStart.. $cEnd] IF ($stTotalChunks -gt; 1) { Write-Host " Bloks $($ci + 1)/$stTotalChunks ($($cFiles.Count) faili): " -NoNewline -ForegroundColor Grey } citādi { Write-Host " Notiek $($cFiles.Count) failu ielāde: " -NoNewline -ForegroundColor Grey } $cSw = [System.Diagnostics.Chronowatch]::StartNew() $rawDevices = Load-FilesParallel -Files $cFiles -Threads $ParallelThreads # Lieli bloku pakešu saraksti $cBatches = @{} foreach ($df $stDeviceFiles) { $cBatches[$df] = [System.Collections.Generic.List[object]]::new() } $cNew = 0; $cDupe = 0 foreach ($raw $rawDevices) { if (-not $raw) { continue } $device = Normalize-DeviceRecord $raw $hostname = $device. Resursdatora nosaukums if (-not $hostname) { continue } if ($seenHostnames.Contains($hostname)) { $cDupe++; continue } [void]$seenHostnames.Add($hostname) $cNew++; $c.Total++ $sbOn = ($device. SecureBootEnabled -ne $false -and "$($device. SecureBootEnabled)" -ne "Aplams") if ($sbOn) { $c.SBEnabled++ } else { $c.SBOff++; $cBatches["secureboot_off"]. Add((Get-SlimDevice $device)) } $isUpd = $device. irUpdated -eq $true $conf = ja ($device. PSObject.Properties['ConfidenceLevel'] -and $device. ConfidenceLevel) { "$($device. ConfidenceLevel)" } else { "" } $hasErr = (-not [virkne]::IsNullOrEmpty($device. UEFICA2023Error) -and "$($device. UEFICA2023Error)" -ne "0" -and "$($device. UEFICA2023Error)" -ne "") $tskDis = ($device. SecureBootTaskEnabled -eq $false -or "$($device. SecureBootTaskStatus)" -eq 'Atspējots' -or "$($device. SecureBootTaskStatus)" -eq 'NotFound') $tskNF = ("$($device. SecureBootTaskStatus)" -eq 'NotFound') $bid = ja ($device. PSObject.Properties['BucketId'] -and $device. BucketId) { "$($device. BucketId)" } else { "" } $e 1808 = ja ($device. PSObject.Properties['Event1808Count']) { [int]$device. Event1808Count } else { 0 } $e 1801 = ja ($device. PSObject.Properties['Event1801Count']) { [int]$device. Event1801Count } else { 0 } $e 1803 = ja ($device. PSObject.Properties['Event1803Count']) { [int]$device. Event1803Count } else { 0 } $mKEK = ($e 1803 -gt; 0 -vai $device. MissingKEK -eq $true -or "$($device. MissingKEK)" -eq "True") $hKI = ((-not [virkne]::IsNullOrEmpty($device. SkipReasonKnownIssue)) -or (-not [virkne]::IsNullOrEmpty($device. KnownIssueId))) $rStat = ja ($device. PSObject.Properties['RolloutStatus']) { $device. RolloutStatus } else { "" } $mfr = ja ($device. PSObject.Properties['WMI_Manufacturer'] -and -not [virkne]::IsNullOrEmpty($device. WMI_Manufacturer)) { $device. WMI_Manufacturer } else { "Nezināms" } $bid = if (-not [virkne]::IsNullOrEmpty($bid)) { $bid } else { "" } # Gaida pirmsaprēķināšanas atjaunināšanu karodziņš (politika/WinCS tiek lietota, statuss vēl nav atjaunināts, SB ieslēgts, uzdevums nav atspējots) $uefiStatus = ja ($device. PSObject.Properties['UEFICA2023Status']) { "$($device. UEFICA2023Status)" } else { "" } $hasPolicy = ($device. PSObject.Properties['AvailableUpdatesPolicy'] -and $null -ne $device. AvailableUpdatesPolicy -and "$($device. AvailableUpdatesPolicy)" -ne '') $hasWinCS = ($device. PSObject.Properties['WinCSKeyApplied'] -and $device. WinCSKeyApplied -eq $true) $statusPending = ([virkne]::IsNullOrEmpty($uefiStatus) -or $uefiStatus -eq 'NotStarted' -or $uefiStatus -eq 'Notiekošs') $isUpdatePending = (($hasPolicy -vai $hasWinCS) -and $statusPending -and -not $isUpd -and $sbOn -and -not $tskDis) Ja ($isUpd) { $c.Atjaunināts++; [void]$stSuccessBuckets.Add($bid); $cBatches["updated_devices"]. Add((Get-SlimDevice $device)) # Izsekot atjauninātās ierīces, kurām nepieciešama atkārtota palaišana (UEFICA2023Status=Atjaunināts, bet Event1808=0) IF ($e 1808 -eq 0) { $c.NeedsReboot++; $cBatches["needs_reboot"]. Add((Get-SlimDevice $device)) } } elseif (-not $sbOn) { # SecureBoot OFF — ārpus darbības jomas, neklasificējiet pēc pārliecības } citādi { if ($isUpdatePending) { } # Skaitīts atsevišķi atjauninājumā Gaida — savstarpēji izslēdz sektoru diagramma elseif (Test-ConfidenceLevel $conf "HighConfidence") { $c.HighConf++ } elseif (Test-ConfidenceLevel $conf "UnderObservation") { $c.UnderObs++ } elseif (Test-ConfidenceLevel $conf "TemporarilyPause") { $c.TempPaused++ } elseif (Test-ConfidenceLevel $conf "NotSupported") { $c.NotSupported++ } citādi { $c.ActionReq++ } if ([virkne]::IsNullOrEmpty($conf)) { $c.NoConfData++ } } if ($tskDis) { $c.TaskDisabled++; $cBatches["task_disabled"]. Add((Get-SlimDevice $device)) } ja ($tskNF) { $c.TaskNotFound++ } if (-not $isUpd -and $tskDis) { $c.TaskDisabledNotUpdated++ } Ja ($hasErr) { $c.WithErrors++; [void]$stFailedBuckets.Add($bid); $cBatches["errors"]. Add((Get-SlimDevice $device)) $ec = $device. UEFICA2023Kļūda if (-not $stErrorCodeCounts.ContainsKey($ec)) { $stErrorCodeCounts[$ec] = 0; $stErrorCodeSamples[$ec] = @() } $stErrorCodeCounts[$ec]++ IF ($stErrorCodeSamples[$ec]. Count -lt 5) { $stErrorCodeSamples[$ec] += $hostname } } Ja ($hKI) { $c.WithKnownIssues++; $cBatches["known_issues"]. Add((Get-SlimDevice $device)) $ki = if (-not [virkne]::IsNullOrEmpty($device. SkipReasonKnownIssue)) { $device. SkipReasonKnownIssue } else { $device. KnownIssueId } if (-not $stKnownIssueCounts.ContainsKey($ki)) { $stKnownIssueCounts[$ki] = 0 }; $stKnownIssueCounts[$ki]++ } if ($mKEK) { $c.WithMissingKEK++; $cBatches["missing_kek"]. Add((Get-SlimDevice $device)) } if (-not $isUpd -and ($tskDis -or (Test-ConfidenceLevel $conf 'TemporarilyPause'))) { $c.TempFailures++; $cBatches["temp_failures"]. Add((Get-SlimDevice $device)) } if (-not $isUpd -and ((Test-ConfidenceLevel $conf 'NotSupported') -or ($tskNF -and $hasErr))) { $c.PermFailures++; $cBatches["perm_failures"]. Add((Get-SlimDevice $device)) } IF ($e 1801 -gt; 0 -and $e 1808 -eq 0 -and -not $hasErr -and $rStat -eq "InProgress") { $c.RolloutInProgress++; $cBatches["rollout_inprogress"]. Add((Get-SlimDevice $device)) } if ($e 1801 -gt; 0 -and $e 1808 -eq 0 -and -not $hasErr -and $rStat -ne "InProgress") { $c.NotYetInitiated++ } if ($rStat -eq "InProgress" -and $e 1808 -eq 0) { $c.InProgress++ } # Gaida atjauninājumu: politika vai WinCS lietota, statuss gaida, SB ieslēgts, uzdevums nav atspējots Ja ($isUpdatePending) { $c.UpdatePending++; $cBatches["update_pending"]. Add((Get-SlimDevice $device)) } IF (-not $isUpd -and $sbOn) { $cBatches["not_updated"]. Add((Get-SlimDevice $device)) } # Zem novērošanas ierīcēm (atsevišķi no Nepieciešamā darbība) if (-not $isUpd -and (Test-ConfidenceLevel $conf 'UnderObservation')) { $cBatches["under_observation"]. Add((Get-SlimDevice $device)) } # Nepieciešamā darbība: nav atjaunināts, SB IESLĒGTS, neatbilst citām ticamības kategorijām, nav Gaida atjaunināšanu if (-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)) } if (-not $stMfrCounts.ContainsKey($mfr)) { $stMfrCounts[$mfr] = @{ Total=0; Atjaunināts=0; Gaida atjauninājumu=0; HighConf=0; UnderObs=0; ActionReq=0; TempPaused=0; Netiek atbalstīts=0; SBOff=0; ArKļūdām=0 } } $stMfrCounts[$mfr]. Kopā++ IF ($isUpd) { $stMfrCounts[$mfr]. Pēdējoreiz atjaunināts++ } elseif (-not $sbOn) { $stMfrCounts[$mfr]. SBOff++ } elseif ($isUpdatePending) { $stMfrCounts[$mfr]. Gaida atjauninājumu++ } elseif (Test-ConfidenceLevel $conf "HighConfidence") { $stMfrCounts[$mfr]. HighConf++ } elseif (Test-ConfidenceLevel $conf "UnderObservation") { $stMfrCounts[$mfr]. ApakšāObs++ } elseif (Test-ConfidenceLevel $conf "Īslaicīgi pauzēts") { $stMfrCounts[$mfr]. TempPaused++ } elseif (Test-ConfidenceLevel $conf "NotSupported") { $stMfrCounts[$mfr]. Netiek atbalstīts++ } citādi { $stMfrCounts[$mfr]. ActionReq++ } IF ($hasErr) { $stMfrCounts[$mfr]. WithErrors++ } # Izsekot visām ierīcēm pēc intervāla (ieskaitot tukšo BucketId) $bucketKey = if ($bid -and $bid -ne "") { $bid } else { "(tukšs)" } if (-not $stAllBuckets.ContainsKey($bucketKey)) { $stAllBuckets[$bucketKey] = @{ Count=0; Atjaunināts=0; Ražotājs = $mfr; Model=""; BIOS="" } Ja ($device. PSObject.Properties['WMI_Model']) { $stAllBuckets[$bucketKey]. Modelis = $device. WMI_Model } Ja ($device. PSObject.Properties['BIOSDescription']) { $stAllBuckets[$bucketKey]. BIOS = $device. BIOSDescription } } $stAllBuckets[$bucketKey]. Skaits++ IF ($isUpd) { $stAllBuckets[$bucketKey]. Pēdējoreiz atjaunināts++ } } # Izskalojiet paketes diskā foreach ($df $stDeviceFiles) { Flush-DeviceBatch -StreamName $df -Batch $cBatches[$df] } $rawDevices = $null; $cBatches = $null; [System.GC]::Collect() $cSw.Stop() $cTime = [Math]::Round($cSw.Elapsed.TotalSeconds, 1) $cRem = $stTotalChunks - $ci - 1 $cEta = ja ($cRem -gt; 0) { " | ETA: ~$([Math]::Round($cRem * $cSw.Elapsed.TotalSeconds / 60, 1)) min" } else { "" } $cMem = [math]::Round([System.GC]::GetTotalMemory($false) / 1MB, 0) if ($cMem -gt; $stPeakMemMB) { $stPeakMemMB = $cMem } Write-Host " +$cNew jauns, $cDupe dupes, ${cTime}s | Mem: ${cMem}MB$cEta" -Priekšplāna krāsa zaļa } # JSON masīvu pabeigšana foreach ($dfName $stDeviceFiles) { [System.IO.File]::AppendAllText($stDeviceFilePaths[$dfName], "'n]", [System.Text.Encoding]::UTF8) Write-Host " $dfName.json: $($stDeviceFileCounts[$dfName]) devices" -ForegroundColor DarkGray } # Aprēķināt atvasināto statistiku $stAtRisk = 0; $stSafeList = 0 foreach ($bid $stAllBuckets.Keys) { $b = $stAllBuckets[$bid]; $nu = $b.Count - $b.Updated if ($stFailedBuckets.Contains($bid)) { $stAtRisk += $nu } elseif ($stSuccessBuckets.Contains($bid)) { $stSafeList += $nu } } $stAtRisk = [math]::Max(0, $stAtRisk - $c.WithErrors) # NotUptodate = skaits no not_updated paketes (ierīces ar SB ieslēgtu un nav atjauninātas) $stNotUptodate = $stDeviceFileCounts["not_updated"] $stats = [pasūtīts]@{ ReportGeneratedAt = (get-date). ToString("yyyy-MM-dd HH:mm:ss") TotalDevices = $c.Total; SecureBootEnabled = $c.SBEnabled; SecureBootOFF = $c.SBOff Atjaunināts = $c.Atjaunināts; HighConfidence = $c.HighConf; UnderObservation = $c.UnderObs ActionRequired = $c.ActionReq; Īslaicīgi pauzēta = $c.TempPaused; NotSupported = $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 = if ($c.Total -gt; 0) { [math]::Round(($c.WithErrors/$c.Total)*100,2) } else { 0 } PercentAtRisk = if ($c.Total -gt; 0) { [math]::Round(($stAtRisk/$c.Total)*100,2) } else { 0 } PercentSafeList = if ($c.Total -gt; 0) { [math]::Round(($stSafeList/$c.Total)*100,2) } else { 0 } PercentHighConfidence = if ($c.Total -gt; 0) { [math]::Round(($c.HighConf/$c.Total)*100,1) } else { 0 } PercentCertUpdated = if ($c.Total -gt; 0) { [math]::Round(($c.Updated/$c.Total)*100,1) } else { 0 } PercentActionRequired = if ($c.Total -gt; 0) { [math]::Round(($c.ActionReq/$c.Total)*100,1) } else { 0 } PercentNotUptodate = if ($c.Total -gt; 0) { [math]::Round($stNotUptodate/$c.Total*100,1) } else { 0 } PercentFullyUpdated = if ($c.Total -gt; 0) { [math]::Round(($c.Updated/$c.Total)*100,1) } else { 0 } UniqueBuckets = $stAllBuckets.Count; PeakMemoryMB = $stPeakMemMB; ProcessingMode = "Streaming" } # CSV rakstīšana [PSCustomObject]$stats | Export-Csv -ceļš (pievienošanās ceļš $OutputPath "SecureBoot_Summary_$timestamp.csv") -NoTypeInformation -kodēšana UTF8 $stMfrCounts.GetEnumerator() | Sort-Object { $_. Vērtība.Kopsumma } -dilstošā secībā | ForEach-Object { [PSCustomObject]@{ Ražotājs=$_. Atslēga; Skaits=$_. Vērtība.Kopā; Atjaunināts=$_. Vērtība.Atjaunināts; Augsta pārliecība=$_. Vērtība.HighConf; ActionRequired=$_. Value.ActionReq } } | Export-Csv -ceļš (pievienošanās ceļš $OutputPath "SecureBoot_ByManufacturer_$timestamp.csv") -NoTypeInformation -kodēšana UTF8 $stErrorCodeCounts.GetEnumerator() | Sort-Object vērtība — dilstošā secībā | ForEach-Object { [PSCustomObject]@{ ErrorCode=$_. Atslēga; Skaits=$_. Vērtība; SampleDevices=($stErrorCodeSamples[$_. Key] -join ", ") } } | Export-Csv -ceļš (pievienošanās ceļš $OutputPath "SecureBoot_ErrorCodes_$timestamp.csv") -NoTypeInformation -kodēšana UTF8 $stAllBuckets.GetEnumerator() | Sort-Object { $_. Value.Count } -dilstošā secībā | ForEach-Object { [PSCustomObject]@{ BucketId=$_. Atslēga; Skaits=$_. Vērtība.Skaits; Atjaunināts=$_. Vērtība.Atjaunināts; NotUpdated=$_. Vērtība.Skaits-$_. Vērtība.Atjaunināts; Ražotājs=$_. Vērtība.Ražotājs } } | Export-Csv -ceļš (pievienošanās ceļš $OutputPath "SecureBoot_UniqueBuckets_$timestamp.csv") -NoTypeInformation -kodēšana UTF8 # Ģenerējiet ar orķestri saderīgus CSV failus (paredzētie failu nosaukumi Start-SecureBootRolloutOrchestrator.ps1) $notUpdatedJsonPath = Join-Path $dataDir "not_updated.json" IF (Testa ceļš $notUpdatedJsonPath) { Izmēģiniet { $nuData = Get-Content $notUpdatedJsonPath -Raw | ConvertFrom-JSON IF ($nuData.Count, -gt; 0) { # NotUptodate CSV — orchestrator meklē *NotUptodate*.csv $nuData | Export-Csv -ceļš (pievienošanās ceļš $OutputPath "SecureBoot_NotUptodate_$timestamp.csv") -NoTypeInformation -kodēšana UTF8 Write-Host " Orchestrator CSV: SecureBoot_NotUptodate_$timestamp.csv ($($nuData.Count) devices)" -ForegroundColor Grey } } nozveja { } } # Rakstiet JSON datus informācijas panelim $stats | ConvertTo-Json — Dziļums 3 | Set-Content (pievienošanās ceļš $dataDir "summary.json") — kodēšana UTF8 # VĒSTURISKĀ IZSEKOŠANA: Saglabāt datu punktu tendenču diagrammai # Izmantojiet stabilu kešatmiņas atrašanās vietu, lai tendenču dati saglabātos visās apkopojuma mapēs ar laikspiedoliem. # Ja OutputPath izskatās kā "...\Aggregation_yyyyMMdd_HHmmss", kešatmiņa tiek ievietota vecākmapē.# Pretējā gadījumā kešatmiņa nonāk pašā OutputPath.$parentDir = Split-Path $OutputPath -Parent $leafName = Split-Path $OutputPath -Leaf if ($leafName -match '^Aggregation_\d{8}' -or $leafName -eq 'Aggregation_Current') { # Orchestrator created timestamped folder — use parent for stable cache $historyPath = Join-Path $parentDir ".cache\trend_history.json" } citādi { $historyPath = Join-Path $OutputPath ".cache\trend_history.json" } $historyDir = Split-Path $historyPath -Parent if (-not (testa ceļa $historyDir)) { New-Item -itemType directory -path $historyDir -force | Out-Null } $historyData = @() IF (Testa ceļš $historyPath) { try { $historyData = @(Get-Content $historyPath -Raw | ConvertFrom-Json) } catch { $historyData = @() } } # Pārbaudiet arī iekšpusē OutputPath\.cache\ (mantota atrašanās vieta no vecākām versijām) # Sapludināt visus datu punktus, kas vēl nav primārajā vēsturē if ($leafName -eq 'Aggregation_Current' -or $leafName -match '^Aggregation_\d{8}') { $innerHistoryPath = Join-Path $OutputPath ".cache\trend_history.json" if ((testa ceļš $innerHistoryPath) -and $innerHistoryPath -ne $historyPath) { Izmēģiniet { $innerData = @(Get-Content $innerHistoryPath -Raw | ConvertFrom-Json) $existingDates = @($historyData | ForEach-Object { $_. Datums }) foreach ($entry $innerData) { Ja ($entry. Datums un $entry. Datums -notin $existingDates) { $historyData += $entry } } if ($innerData.Count, -gt; 0) { Write-Host " Sapludināti $($innerData.Count) datu punkti no iekšējās kešatmiņas" -ForegroundColor DarkGray } } nozveja { } } }
# BOOTSTRAP: Ja tendenču vēsture ir tukša/reta, rekonstruējiet no vēsturiskajiem datiem if ($historyData.Count -lt, 2 -and ($leafName -match '^Aggregation_\d{8}' -or $leafName -eq 'Aggregation_Current')) { Write-Host " Bootstrapping tendenču vēsture no vēsturiskiem datiem..." -ForegroundColor Yellow $dailyData = @{} # 1. avots: kopsavilkuma CSV pašreizējā mapē (Aggregation_Current saglabā visus kopsavilkuma CSV) $localSummaries = Get-ChildItem $OutputPath -Filter "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Sort-Object nosaukums foreach ($summCsv $localSummaries) { Izmēģiniet { $summ = Import-Csv $summCsv.Vārds_Uzvārds | Select-Object -Pirmais 1 Ja ($summ. TotalDevices un [int]$summ. TotalDevices -gt; 0 -and $summ. ReportGeneratedAt) { $dateStr = ([datetime]$summ. ReportGeneratedAt). ToString("yyyy-MM-dd") $updated = ja ($summ. Atjaunināts) { [int]$summ. Pēdējoreiz { 0 } $notUpd = ja ($summ. NotUptodate) { [int]$summ. NotUptodate } else { [int]$summ. TotalDevices - $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Datums = $dateStr; Kopsumma = [int]$summ. TotalDevices; Atjaunināts = $updated; NotUpdated = $notUpd NeedsReboot = 0; Kļūdas = 0; ActionRequired = ja ($summ. ActionRequired) { [int]$summ. ActionRequired } else { 0 } } } } nozveja { } } # 2. avots: vecās mapes ar Aggregation_ laikspiedolu * (mantotas, ja tās joprojām pastāv) $aggFolders = Get-ChildItem $parentDir -Directory -Filter "Aggregation_*" -EA SilentlyContinue | Where-Object { $_. Name -match '^Aggregation_\d{8}' } | Sort-Object nosaukums foreach ($folder $aggFolders) { $summCsv = Get-ChildItem $folder. FullName -Filter "SecureBoot_Summary_*.csv" -EA SilentlyContinue | Select-Object -Pirmais 1 Ja ($summCsv) { Izmēģiniet { $summ = Import-Csv $summCsv.Vārds_Uzvārds | Select-Object -Pirmais 1 Ja ($summ. TotalDevices un [int]$summ. TotalDevices -gt; 0) { $dateStr = $folder. Name -replace '^Aggregation_(\d{4})(\d{2})(\d{2})_.*', '$1-$2-$3' $updated = ja ($summ. Atjaunināts) { [int]$summ. Pēdējoreiz { 0 } $notUpd = ja ($summ. NotUptodate) { [int]$summ. NotUptodate } else { [int]$summ. TotalDevices - $updated } $dailyData[$dateStr] = [PSCustomObject]@{ Datums = $dateStr; Kopsumma = [int]$summ. TotalDevices; Atjaunināts = $updated; NotUpdated = $notUpd NeedsReboot = 0; Kļūdas = 0; ActionRequired = ja ($summ. ActionRequired) { [int]$summ. ActionRequired } else { 0 } } } } nozveja { } } } # Avots 3: RolloutState.json WaveHistory (ir per-wave timestamps no 1. dienas) # Tas nodrošina bāzlīnijas datu punktus pat tad, ja nepastāv vecas apkopojuma mapes $rolloutStatePaths = @( (Join-path $parentDir "RolloutState\RolloutState.json"), (Join-path $OutputPath "RolloutState\RolloutState.json") ) foreach ($rsPath $rolloutStatePaths) { IF (Testa ceļš $rsPath) { Izmēģiniet { $rsData = Get-Content $rsPath -Raw | ConvertFrom-JSON ja ($rsData.WaveHistory) { # Viļņu sākuma datumu kā tendenču datu punktu izmantošana # Aprēķiniet kumulatīvās ierīces, kas vērstas uz katru vilni $cumulativeTargeted = 0 foreach ($wave $rsData.WaveHistory) { Ja ($wave. Sāka un $wave. DeviceCount) { $waveDate = ([datetime]$wave. Sākts). ToString("yyyy-MM-dd") $cumulativeTargeted += [int]$wave. Ierīču skaits if (-not $dailyData.ContainsKey($waveDate)) { # Aptuveni: viļņa sākuma laikā tika atjauninātas tikai ierīces no iepriekšējiem viļņiem $dailyData[$waveDate] = [PSCustomObject]@{ Datums = $waveDate; Kopsumma = $c.Kopā; Atjaunināts = [math]::Max(0, $cumulativeTargeted - [int]$wave. DeviceCount) NotUpdated = $c.Total - [math]::Max(0, $cumulativeTargeted - [int]$wave. DeviceCount) NeedsReboot = 0; Kļūdas = 0; ActionRequired = 0 } } } } } } nozveja { } pārtraukums # Izmantojiet pirmo atrasto } }
ja ($dailyData.Count, -gt; 0) { $historyData = @($dailyData.GetEnumerator() | Sort-Object Atslēga | ForEach-Object { $_. Vērtība }) Write-Host " Bootstrapped $($historyData.Count) data points from historical briefries" -ForegroundColor Green } }
# Pievienot pašreizējo datu punktu (dedublēt pa dienām — saglabāt vēlāko dienā) $todayKey = (Get-Date). ToString("yyyy-MM-dd") $existingToday = $historyData | Where-Object { "$($_. Date)" -like "$todayKey*" } Ja ($existingToday) { # Aizstāt šodienas ierakstu $historyData = @($historyData | Where-Object { "$($_. Datums)" -notlike "$todayKey*" }) } $historyData += [PSCustomObject]@{ Datums = $todayKey Kopsumma = $c. Kopsumma Atjaunināts = $c.Updated NotUpdated = $stNotUptodate NeedsReboot = $c.NeedsReboot Kļūdas = $c.WithErrors ActionRequired = $c.ActionReq } # Noņemt nederīgus datu punktus (kopā 0) un paturēt pēdējos 90 $historyData = @($historyData | Where-Object { [int]$_. Kopā: -gt; 0 }) # Bez vāciņa — tendences dati ir ~100 baiti/ieraksts, pilns gads = ~36 KB $historyData | ConvertTo-Json — dziļums 3 | Set-Content $historyPath — UTF8 kodēšana Write-Host " Tendenču vēsture: $($historyData.Count) data points" -ForegroundColor DarkGray # Tendenču diagrammas datu izveide HTML vajadzībām $trendLabels = ($historyData | ForEach-Object { "'$($_. Datums)'" }) -join "," $trendUpdated = ($historyData | ForEach-Object { $_. Atjaunināts: }) -join "," $trendNotUpdated = ($historyData | ForEach-Object { $_. NotUpdated }) -join "," $trendTotal = ($historyData | ForEach-Object { $_. Total }) -join "," # Projekcija: paplašināt tendenču līniju, izmantojot eksponenciālo dubultošanos (2,4,8,16...) # Iegūst viļņu lielumu un novērošanas periodu no faktiskajiem tendenču vēstures datiem.# - Viļņa lielums = lielākais viena perioda pieaugums vēsturē (jaunākais izvietotais vilnis) # - Novērošanas dienas = vidējais kalendārais dienu skaits starp tendenču datu punktiem (cik bieži mēs skrienam) # Pēc tam dubulto viļņa lielumu katrā periodā, kas atbilst orķestra 2x izaugsmes stratēģijai.$projLabels = ""; $projUpdated = ""; $projNotUpdated = ""; $hasProjection = $false IF ($historyData.Count -ge 2) { $lastUpdated = $c.Updated $remaining = $stNotUptodate # Tikai SB-ON neatjauninātās ierīces (izņemot SecureBoot OFF) $projDates = @(); $projValues = @(); $projNotUpdValues = @() $projDate = Get-Date
# Atvasiniet viļņa lielumu un novērošanas periodu no tendenču vēstures $increments = @() $dayGaps = @() for ($hi = 1; $hi -lt $historyData.Count; $hi++) { $inc = $historyData[$hi]. Atjaunināts - $historyData[$hi-1]. Atjaunināts IF ($inc -gt; 0) { $increments += $inc } Izmēģiniet { $d 1 = [datetime]::P arse($historyData[$hi-1]. Datums) $d 2 = [datetime]::P arse($historyData[$hi]. Datums) $gap = ($d 2–$d 1). TotalDays IF ($gap -gt; 0) { $dayGaps += $gap } } nozveja {} } # Viļņa lielums = jaunākais pozitīvais pieaugums (pašreizējais vilnis), atkāpšanās uz vidējo, minimums 2 $waveSize = ja ($increments. Skaits -gt; 0) { [matemātika]::Max(2; $increments[-1]) } citādi { 2 } # Novērošanas periods = vidējā starpība starp datu punktiem (kalendārās dienas vienā viļņā), vismaz 1 $waveDays = IF ($dayGaps.Count, -gt; 0) { [matemātika]::max(1; [matemātika]::round(($dayGaps | Measure-Object -vidējais). Vidējais, 0)) } citādi { 1 }
Write-Host " Projekcija: waveSize=$waveSize (no pēdējā pieauguma), waveDays=$waveDays (vidējā atstarpe no vēstures)" -ForegroundColor DarkGray
$dayCounter = 0 # Projicēšana, līdz visas ierīces ir atjauninātas vai ne vairāk kā 365 dienas for ($pi = 1; $pi -le 365; $pi++) { $projDate = $projDate.AddDays(1) $dayCounter++ # Pie katras novērošanas perioda robežas izvietojiet vilni, pēc tam dubultojiet Ja ($dayCounter -ge $waveDays) { $devicesThisWave = [math]::Min($waveSize, $remaining) $lastUpdated += $devicesThisWave $remaining -= $devicesThisWave IF ($lastUpdated -gt; ($c.Updated + $stNotUptodate)) { $lastUpdated = $c.Updated + $stNotUptodate; $remaining = 0 } # Dubultā viļņa lielums nākamajam periodam (orchestrator 2x stratēģija) $waveSize = $waveSize * 2 $dayCounter = 0 } $projDates += "'$($projDate.ToString("yyyy-MM-dd"))'" $projValues += $lastUpdated $projNotUpdValues += [matemātika]::Max(0, $remaining) IF ($remaining -LE 0) { pārtraukums } } $projLabels = $projDates -join "," $projUpdated = $projValues -join "," $projNotUpdated = $projNotUpdValues -join "," $hasProjection = $projDates.Count -gt; 0 } elseif ($historyData.Count -eq 1) { Write-Host " Projekcija: viļņu hronometrāžas izgūšanai nepieciešami vismaz 2 tendenču datu punkti" -ForegroundColor DarkGray } # Izveidot apvienotas diagrammas datu virknes šeit-virknei $allChartLabels = if ($hasProjection) { "$trendLabels,$projLabels" } else { $trendLabels } $projDataJS = if ($hasProjection) { $projUpdated } else { "" } $projNotUpdJS = if ($hasProjection) { $projNotUpdated } else { "" } $histCount = ($historyData | mērs-objekts). Skaits $stMfrCounts.GetEnumerator() | Sort-Object { $_. Vērtība.Kopsumma } -dilstošā secībā | ForEach-Object { @{ vārds=$_. Atslēga; kopsumma=$_. Vērtība.Kopā; atjaunināts=$_. Vērtība.Atjaunināts; highConf=$_. Vērtība.HighConf; actionReq=$_. Value.ActionReq } } | ConvertTo-Json — dziļums 3 | Set-Content (pievienošanās ceļš $dataDir "manufacturers.json") — kodēšana UTF8 # Konvertējiet JSON datu failus uz CSV cilvēkam lasāmām Excel lejupielādēm Write-Host "Converting device data to CSV for Excel download..." -ForegroundColor Grey foreach ($dfName $stDeviceFiles) { $jsonFile = Join-Path $dataDir "$dfName.json" $csvFile = Join-Path $OutputPath "SecureBoot_${dfName}_$timestamp.csv" Ja (testa ceļš $jsonFile) { Izmēģiniet { $jsonData = Get-Content $jsonFile -Raw | ConvertFrom-JSON IF ($jsonData.Count, -gt; 0) { # Papildu kolonnu iekļaušana update_pending CSV failā $selectProps = IF ($dfName -eq "update_pending") { @('Resursdatora_nosaukums', 'WMI_Manufacturer', 'WMI_Model', 'BucketId', 'ConfidenceLevel', 'IsUpdated', 'UEFICA2023Status', 'UEFICA2023Error', 'AvailableUpdatesPolicy', 'WinCSKeyApplied', 'SecureBootTaskStatus') } citādi { @('Resursdatora_nosaukums', '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) rindas -> CSV" -ForegroundColor DarkGray } } catch { Write-Host " $dfName - izlaists" -ForegroundColor tumši dzeltens } } } # Patstāvīga HTML informācijas paneļa ģenerēšana $htmlPath = Join-Path $OutputPath "SecureBoot_Dashboard_$timestamp.html" Write-Host "Ģenerē patstāvīgu HTML informācijas paneli..." -ForegroundColor Yellow # ĀTRUMA PROJEKCIJA: Aprēķināt pēc skenēšanas vēstures vai iepriekšējā kopsavilkuma $stDeadline = [datetime]"2026-06-24" # KEK sertifikāta derīguma termiņš $stDaysToDeadline = [math]::Max(0, ($stDeadline - (Get-Date)). dienas) $stDevicesPerDay = 0 $stProjectedDate = $null $stVelocitySource = "N/A" $stWorkingDays = 0 $stCalendarDays = 0 # Vispirms izmēģiniet tendenču vēsturi (viegla, jau uzturēta apkopotāja — aizstāj uzpūstas ScanHistory.json) IF ($historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Kopā -gt; 0 -and [int]$_. Atjaunināts -ge 0 }) ja ($validHistory.Count -ge 2) { $prev = $validHistory[-2]; $curr = $validHistory[-1] $prevDate = [datetime]::P arse($prev. Date.Substring(0, [matemātika]::min(10, $prev. Date.Length))) $currDate = [datetime]::P arse($curr. Date.Substring(0, [matemātika]::min(10, $curr. Date.Length))) $daysDiff = ($currDate - $prevDate). TotalDays IF ($daysDiff -gt; 0) { $updDiff = [int]$curr. Atjaunināts - [int]$prev. Atjaunināts IF ($updDiff -gt; 0) { $stDevicesPerDay = [matemātika]::Round($updDiff / $daysDiff, 0) $stVelocitySource = "Tendenču vēsture" } } } } # Izmēģiniet vadības moduļa ieviešanas kopsavilkumu (tam ir iepriekš aprēķināts ātrums) if ($stVelocitySource -eq "N/A" -and $RolloutSummaryPath -and (Test-Path $RolloutSummaryPath)) { Izmēģiniet { $rolloutSummary = Get-Content $RolloutSummaryPath -Raw | ConvertFrom-JSON if ($rolloutSummary.DevicesPerDay -and [double]$rolloutSummary.DevicesPerDay -gt; 0) { $stDevicesPerDay = [math]::Round([double]$rolloutSummary.DevicesPerDay, 1) $stVelocitySource = "Orchestrator" if ($rolloutSummary.ProjectedCompletionDate) { $stProjectedDate = $rolloutSummary.ProjectedCompletionDate } if ($rolloutSummary.WorkingDaysRemaining) { $stWorkingDays = [int]$rolloutSummary.WorkingDaysRemaining } if ($rolloutSummary.CalendarDaysRemaining) { $stCalendarDays = [int]$rolloutSummary.CalendarDaysRemaining } } } nozveja { } } # Atkāpšanās: mēģiniet iepriekšējo kopsavilkuma CSV (meklēt pašreizējā mapē UN vecāku/brāļu apkopojuma mapēs) if ($stVelocitySource -eq "N/A") { $searchPaths = @( (Pievienošanās ceļš $OutputPath "SecureBoot_Summary_*.csv") ) # Meklējiet arī tā paša līmeņa apkopojuma mapes (vadības modulis izveido jaunu mapi katrā palaišanas reizē) $parentPath = Split-Path $OutputPath -Parent Ja ($parentPath) { $searchPaths += (pievienošanās ceļš $parentPath "Aggregation_*\SecureBoot_Summary_*.csv") $searchPaths += (pievienošanās ceļš $parentPath "SecureBoot_Summary_*.csv") } $prevSummary = $searchPaths | ForEach-Object { Get-ChildItem $_ -EA SilentlyContinue } | Sort-Object LastWriteTime — dilstošā secībā | Select-Object -Pirmais 1 Ja ($prevSummary) { Izmēģiniet { $prevStats = Get-Content $prevSummary.Vārds_Uzvārds | ConvertFrom-CSV $prevDate = [datums/laiks]$prevStats.ReportGeneratedAt $daysSinceLast = ((Get-Date) - $prevDate). TotalDays IF ($daysSinceLast -gt; 0.01) { $prevUpdated = [int]$prevStats.Updated $updDelta = $c.Updated - $prevUpdated IF ($updDelta -gt; 0) { $stDevicesPerDay = [matemātika]::Round($updDelta / $daysSinceLast, 0) $stVelocitySource = "PreviousReport" } } } nozveja { } } } # Atkāpšanās: aprēķināt ātrumu no pilna tendenču vēstures span (pirmais vs jaunākais datu punkts) if ($stVelocitySource -eq "N/A" -and $historyData.Count -ge 2) { $validHistory = @($historyData | Where-Object { [int]$_. Kopā -gt; 0 -and [int]$_. Atjaunināts -ge 0 }) ja ($validHistory.Count -ge 2) { $first = $validHistory[0] $last = $validHistory[-1] $firstDate = [datetime]::P arse($first. Date.Substring(0, [matemātika]::min(10, $first. Date.Length))) $lastDate = [datetime]::P arse($last. Date.Substring(0, [matemātika]::min(10, $last. Date.Length))) $daysDiff = ($lastDate - $firstDate). TotalDays IF ($daysDiff -gt; 0) { $updDiff = [int]$last. Atjaunināts - [int]$first. Atjaunināts IF ($updDiff -gt; 0) { $stDevicesPerDay = [matemātika]::Round($updDiff / $daysDiff, 1) $stVelocitySource = "Tendenču vēsture" } } } } # Aprēķināt projekciju, izmantojot eksponenciālo dubultošanu (saskaņā ar tendenču diagrammu) # Atkārtota projekcijas datu izmantošana, ja tādi ir aprēķināti diagrammai if ($hasProjection -and $projDates.Count -gt; 0) { # Izmantojiet pēdējo prognozēto datumu (kad visas ierīces ir atjauninātas) $lastProjDateStr = $projDates[-1] -replace "'", "" $stProjectedDate = ([datetime]::P arse($lastProjDateStr)). ToString("MMM dd, gggg") $stCalendarDays = ([datetime]::P arse($lastProjDateStr) - (get-date)). Dienas $stWorkingDays = 0 $d = get-date for ($i = 0; $i -lt $stCalendarDays; $i++) { $d = $d.AddDays(1) if ($d.DayOfWeek -ne 'Saturday' -and $d.DayOfWeek -ne 'Sunday') { $stWorkingDays++ } } } elseif ($stDevicesPerDay -gt; 0 -and $stNotUptodate -gt; 0) { # Atkāpšanās: lineāra projekcija, ja nav pieejami eksponenciālie dati $daysNeeded = [matemātika]::Ceiling($stNotUptodate / $stDevicesPerDay) $stProjectedDate = (Get-Date). AddDays($daysNeeded). ToString("MMM dd, gggg") $stWorkingDays = 0; $stCalendarDays = $daysNeeded $d = get-date for ($i = 0; $i -lt $daysNeeded; $i++) { $d = $d.AddDays(1) if ($d.DayOfWeek -ne 'Saturday' -and $d.DayOfWeek -ne 'Sunday') { $stWorkingDays++ } } } # Veidošanas ātrums HTML $velocityHtml = IF ($stDevicesPerDay -gt; 0) { "<div><spēcīgs>🚀 Ierīces/dienā:</strong> $($stDevicesPerDay.ToString('N0')) (avots: $stVelocitySource)</div>" + "<div><spēcīgs>📅 Prognozētā pabeigtība:</strong> $stProjectedDate" + $(if ($stProjectedDate -and [datetime]::P arse($stProjectedDate) -gt; $stDeadline) { " <span style='color:#dc3545; font-weight:bold'>⚠ PAST DEADLINE</span>" } else { " <span style='color:#28a745'>✓ Before deadline</span>" }) + "</div>" + "<div><spēcīgs>🕐 Darba dienas:</strong> $stWorkingDays | <spēcīgs>Calendar Dienas:</stiprs> $stCalendarDays</div>" + "<div style='font-size:.8em; krāsa:#888'>Termiņš: 2026. gada 24. jūnijs (KEK sertifikāta derīguma termiņš) | Atlikušās dienas: $stDaysToDeadline</div>" } citādi { "<div style='padding:8px; fons:#fff3cd; robežas rādiuss:4px; border-left:3px solid #ffc107'>" + "<spēcīgs>📅 Prognozētā pabeigšana:</strong> Nepietiek datu ātruma aprēķināšanai. " + "Veikt apkopošanu vismaz divas reizes ar datu izmaiņām, lai noteiktu likmi.<br/>" + "<spēcīgs>Termiņš:</strong> 24. gada 2026. jūnijs (KEK sertifikāta derīguma termiņš) | <strong>Atlikušās dienas:</strong> $stDaysToDeadline</div>" } # Sertifikāta derīguma termiņa atpakaļskaitīšana $certToday = get-date $certKekExpiry = [datetime]"2026-06-24" $certUefiExpiry = [datetime]"2026-06-27" $certPcaExpiry = [datetime]"2026-10-19" $daysToKek = [matemātika]::Max(0, ($certKekExpiry - $certToday). dienas) $daysToUefi = [matemātika]::Max(0, ($certUefiExpiry - $certToday). dienas) $daysToPca = [matemātika]::Max(0, ($certPcaExpiry - $certToday). dienas) $certUrgency = if ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # Palīgs: Lasīt ierakstus no JSON, build bucket summary + first N device rows $maxInlineRows = 200 Funkcija Build-InlineTable { param([virkne]$JsonPath, [int]$MaxRows = 200, [virkne]$CsvFileName = "") $bucketSummary = "" $deviceRows = "" $totalCount = 0 Ja (testa ceļš $JsonPath) { Izmēģiniet { $data = Get-Content $JsonPath -Raw | ConvertFrom-JSON $totalCount = $data. Skaits # INTERVĀLA KOPSAVILKUMS: Grupēt pēc BucketId, rādīt skaitu katrā intervālā ar Atjaunināts no globālās intervāla statistikas IF ($totalCount -gt; 0) { $buckets = $data | Group-Object BucketId | Sort-Object Count — dilstošā secībā $bucketSummary = "><2 h3 style='font-size:.95em; krāsa:#333; margin:10px 0 5px'><3 Pēc aparatūras kausa ($($buckets. Count) buckets)><4 /h3>" $bucketSummary += "><6 div style='max-height:300px; overflow-y:auto; 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'>Atjaunināts</th><style='text-align:right; color:#dc3545'>Not Updated</th><th><1 Manufacturer><2 /th></tr></thead><tbody>" foreach ($b $buckets) { $bid = IF ($b.Name) { $b.Name } else { "(tukšs)" } $mfr = ($b.Grupa | Select-Object -Pirmais 1). WMI_Manufacturer # Saņemt atjauninātu skaitu no globālās intervāla statistikas (visas ierīces šajā intervālā visā datu kopā) $lookupKey = $bid $globalBucket = if ($stAllBuckets.ContainsKey($lookupKey)) { $stAllBuckets[$lookupKey] } else { $null } $bUpdatedGlobal = if ($globalBucket) { $globalBucket.Updated } else { 0 } $bTotalGlobal = if ($globalBucket) { $globalBucket.Count } else { $b.Count } $bNotUpdatedGlobal = $bTotalGlobal - $bUpdatedGlobal $bucketSummary += "<tr><td style='font-size:.8em'>$bid><4 /td><td style='text-align:right; font-weight:bold'>$bTotalGlobal><8 /td><td style='text-align:right; krāsa:#28a745; font-weight:bold'>$bUpdatedGlobal><2 /td><td style='text-align:right; krāsa:#dc3545; font-weight:bold'>$bNotUpdatedGlobal><6 /td><td><9 $mfr</td></tr>'n" } $bucketSummary += "</tbody></table></div>" } # IERĪCES INFORMĀCIJA: PIRMĀS N RINDAS KĀ PLAKANS SARAKSTS $slice = $data | Select-Object -First $MaxRows foreach ($d $slice) { $conf = $d.ConfidenceLevel $confBadge = if ($conf -match "Augsts") { '<span class="badge badge-success">High Conf><2 /span>' } elseif ($conf -match "Not Sup") { '<span class="badge badge-danger">Netiek atbalstīts><6 /span>' } elseif ($conf -match "Under") { '<span class="badge badge-info">Under Obs><0 /span>' } elseif ($conf -match, "Pauzēts") { '<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 atjaunināts</span>' } elseif ($d.UEFICA2023Error) { '><04 span class="badge badge-danger"><05 Kļūda</span>' } else { '><08 span class="badge badge-warning"><09 Gaida><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" } } nozveja { } } IF ($totalCount -EQ 0) { return "><44 div style='padding:20px; krāsa:#888; font-style:italic'><45 Šajā kategorijā nav nevienas ierīces.><46 /div>" } $showing = [matemātika]::Min($MaxRows, $totalCount) $header = "><48 div style='margin:5px 0; fonta izmērs:.85em; krāsa:#666'><49 Kopā: $($totalCount.ToString("N0")) ierīces" ja ($CsvFileName) { $header += " | ><50 a href='$CsvFileName' style='color:#1a237e; fonta biezums:treknraksts'>📄 Lejupielādēt pilnu CSV failu programmai Excel><3 /a>" } $header += "><55 /div>" $deviceHeader = "><57 h3 style='font-size:.95em; krāsa:#333; margin:10px 0 5px'><58 Ierīces informācija (rāda pirmo $showing)><59 /h3>" $deviceTable = "><61 div style='max-height:500px; overflow-y:auto'><tabula><thead><tr><th><0 HostName><1 /th><th><4 Manufacturer><5 /th><th><8 Model><9 /th><th><2 Confidence><3 /th><th><6 Status><7 /th><th><0 Error><1 /th><th><4 BucketId><5 /th></tr></thead><tbody><2 $deviceRows><3 /tbody></table></div>" return "$header$bucketSummary$deviceHeader$deviceTable" } # Veidojiet iekļautās tabulas no JSON failiem, kas jau atrodas diskā, saistot ar CSV $tblErrors = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "errors.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_errors_$timestamp.csv" $tblKI = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "known_issues.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_known_issues_$timestamp.csv" $tblKEK = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "missing_kek.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_missing_kek_$timestamp.csv" $tblNotUpd = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "not_updated.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_not_updated_$timestamp.csv" $tblTaskDis = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "task_disabled.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_task_disabled_$timestamp.csv" $tblTemp = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "temp_failures.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_temp_failures_$timestamp.csv" $tblPerm = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "perm_failures.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_perm_failures_$timestamp.csv" $tblUpdated = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "updated_devices.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_updated_devices_$timestamp.csv" $tblActionReq = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "action_required.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_action_required_$timestamp.csv" $tblUnderObs = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "under_observation.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_under_observation_$timestamp.csv" $tblNeedsReboot = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "needs_reboot.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_needs_reboot_$timestamp.csv" $tblSBOff = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "secureboot_off.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_secureboot_off_$timestamp.csv" $tblRolloutIP = Build-InlineTable -JsonPath (pievienošanās ceļš $dataDir "rollout_inprogress.json") -MaxRows $maxInlineRows -CsvFileName "SecureBoot_rollout_inprogress_$timestamp.csv" # Pielāgota tabula gaidošam atjauninājumam — ietver kolonnas UEFICA2023Status un UEFICA2023Error $tblUpdatePending = "" $upJsonPath = Join-Path $dataDir "update_pending.json" Ja (testa ceļš $upJsonPath) { Izmēģiniet { $upData = Get-Content $upJsonPath -Raw | ConvertFrom-JSON $upCount = $upData.Count IF ($upCount -gt; 0) { $upHeader = "<div style='margin:5px 0; fonta izmērs:.85em; krāsa:#666'>Kopā: $($upCount.ToString("N0")) ierīces | <a href='SecureBoot_update_pending_$timestamp.csv' style='color:#1a237e; fonta biezums:treknraksts'>📄 Lejupielādēt pilnu CSV failu programmai Excel><4 /a></div>" $upRows = "" $upSlice = $upData | Select-Object -Pirmais $maxInlineRows foreach ($d $upSlice) { $uefiSt = if ($d.UEFICA2023Status) { $d.UEFICA2023Status } else { '<span style="color:#999">null><0 /span>' } $uefiErr = if ($d.UEFICA2023Error) { "<span style='color:#dc3545'>$($d.UEFICA2023Error)</span>" } else { '-' } $policyVal = if ($d.AvailableUpdatesPolicy) { $d.AvailableUpdatesPolicy } else { '-' } $wincsVal = if ($d.WinCSKeyApplied) { '<span class="badge badge-success">Yes><8 /span>' } else { '-' } $upRows += "<tr><td><3 $($d.HostName)</td><td><7 $($d.WMI_Manufacturer)</td><td><1 $($d.WMI_Model)</td><td><5 $uefiSt><6 /td><td><9 $uefiErr><50 /td><td><53 $policyVal><54 /td><td><57 $wincsVal><58 /td><td style='font-size:.75em'>$($d.BucketId)</td></tr><65 'n" } $upShowing = [matemātika]::Min($maxInlineRows, $upCount) $upDevHeader = "<h3 style='font-size:.95em; krāsa:#333; margin:10px 0 5px'>Ierīces informācija (rāda pirmo $upShowing)</h3>" $upTable = "<div style='max-height:500px; Overflow-y:auto'><tabula><thead><tr><th><9 HostName><0 /th><th><3 Manufacturer><4 /th><th><7 Model><8 /th><th><1 UEFICA2023Status><2 /th><th><5 UEFICA2020 23Error><6 /th><th><9 Policy</th><th>WinCS atslēga</th><th>BucketId</th></tr></thead><tbody><5 $upRows><6 /tbody></table></div>" $tblUpdatePending = "$upHeader$upDevHeader$upTable" } citādi { $tblUpdatePending = "<div style='padding:20px; krāsa:#888; font-style:italic'>Šajā kategorijā nav nevienas ierīces.</div>" } } noķert { $tblUpdatePending = "<div style='padding:20px; krāsa:#888; font-style:italic'>Šajā kategorijā nav nevienas ierīces.</div>" } } citādi { $tblUpdatePending = "<div style='padding:20px; krāsa:#888; font-style:italic'>Šajā kategorijā nav nevienas ierīces.</div>" } # Sertifikāta derīguma termiņa atpakaļskaitīšana $certToday = get-date $certKekExpiry = [datetime]"2026-06-24" $certUefiExpiry = [datetime]"2026-06-27" $certPcaExpiry = [datetime]"2026-10-19" $daysToKek = [matemātika]::Max(0, ($certKekExpiry - $certToday). dienas) $daysToUefi = [matemātika]::Max(0, ($certUefiExpiry - $certToday). dienas) $daysToPca = [matemātika]::Max(0, ($certPcaExpiry - $certToday). dienas) $certUrgency = if ($daysToKek -lt 30) { '#dc3545' } elseif ($daysToKek -lt 90) { '#fd7e14' } else { '#28a745' } # Būvējuma ražotāja diagrammas iekļautie dati (Top 10 pēc ierīču skaita) $mfrSorted = $stMfrCounts.GetEnumerator() | Sort-Object { $_. Vērtība.Kopsumma } -dilstošā secībā | Select-Object -Pirmie 10 $mfrChartTitle = if ($stMfrCounts.Count -le 10) { "Pēc ražotāja" } else { "Top 10 Manufacturers" } $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 "," # Build manufacturer table $mfrTableRows = "" $stMfrCounts.GetEnumerator() | Sort-Object { $_. Vērtība.Kopsumma } -dilstošā secībā | ForEach-Object { $mfrTableRows += "<tr><td><7 $($_. Atslēga)</td><td>$($_. Value.Total.ToString("N0"))</td><td>$($_. Value.Updated.ToString("N0"))</td><td>$($_. Value.HighConf.ToString("N0"))><0 /td><td>$($_. Value.ActionReq.ToString("N0"))><4 /td></tr>'n" } # HTML close-tag fragmenti: sadalīti daļās, lai tīmekļa CMS platformas nebūtu # interpretējiet tos kā īstus HTML un ievietojiet tiem apkārt neredzamas unikoda rakstzīmes.$endScript = '</scr' + 'ipt>' $endStyle = '</sty' + 'le>' $endHead = '</viņš' + 'ad>' $endBody = '</bo' + 'dy>' $endHtml = '</ht' + 'ml>' $htmlContent = @" <! DOCTYPE html> <html lang="lv"> <galvas> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Drošās palaišanas sertifikāta statusa informācijas panelis</title> <script src="https://cdn.jsdelivr.net/npm/chart.js">$endScript <stila> *{box-sizing:border-box; rezerve:0; polsterējums:0} body{font-family:'Segoe UI',Tahoma,sans-serif; fons:#f0f2f5; krāsa:#333} .header{background:linear-gradient(135deg,#1a237e,#0d47a1); krāsa:#fff; polsterējums:20px 30px} .header h1{font-size:1.6em; margin-bottom:5px} .header .meta{font-size:.85em; necaurspīdīgums:.9} .container{maksimālais platums:1400px; margin:0 auto; polsterējums:20px} .cards{displejs:režģis; grid-template-columns:repeat(auto-fill,minmax(170px,1fr)); atstarpe:12px; margin:20px 0} .card{fons:#fff; robežas rādiuss:10px; polsterējums: 15px; kaste-ēna:0 2px 8px rgba (0,0,0,.08); border-left:4px solid #ccc;transition:transform .2s} .card:hover{transform:translateY(-2px); kaste-ēna:0 4px 15px rgba(0,0,0,.12)} .card .value{fonta lielums:1.8em; fonta biezums:700} .card .label{fonta lielums:.8em; krāsa:#666; margin-top:4px} .card .pct{fonta lielums:.75em; krāsa:#888} .section{fons:#fff; robežas rādiuss:10px; polsterējums:20px; piemale:15px 0; kaste-ēna:0 2px 8px rgba(0,0,0,.08)} .section h2{font-size:1.2em; krāsa:#1a237e; margin-bottom:10px; kursors:rādītājs; user-select:none} .section h2:hover{text-decoration:underline} .section-body{display:none} .section-body.open{display:block} .charts{attēlojums:režģis; režģa veidnes kolonnas:1fr 1fr; atstarpe:20px; margin:20px 0} .chart-box{fons:#fff; robežas rādiuss:10px; polsterējums:20px; kaste-ēna:0 2px 8px rgba(0,0,0,.08)} tabula{platums:100%; border-collapse:collapse; fonta lielums:.85em} th{fons:#e8eaf6; polsterējums:8px 10px; text-align:left; pozīcija: lipīgs; augšā:0; z-indekss:1} td{padding:6px 10px; border-bottom:1px solid #eee} tr:hover{fons:#f5f5f5} .badge{displejs:inline-block; polsterējums:2px 8px;robežas rādiuss:10px; fonta izmērs:.75em; fonta biezums:700} .badge-success{fons:#d4edda; krāsa:#155724} .badge-danger{fons:#f8d7da; krāsa:#721c24} .badge-warning{fons:#fff3cd; krāsa:#856404} .badge-info{fons:#d1ecf1; krāsa:#0c5460} .top-link{float:right; fonta izmērs:.8em; krāsa:#1a237e; teksta dekorācija:nav} .footer{text-align:center; polsterējums:20px; krāsa:#999; fonta lielums:.8em} a{color:#1a237e} $endStyle $endHead <ķermeņa> <div class="header"> <h1>drošās palaišanas sertifikāta statusa informācijas panelis</h1> <div class="meta">Ģenerēts: $($stats. ReportGeneratedAt) | Kopējais ierīču skaits: $($c.Total.ToString("N0")) | Unikāli intervāli: $($stAllBuckets.Count)</div> </div> <div class="container">
<!-- KPI kartes — uz kurām var noklikšķināt, saistītas ar sadaļām --> <div class="cards"> <class="card" href="#s-nu" onclick="openSection('d-nu')" style="border-left-color:#dc3545; teksta dekorācija: nav; position:relative"><div style="position:absolute; augšpusē: 8 pikseļi; pa labi: 8px; fons:#dc3545; krāsa:#fff; polsterējums:1px 6px; robežas rādiuss:8px; fonta izmērs:.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)% — JĀVEIC DARBĪBA><0 /div></a><3 <class="card" href="#s-upd" onclick="openSection('d-upd')" style="border-left-color:#28a745; teksta dekorācija: nav; pozīcija:relatīva"><div style="pozīcija:absolūta; augšpusē: 8 pikseļi; pa labi: 8px; fons:#28a745; krāsa:#fff; polsterējums:1px 6px; robežas rādiuss:8px; fonta izmērs:.65em; font-weight:700">PRIMARY><8 /div><div class="value" style="color:#28a745">$($c.Updated.ToString("N0"))</div><div class="label">atjaunināts><6 /div><div class="pct">$($stats. PercentCertUpdated)%</div></a><3 <class="card" href="#s-sboff" onclick="openSection('d-sboff')" style="border-left-color:#6c757d; teksta dekorācija: nav; position:relative"><div style="position:absolute; augšpusē: 8 pikseļi; pa labi: 8px; fons:#6c757d; krāsa:#fff; polsterējums:1px 6px; robežas rādiuss:8px; fonta izmērs:.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})% - Ārpus tvēruma><0 /div></a><3 <a 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})% - gaida restartēšanu><6 /div></a><9 <a 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">atjauninājums gaida</div><div class="pct">$(if($c.Total -gt; 0){[math]::Round(($c.UpdatePending/$c.Total)*100,1)}else{0})% — piemērota politika/WinCS, gaida atjauninājumu><2 /div></a><5 <a 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">Notiek ieviešana><4 /div><div class="pct">$(if($c.Total -gt; 0){[math]::Round(($c.RolloutInProgress/$c.Total)*100,1)}else{0})%</div></a><11 <a 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">Augsta ticamības><20 /div><div class="pct">$($stats. PercentHighConfidence)% — drošs izvēršanai><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">Nepieciešamā darbība><2 /div><div class="pct">$($stats. PercentActionRequired)% — jāpārbauda><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">Pakļauts riskam><68 /div><div class="pct">$($stats. PercentAtRisk)% — līdzīga neveiksmīgajiem><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">uzdevums atspējots><4 /div><div class="pct">$(if($c.Total -gt; 0){[math]::round(($c.TaskDisabled/$c.Total)*100,1)}else{0})% - bloķēts><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. Paused</div><div class="pct">$(if($c.Total -gt; 0){[math]::Round(($c.TempPaused/$c.Total)*100,1)}else{0})%</div></a> <class="card" href="#s-ki" onclick="openSection('d-ki')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($c.WithKnownIssues.ToString("N0"))</div><div class="label">Zināmās problēmas><6 /div><div class="pct">$(if($c.Total -gt; 0){[math]::round(($c.WithKnownIssues/$c.Total)*100,1)}else{0})%</div></a><3 <a 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">trūkst KEK</div><div class="pct">$(if($c.Total -gt; 0){[math]::round(($c.WithMissingKEK/$c.Total)*100,1)}else{0})%</div></a> <class="card" href="#s-err" onclick="openSection('d-err')" style="border-left-color:#dc3545; text-decoration:none"><div class="value" style="color:#dc3545">$($c.WithErrors.ToString("N0"))</div><div class="label">ar kļūdām</div><div class="pct"><1 $($stats. PercentWithErrors)% — UEFI kļūdas</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. Kļūmes</div><div class="pct">$(if($c.Total -gt; 0){[math]::Round(($c.TempFailures/$c.Total)*100,1)}else{0})%</div></a> <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>
<!-- izvietošanas ātrums & sertifikāta derīguma termiņš --> <div id="s-velocity" style="display:grid; režģa veidnes kolonnas:1fr 1fr; atstarpe:20px; margin:15px 0"> <div class="section" style="margin:0"> <h2>📅 Izvietošanas ātrums</h2> ><2 div class="section-body open"><3 ><4 div style="font-size:2.5em; fonta biezums:700; color:#28a745"><5 $($c.Updated.ToString("N0"))><6 /div> ><8 div style="color:#666"><9 ierīces ir atjauninātas no $($c.Total.ToString("N0"))</div> <div style="margin:10px 0; fons:#e8eaf6; augstums:20px; robežas rādiuss:10px; overflow:hidden"><div style="background:#28a745; augstums:100%; platums:$($stats. PercentCertUpdated)%; border-radius:10px"></div></div> <div style="font-size:.8em; Krāsa:#888">$($stats. PercentCertUpdated)% complete</div> <div style="margin-top:10px; polsterējums: 10px; fons:#f8f9fa; robežas rādiuss:8px; font-size:.85em"> <div><strong>Atlikušais:</strong> $($stNotUptodate.ToString("N0")) ierīcēm nepieciešama darbība</div> <div><strong>Blocking:</strong> $($c.WithErrors + $c.PermFailures + $c.TaskDisabledNotUpdated) devices (errors + permanent + task disabled)</div> <div><strong>Droši izvietot:</strong> $($stSafeList.ToString("N0")) ierīces (tas pats intervāls kā sekmīgs)</div> $velocityHtml </div> </div> </div> <div class="section" style="margin:0; border-left:4px solid #dc3545"> <h2 style="color:#dc3545">⚠ Sertifikātu derīguma termiņa atskaite</h2> <div class="section-body open"> <div style="display:flex; atstarpe: 15 pikseļi; margin-top:10px"> <div style="text-align:center; polsterējums: 15px; robežas rādiuss:8px; minimālais platums: 120 pikseļi; fons:lineārs gradients (135 grādi, #fff5f5, #ffe0e0); robeža:2px cieta #dc3545; flex:1"> <div style="font-size:.65em; krāsa:#721c24; teksta transformācija: lielie burti; font-weight:bold">⚠ PIRMAIS, KURA DERĪGUMS BEIDZAS</div> ><4 div style="font-size:.85em; fonta biezums:treknraksts; krāsa:#dc3545; margin:3px 0"><5 KEK CA 2011</div> ><8 div id="daysKek" style="font-size:2.5em; fonta biezums:700; krāsa:#dc3545; line-height:1"><9 $daysToKek><0 /div><1 <div style="font-size:.8em; color:#721c24">dienas (2026. gada 24. jūnijs)</div><5 </div><7 <div style="text-align:center; polsterējums: 15px; robežas rādiuss:8px; minimālais platums: 120 pikseļi; fons:lineārs gradients (135 grādi, #fffef5, #fff3cd); robeža:2px cieta #ffc107; flex:1"> ><00 div style="font-size:.65em; krāsa:#856404; teksta transformācija: lielie burti; font-weight:bold"><01 UEFI CA 2011</div> ><04 div id="daysUefi" style="font-size:2.2em; fonta biezums:700; krāsa:#856404; līnijas augstums:1; margin:5px 0"><05 $daysToUefi</div> ><08 div style="font-size:.8em; color:#856404"><09 dienas (2026. gada 27. jūnijs)><10 /div> ><12 /div> ><14 div style="text-align:center; polsterējums: 15px; robežas rādiuss:8px; minimālais platums: 120 pikseļi; fons:lineārs gradients (135 grādi, #f0f8ff, #d4edff); robeža:2px cieta #0078d4; flex:1"><15 ><16 div style="font-size:.65em; krāsa:#0078d4; teksta transformācija: lielie burti; font-weight:bold"><17 Windows PCA</div> ><20 div id="daysPca" style="font-size:2.2em; fonta biezums:700; krāsa:#0078d4; līnijas augstums:1; margin:5px 0"><21 $daysToPca><2 /div><3 ><24 div style="font-size:.8em; color:#0078d4"><25 dienas (2026. gada 19. okt.)><26 /div><7 ><28 /div><9 ><30 /div><1 ><32 div style="margin-top:15px; polsterējums: 10px; fons:#f8d7da; robežas rādiuss:8px; fonta izmērs:.85em; border-left:4px solid #dc3545"><33 ><34 stipra>⚠ KRITISKA:><37 /strong> Visas ierīces ir jāatjaunina pirms sertifikāta derīguma beigām. Ierīces, kas nav atjauninātas līdz noteiktajam termiņam, nevar lietot turpmākos drošības atjauninājumus palaišanas pārvaldniekam un drošajai palaišanai pēc derīguma beigām.</div> </div> </div> </div>
<!-- diagrammas --> <div class="charts"> <div class="chart-box"><h3>izvietošanas statuss</h3><canvas id="deployChart" height="200"></canvas></div><5 <div class="chart-box"><h3><9 $mfrChartTitle</h3><canvas id="mfrChart" height="200"></canvas></div> </div>
$(if ($historyData.Count -ge 1) { "<!-- vēsturisko tendenču diagramma --> <div class='section'> <h2 onclick='"toggle('d-trend')'">📈 Atjaunināšanas norise laika gaitā <class='top-link' href='#'>↑ top</a></h2> <div id='d-trend' class='section-body open'> <canvas id='trendChart' height='120'></canvas> <div style='font-size:.75em; krāsa:#888; margin-top:5px'>Nepārtrauktās līnijas = faktiskie dati$(if ($historyData.Count -ge 2) { " | Punktēta līnija = projicēta (eksponenciālā dubultošanās: 2→4→8→16... ierīces uz vilni)" } citādi { " | Rīt vēlreiz palaist apkopojumu, lai redzētu tendenču līnijas un projekciju" })</div> </div> </div>" })
<!-- CSV lejupielādes --> <div class="section"> <h2 onclick="toggle('dl-csv')">📥 Lejupielādēt pilnos datus (CSV programmai Excel) <class="top-link" href="#">Top</a></h2> <div id="dl-csv" class="section-body open" style="display:flex; flex-wrap:ietīšana; atstarpe:5px"> <a href="SecureBoot_not_updated_$timestamp.csv" style="display:inline-block; fons:#dc3545; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">nav atjaunināts ($($stNotUptodate.ToString("N0")))</a> <a href="SecureBoot_errors_$timestamp.csv" style="display:inline-block; fons:#dc3545; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">kļūdas ($($c.WithErrors.ToString("N0")))</a><2 <a href="SecureBoot_action_required_$timestamp.csv" style="display:inline-block; fons:#fd7e14; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">nepieciešamā darbība ($($c.ActionReq.ToString("N0")))</a><6 <a href="SecureBoot_known_issues_$timestamp.csv" style="display:inline-block; fons:#dc3545; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">zināmās problēmas ($($c.WithKnownIssues.ToString("N0")))</a> <a href="SecureBoot_task_disabled_$timestamp.csv" style="display:inline-block; fons:#dc3545; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">uzdevums atspējots ($($c.TaskDisabled.ToString("N0")))</a> <a href="SecureBoot_updated_devices_$timestamp.csv" style="display:inline-block; fons:#28a745; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">atjaunināts ($($c.Updated.ToString("N0")))</a> <a href="SecureBoot_Summary_$timestamp.csv" style="display:inline-block; fons:#6c757d; krāsa:#fff; polsterējums:6px 14px; robežas rādiuss:5px; teksta dekorācija: nav; font-size:.8em">kopsavilkums</a> <div style="width:100%; fonta izmērs:.75em; krāsa:#888; margin-top:5px">CSV faili tiek atvērti programmā Excel. Pieejams, viesojot tīmekļa serverī.</div> </div> </div>
<!-- ražotāja sadalījums --> <div class="section"> <h2 onclick="toggle('mfr')">pēc ražotāja <a class="top-link" href="#">Top</a></h2><1 <div id="mfr" class="section-body open"> <tabula><thead><tr><th><1 Manufacturer><2 /th><th><5 Kopā><6 /th><th><9 Atjaunināts><0 /th><th><3 Augsta ticamības><4 /th><th><7 Nepieciešamā darbība><8 /th></tr></thead><3 <tbody><5 $mfrTableRows><6 /tbody></table><9 </div><1 </div>
<!-- ierīču sadaļas (pirmās 200 iekļautās + CSV lejupielādes) --> <div class="section" id="s-err"> <h2 onclick="toggle('d-err')">🔴 Ierīces ar kļūdām ($($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">🔴 Zināmās problēmas ($($c.WithKnownIssues.ToString("N0"))) <a 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ūkst KEK — notikums 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">🟠 Nepieciešamā darbība ($($c.ActionReq.ToString("N0"))) <a 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">🔵 Under observation ($($c.UnderObs.ToString("N0"))) <a 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">🔴 Nav atjaunināts ($($stNotUptodate.ToString("N0"))) <a 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">🔴 Uzdevums atspējots ($($c.TaskDisabled.ToString("N0"))) >↑ 5 a 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">🔴 Pagaidu kļūmes ($($c.TempFailures.ToString("N0"))) <a 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">🔴 Pastāvīgas kļūmes / netiek atbalstīts ($($c.PermFailures.ToString("N0"))) <a 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">⏳ Gaida atjauninājumu ($($c.UpdatePending.ToString("N0"))) — lietota politika/WinCS, gaida atjaunināšanu <a class="top-link" href="#">↑ Top</a></h2> <div id="d-upd-pend" class="section-body"><p style="color:#666; margin-bottom:10px">Ierīces, kurās tiek lietota AvailableUpdatesPolicy vai WinCS atslēga, bet UEFICA2023Status joprojām ir Nesākts, Nenotiek izpilde vai Null.</p>$tblUpdatePending</div> </div> <div class="section" id="s-rip"> <h2 onclick="toggle('d-rip')" style="color:#17a2b8">🔵 Notiek ieviešana ($($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"))) - Ārpus tvēruma <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">🟢 Atjauninātās ierīces ($($c.Updated.ToString("N0"))) <a 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">🔄 Atjaunināts - nepieciešama atsāknēšana ($($c.NeedsReboot.ToString("N0"))) <a class="top-link" href="#">↑ Top</a></h2> <div id="d-nrb" class="section-body">$tblNeedsReboot</div> </div>
<div class="footer">Drošās palaišanas sertifikāta ieviešanas informācijas panelis | Ģenerēti $($stats. ReportGeneratedAt) | Straumēšanas režīms | Maksimālā atmiņa: ${stPeakMemMB} MB</div> </div><!-- /container -->
<skripta> funkcija toggle(id){var e=document.getElementById(id); e.classList.toggle('atvērt')} funkcija 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:['Atjaunināts','Gaida atjauninājumu','Augsta ticamība','Novērošanā','Nepieciešama darbība','Temp. Pauzēts','Netiek atbalstīts','SecureBoot OFF','' With Errors'],datasets:[{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:'Gaida atjauninājumu',data:[$mfrUpdatePending],backgroundColor:'#6f42c1'},{label:'Augsta ticamība',data:[$mfrHighConf],backgroundColor:'#20c997'},{label:'Novērojumā',data:[$mfrUnderObs],backgroundColor:'#17a2b8'},{label:'Nepieciešama darbība',dati:[$mfrActionReq],backgroundColor:'#fd7e14'},{ label:'Temp. Pauzēts',dati:[$mfrTempPaused],backgroundColor:'#6c757d'},{label:'Netiek atbalstīts',dati:[$mfrNotSupported],backgroundColor:'#721c24'},{label:'SecureBoot OFF',dati:[$mfrSBOff],backgroundColor:'#adb5bd'},{label:'With errors',data:[$mfrWithErrors],backgroundColor:'#dc3545'}]},options:{responsive:true,scales:{x:{stacked:true},y:{stacked:true}},plugins:{legend:{position:'top'}}}}); Vēsturisko tendenču diagramma if (document.getElementById('trendChart')) { var allLabels = [$allChartLabels]; var actualUpdated = [$trendUpdated]; var actualNotUpdated = [$trendNotUpdated]; var faktiskais_kopsumma = [$trendTotal]; var projData = [$projDataJS]; var projNotUpdData = [$projNotUpdJS]; var histLen = actualUpdated.length; var projLen = projData.length; var paddedUpdated = actualUpdated.concat(Array(projLen).fill(null)); var paddedNotUpdated = actualNotUpdated.concat(Array(projLen).fill(null)); var paddedTotal = actualTotal.concat(Array(projLen).fill(null)); var projLine = Array(histLen).fill(null); var projNotUpdLine = Array(histLen).fill(null); if (projLen > 0) { projLine[histLen-1] = actualUpdated[histLen-1]; projLine = projLine.concat(projData); projNotUpdLine[histLen-1] = actualNotUpdated[histLen-1]; projNotUpdLine = projNotUpdLine.concat(projNotUpdData); } var datu kopas = [ {label:'Atjaunināts',data:paddedUpdated,borderColor:'#28a745',backgroundColor:'rgba(40,167,69,0.1)',fill:true,tension:0.3,borderWidth:2}, {label:'Nav atjaunināts',data:paddedNotUpdated,borderColor:'#dc3545',backgroundColor:'rgba(220,53,69,0.1)',fill:true,tension:0.3,borderWidth:2}, {label:'Total',data:paddedTotal,borderColor:'#6c757d',borderDash:[5,5],fill:false,tension:0,pointRadius:0,borderWidth:1} ]; if (projLen > 0) { datasets.push({label:'Projected Updated (2x doubleubling)',data:projLine,borderColor:'#28a745',borderDash:[8,4],borderWidth:3,fill:false,tension:0.3,pointRadius:3,pointStyle:'triangle'}); datasets.push({label:'Projected Not Updated',data:projNotUpdLine,borderColor:'#dc3545',borderDash:[8,4],borderWidth:3,fill:false,tension:0.3,pointRadius:3,pointStyle:'triangle'}); } new Chart(document.getElementById('trendChart'),{type:'line',data:{labels:allLabels,datasets:datasets},options:{responsive:true,scales:{y:{beginAtZero:true,title:{display:true,text:'Devices'}},x:{title:{display:true,text:'Date'}}},plugins:{legend:{position:'top'},title:{display:true,text:'Secure boot update progress over time'}); } Dinamiskā atpakaļskaitīšana (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))})(); $endScript $endBody $endHtml "@ [System.IO.File]::WriteAllText($htmlPath, $htmlContent, [System.Text.UTF8Encoding]::new($false)) # Vienmēr saglabājiet stabilu "jaunāko" kopiju, lai administratoriem nebūtu jāseko laikspiedoliem $latestPath = Join-Path $OutputPath "SecureBoot_Dashboard_Latest.html" Copy-Item $htmlPath $latestPath — spēks $stTotal = $streamSw.Elapsed.TotalSeconds # Saglabāt faila manifestu inkrementālajam režīmam (ātra neizmaiņu noteikšana nākamajā palaišanas reizē) IF ($IncrementalMode vai $StreamingMode) { $stManifestDir = Join-Path $OutputPath ".cache" if (-not (testa ceļš $stManifestDir)) { New-Item -itemType directory -path $stManifestDir -force | Out-Null } $stManifestPath = Join-Path $stManifestDir "StreamingManifest.json" $stNewManifest = @{} Write-Host "Saving file manifest for incremental mode..." -ForegroundColor Grey foreach ($jf $jsonFiles) { $stNewManifest[$jf. FullName.ToLowerInvariant()] = @{ LastWriteTimeUtc = $jf. LastWriteTimeUtc.ToString("o") Lielums = $jf. Garums } } Save-FileManifest -manifest $stNewManifest -path $stManifestPath Write-Host " Saglabāts $($stNewManifest.Count) failu manifests" -ForegroundColor DarkGray } # SAGLABĀŠANAS TĪRĪŠANA # Orchestrator atkārtoti lietojama mape (Aggregation_Current): saglabāt tikai pēdējo palaišanu (1) # Administrēšanas rokasgrāmata runs / citas mapes: saglabāt pēdējos 7 braucienus # Kopsavilkums CSV nekad netiek izdzēsti - tie ir niecīgi (~ 1 KB) un ir tendenču vēstures rezerves avots $outputLeaf = Split-Path $OutputPath -Leaf $retentionCount = if ($outputLeaf -eq 'Aggregation_Current') { 1 } else { 7 } # Failu prefiksi droši tīrīt (īslaicīgi momentuzņēmumi) $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_' ) # Atrast visus unikālos laikspiedolus tikai no tīrāmiem failiem $cleanableFiles = Get-ChildItem $OutputPath -File -EA SilentlyContinue | Where-Object { $f = $_. Vārds; ($cleanupPrefixes | Where-Object { $f.StartsWith($_) }). Skaits -gt; 0 } $allTimestamps = @($cleanableFiles | ForEach-Object { Ja ($_. Name -match '(\d{8}-\d{6})') { $Matches[1] } } | Sort-Object -Unique -Descending) IF ($allTimestamps.Count, -gt; $retentionCount) { $oldTimestamps = $allTimestamps | Select-Object -Skip $retentionCount $removedFiles = 0; $freedBytes = 0 foreach ($oldTs $oldTimestamps) { foreach ($prefix $cleanupPrefixes) { $oldFiles = Get-ChildItem $OutputPath -file -filter "${prefix}${oldTs}*" -EA SilentlyContinue foreach ($f $oldFiles) { $freedBytes += $f.Garums Remove-Item $f.FullName -Force -EA SilentlyContinue $removedFiles++ } } } $freedMB = [matemātika]::Round($freedBytes / 1MB, 1) Write-Host "Retention cleanup: removed $removedFiles files from $($oldTimestamps.Count) old runs, released ${freedMB} MB (keeping last $retentionCount + all Summary/NotUptodate CSVs)" -ForegroundColor DarkGray } Write-Host "'n$("=" * 60)" -ForegroundColor Cyan Write-Host "STREAMING AGGREGATION COMPLETE" -ForegroundColor Green Write-Host ("=" * 60) -ForegroundColor Cyan Write-Host " Ierīču kopskaits: $($c.Total.ToString("N0"))" -ForegroundColor White Write-Host " NAV ATJAUNINĀTS: $($stNotUptodate.ToString("N0")) ($($stats. PercentNotUptodate)%)" -ForegroundColor $(if ($stNotUptodate -gt; 0) { "Dzeltena" } citādi { "Zaļa" }) Write-Host " Atjaunināts: $($c.Updated.ToString("N0")) ($($stats. PercentCertUpdated)%)" -ForegroundColor Green Write-Host " ar kļūdām: $($c.WithErrors.ToString("N0"))" -ForegroundColor $(if ($c.WithErrors -gt; 0) { "Red" } else { "Green" }) Write-Host " Maksimālā atmiņa: ${stPeakMemMB} MB" -ForegroundColor ciāna Write-Host " Laiks: $([matemātika]::Round($stTotal/60,1)) min" -Priekšplāna krāsa balta Write-Host " Dashboard: $htmlPath" -ForegroundColor White return [PSCustomObject]$stats } #endregion STRAUMĒŠANAS REŽĪMU } citādi { Write-Error "Ievades ceļš nav atrasts: $InputPath" Izeja 1 }