Copiați și lipiți acest script eșantion și modificați după cum este necesar pentru mediul dvs.:

<# . SINOPSIS     Orchestrator de lansare a bootului securizat continuu care rulează până la finalizarea implementării.

.DESCRIPTION     Acest script oferă automatizare completă integrală pentru lansarea certificatului secure boot:     1.      Generează unde de lansare pe baza datelor de agregare     2. Creează grupuri AD și GPO pentru fiecare val     3. Monitoare pentru actualizări de dispozitiv (evenimentul 1808)     4. Detectează bucketurile blocate (dispozitive inaccesibile)     5. Progresează automat la următorul val     6. Rulează până când sunt actualizate TOATE dispozitivele eligibile          Criterii de finalizare:     - Nu a mai rămas niciun dispozitiv în: acțiune necesară, nivel înalt de încredere, observație, temporar în pauză     - În afara domeniului (prin proiectare): Neacceptat, Bootare sigură dezactivată     - Rulează continuu până la finalizare - fără limită      de undă arbitrară     Strategie de implementare:     - ÎNCREDERE MAXIMĂ: toate dispozitivele din primul val (sigur)     - ACȚIUNE NECESARĂ: Dubluri progresive (1→2→4→8...)          Logică de blocare:     - După MaxWaitHours, orchestrator pings dispozitive care nu au actualizat     - Dacă dispozitivul este inaccesibil (ping nu reușește), → bucket este BLOCAT pentru investigație     - Dacă dispozitivul este accesibil, dar nu este actualizat → continua să așteptați (poate fi necesară repornirea)     - Bucketurile blocate sunt excluse până când administratorul le      deblochează     Deblocare automată:     - Dacă un dispozitiv dintr-un bucket blocat mai târziu se afișează ca actualizat (evenimentul 1808),       bucket-ul este deblocat automat și continuă rollout     - Aceasta gestionează dispozitivele care au fost temporar offline, dar au revenit          Urmărire dispozitiv:     - Urmărește dispozitivele după numele de gazdă (presupune că numele nu se modifică în timpul lansării)     - Notă: colecția JSON nu include un ID unic de computer; adăugați unul pentru o urmărire mai bună

.PARAMETER AggregationInputPath     Calea către datele brute ale dispozitivului JSON (de la Detectare script)

.PARAMETER ReportBasePath     Calea de bază pentru rapoartele de agregare

.PARAMETER TargetOU     Nume distins al OU pentru legarea GPO-urilor.Opțional - dacă nu se specifică, GPO este legat la rădăcina domeniului pentru acoperirea la nivel de domeniu.Filtrarea grupurilor de securitate asigură că doar dispozitivele vizate primesc politica.

.PARAMETER MaxWaitHours     Ore de așteptat ca dispozitivele să se actualizeze înainte de a verifica accesibilitatea.După această dată, dispozitivele care nu au fost actualizate sunt ping.Dispozitivele inaccesibile determină blocarea recipientului.Implicit: 72 (3 zile)

.PARAMETER PollIntervalMinutes     Minute între verificările de stare. Implicit: 1440 (1 zi)

.PARAMETER AllowListPath     Calea către un fișier care conține nume de gazdă pentru ALLOW pentru implementare (lansare pentru audiență specifică).Acceptă .txt (un nume de gazdă pe linie) sau .csv (cu coloana Hostname/ComputerName/Name).Atunci când se specifică, DOAR aceste dispozitive vor fi incluse în implementare.BlockList este încă aplicat după AllowList.

.PARAMETER AllowADGroup     Numele unui grup de securitate AD care conține conturi de computer pentru ALLOW.Exemplu: "SecureBoot-Pilot-Computers" sau "Wave1-Devices"     Atunci când se specifică, doar dispozitivele din acest grup vor fi incluse în implementare.Combinați cu AllowListPath atât pentru direcționarea bazată pe fișiere, cât și pentru direcționarea bazată pe AD.

.PARAMETER ExclusionListPath     Calea către un fișier care conține nume de gazdă pentru EXCLUDE din implementare (dispozitive VIP/executive).Acceptă .txt (un nume de gazdă pe linie) sau .csv (cu coloana Hostname/ComputerName/Name).Aceste dispozitive nu vor fi incluse niciodată în niciun val de lansare.BlockList se aplică DUPĂ filtrarea AllowList.     . PARAMETER ExcludeADGroup     Numele unui grup de securitate AD care conține conturi de computer de exclus.Exemplu: "VIP-Computers" sau "Executive-Devices"     Combinați cu ExclusionListPath atât pentru excluderile bazate pe fișiere, cât și pentru cele bazate pe AD.

.PARAMETER UseWinCS     Utilizați WinCS (Windows Configuration System) în loc de GPO/AvailableUpdatesPolicy.WinCS implementează activarea bootării securizate rulând WinCsFlags.exe direct pe fiecare punct final.WinCsFlags.exe rulează sub context SISTEM printr-o activitate planificată.Această metodă este utilă pentru:     - Lansări mai rapide (efect imediat versus așteptarea procesării GPO)     - Dispozitive care nu sunt asociate la domeniu     - Medii fără infrastructură AD/GPO     Referință: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe

.PARAMETER WinCSKey     Tasta WinCS de utilizat pentru activarea Bootării securizate.Implicit: F33E0C8E002     Această cheie corespunde configurației de lansare Secure Boot.     . PARAMETER DryRun     Afișați ce s-ar face fără a efectua modificări

.PARAMETER ListBlockedBuckets     Afișați toate bucketurile blocate în prezent și ieșiți

.PARAMETER UnblockBucket     Deblocarea unui anumit bucket după tastă și ieșire

.PARAMETER UnblockAll     Deblocați toate bucketurile și ieșiți

.PARAMETER EnableTaskOnDisabled     Implementați Enable-SecureBootUpdateTask.ps1 pentru toate dispozitivele cu activitate programată dezactivată.Creează un GPO cu o activitate planificată unică care rulează opțiunea Activare script cu -Silențios.Acest lucru este util pentru a remedia dispozitivele care au activitatea Secure-Boot-Update dezactivată.

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

.EXAMPLE     # Listați bucketurile blocate     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets

.EXAMPLE     # Deblocați un bucket specific     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"

.EXAMPLE     # Excludeți dispozitivele VIP din implementare utilizând un fișier text     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExclusionListPath "C:\Admin\VIP-Devices.txt"

.EXAMPLE     # Excludeți dispozitivele dintr-un grup de securitate AD (de exemplu, laptopuri executive)     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExcludeAdGroup "VIP-Computers"

.EXAMPLE     # Utilizați WinCS (Windows Configuration System) în loc de GPO/AvailableUpdatesPolicy     # WinCsFlags.exe rulează sub contextul SISTEMului pe fiecare punct final prin activitate planificată     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -UseWinCS '         -WinCSKey "F33E0C8E002" #>

[CmdletBinding()] param(     [Parametru(Obligatoriu = $false)]     [șir]$AggregationInputPath,     [Parametru(Obligatoriu = $false)]     [șir]$ReportBasePath,     [Parametru(Obligatoriu = $false)]     [șir]$TargetOU,     [Parametru(Obligatoriu = $false)]     [șir]$WavePrefix = "SecureBoot-Rollout",     [Parametru(Obligatoriu = $false)]     [int]$MaxWaitHours = 72,     [Parametru(Obligatoriu = $false)]     [int]$PollIntervalMinutes = 1440,                         

    [Parameter(Mandatory = $false)]     [int]$ProcessingBatchSize = 5000,

    [Parameter(Mandatory = $false)]     [int]$DeviceLogSampleSize = 25,

    [Parameter(Mandatory = $false)]     [comutator]$LargeScaleMode,     Nr. ============================================================================     # AllowList / BlockList Parameters     Nr. ============================================================================     # AllowList = Includeți doar aceste dispozitive (lansarea pentru audiență specifică)     # BlockList = Exclude aceste dispozitive (acestea nu vor fi lansate niciodată)     # Procesare comandă: AllowList primul (dacă este specificat), apoi BlockList     [Parametru(Obligatoriu = $false)]     [șir]$AllowListPath,     [Parametru(Obligatoriu = $false)]     [șir]$AllowADGroup,     [Parametru(Obligatoriu = $false)]     [șir]$ExclusionListPath,     [Parametru(Obligatoriu = $false)]     [șir]$ExcludeADGroup,     Nr. ============================================================================     Parametrii # WinCS (Windows Configuration System)     Nr. ============================================================================     # WinCS este o alternativă la implementarea AvailableUpdatesPolicy GPO.                              # Utilizează WinCsFlags.exe pe fiecare punct final pentru a activa lansarea Secure Boot.# WinCsFlags.exe rulează în contextul SYSTEM pe punctul final.# Referință: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe          [Parametru(Obligatoriu = $false)]     [comutator]$UseWinCS,          [Parametru(Obligatoriu = $false)]     [șir]$WinCSKey = "F33E0C8E002",          [Parametru(Obligatoriu = $false)]     [comutator]$DryRun,          [Parametru(Obligatoriu = $false)]     [comutator]$ListBlockedBuckets,          [Parametru(Obligatoriu = $false)]     [șir]$UnblockBucket,          [Parametru(Obligatoriu = $false)]     [comutator]$UnblockAll,          [Parametru(Obligatoriu = $false)]     [argument]$EnableTaskOnDisabled )

$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Eșantioane de implementare și monitorizare"

# ============================================================================ # VALIDARE DEPENDENȚĂ # ============================================================================

function Test-ScriptDependencies {     param(         [Parametru(Obligatoriu = $true)]         [șir]$ScriptDirectory,         [Parametru(Obligatoriu = $true)]         [șir[]]$RequiredScripts     )     $missingScripts = @()     pentru fiecare ($script din $RequiredScripts) {         $scriptPath = Join-Path $ScriptDirectory $script         dacă (-nu (Cale-test $scriptPath)) {             $missingScripts += $script         }     }     dacă ($missingScripts.Count -gt 0) {         Write-Host ""         Write-Host ("=" * 70) - Culoare prim plan roșu         Write-Host "DEPENDENȚE LIPSĂ" -Prim-planColor roșu         Write-Host ("=" * 70) - Culoare prim plan roșu         Write-Host ""         Write-Host "Nu s-au găsit următoarele scripturi necesare:" -Prim-planColor galben         pentru a căuta ($script în $missingScripts) {             Write-Host " - $script" -Culoare prim plan alb         }         Write-Host ""         Write-Host "Descărcați cele mai recente scripturi de la:" -Prim-planColor Cyan         Write-Host " URL: $DownloadUrl" - Culoare prim plan alb         Write-Host " Navigați la: "$DownloadSubPage"" -Culoare prim plan alb         Write-Host ""         Write-Host "Extrageți toate scripturile în același director și rulați din nou." -Prim-planColor galben         Write-Host ""         $false de returnare     }     $true de returnare }                             

# Required scripts for orchestrator $requiredScripts = @(     "Aggregate-SecureBootData.ps1",     "Enable-SecureBootUpdateTask.ps1",     "Deploy-GPO-SecureBootCollection.ps1",     "Detect-SecureBootCertUpdateStatus.ps1" )

if (-not (Test-ScriptDependencies -ScriptDirectory $PSScriptRoot -RequiredScripts $requiredScripts)) {     ieșire 1 }

# ============================================================================ VALIDARE PARAMETRU # # ============================================================================

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

if (-not $ReportBasePath) {     Write-Host "EROARE: -ReportBasePath este obligatoriu". -Prim-planColor Roșu     ieșire 1 }

if (-not $isAdminCommand -and -not $AggregationInputPath) {     Write-Host "ERROR: -AggregationInputPath is required for rollout (not needed for -ListBlockedBuckets, -UnblockBucket, -UnblockAll)" -ForegroundColor Red     ieșire 1 }

# ============================================================================ # GPO DETECTION - VERIFICAȚI PENTRU DETECTAREA GPO # ============================================================================

if (-not $isAdminCommand -and -not $DryRun) {     $CollectionGPOName = "SecureBoot-EventCollection"     # Verificați dacă modulul GroupPolicy este disponibil     if (Get-Module -ListAvailable -Name GroupPolicy) {         Import-Module GroupPolicy -ErrorAction SilentlyContinue         Write-Host "Se verifică detectarea GPO..." -Prim-planColor galben         încercați {             # Verificați dacă există GPO             $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue             dacă ($existingGpo) {                 Write-Host " Detection GPO found: $CollectionGPOName" -ForegroundColor Green             } altfel, {                 Write-Host ""                 Write-Host ("=" * 70) - Culoare prim plan galbenă                 Write-Host " AVERTISMENT: DETECTAREA GPO NEGĂSIT" -Prim-planColor galben                 Write-Host ("=" * 70) - Culoare prim plan galbenă                 Write-Host ""                 Write-Host "Nu s-a găsit "$CollectionGPOName" GPO de detectare." -Prim-planColor galben                 Write-Host "Fără acest GPO, nu vor fi colectate date despre dispozitiv." -Prim planColor galben                 Write-Host ""                 Write-Host "Pentru a implementa detection GPO, run:" -ForegroundColor Cyan                 Write-Host " .\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domeniu> -AutoDetectOU" -ForegroundColor White                 Write-Host ""                 Write-Host "Continuați oricum?                                     (Y/N)" - Culoare prim plan galben                 $response = Gazdă citire                 if ($response -notmatch '^[Yy]') {                     Write-Host "Se abandonează. Implementați întâi detectarea GPO." -Prim-planColor roșu                     ieșire 1                 }             }         } captură {             Write-Host " Nu se poate verifica GPO: $($_. Exception.Message)" -Prim-planColor galben         }     } altfel, {         Write-Host " Modul GroupPolicy indisponibil - se ignoră verificarea GPO" -Prim-planColor gri     }     Write-Host "" }

# ============================================================================ # CĂI DE FIȘIER DE STARE # ============================================================================

$stateDir = Join-Path $ReportBasePath "RolloutState" if (-not (Test-Path $stateDir)) {     New-Item -ItemType Directory -Cale $stateDir -Force | Nul în exterior }

$rolloutStatePath = Join-Path $stateDir "RolloutState.json" $blockedBucketsPath = Join-Path $stateDir "BlockedBuckets.json" $adminApprovedPath = Join-Path $stateDir "AdminApprovedBuckets.json" $deviceHistoryPath = Join-Path $stateDir "DeviceHistory.json" $processingCheckpointPath = Join-Path $stateDir "ProcessingCheckpoint.json"

# ============================================================================ # PS 5.1 COMPATIBILITATE: ConvertTo-Hashtable nr. ============================================================================ # ConvertFrom-Json -AsHashtable este NUMAI PS7+. Acest lucru oferă compatibilitate.

function ConvertTo-Hashtable {     param(         [Parameter(ValueFromPipeline = $true)]         $InputObject     )     proces {         dacă ($null -eq $InputObject) { returnează @{} }         dacă ($InputObject -este [System.Collections.IDictionary]) { returnează $InputObject }         dacă ($InputObject -este [PSCustomObject]) {             # Utilizați [ordonat] pentru o ordonare consistentă a cheilor și pentru manipularea în siguranță a dublurilor             $hash = [ordonat]@{}             foreach ($prop în $InputObject.PSObject.Properties) {                 # Atribuire indexată gestionează în siguranță dublurile prin suprascriere                 $hash[$prop. Nume] = ConvertTo-Hashtable $prop. Valoarea             }             $hash de returnare         }         dacă ($InputObject -este [System.Collections.IEnumerable] -and $InputObject -isnot [șir]) {             returnează @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ })         }         $InputObject de returnare     } }

# ============================================================================ # ADMIN COMENZI: Lista / Deblocare bucketuri # ============================================================================

if ($ListBlockedBuckets) {     Write-Host ""     Write-Host ("=" * 80) - Prim planColor galben     Write-Host "BUCKETURI BLOCATE" -Prim planColor galben     Write-Host ("=" * 80) - Culoare prim plan galbenă     Write-Host ""     if (Test-Cale $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Brut | ConvertFrom-Json | ConvertTo-Hashtable         dacă ($blocked. Count -eq 0) {             Write-Host "Nicio găleată blocată". -Prim-planColor verde         } altfel, {             Write-Host "Total blocat: $($blocked. Count)" -Prim-planColor roșu             Write-Host ""             pentru a căuta ($key în $blocked. Chei) {                 $info = $blocked[$key]                 Write-Host "Bucket: $key" - Roșu prim plan                 Write-Host " Blocat la: $($info. BlockedAt)" -Prim-planColor gri                 Write-Host " Motiv: $($info. Motiv)" -Prim-planColor gri                 Write-Host " Dispozitiv nereușit: $($info. FailedDevice)" -Prim-planColor gri                 Write-Host " Ultima raportare: $($info. LastReported)" -Prim planColor gri                 Write-Host " Wave: $($info. WaveNumber)" -Prim-planColor gri                 Write-Host " Dispozitive în bucket: $($info. DevicesInBucket)" -Prim-planColor gri                 Write-Host ""             }             Write-Host "Pentru a debloca un bucket:"             Write-Host ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY'" -Prim-planColor Cyan             Write-Host ""             Write-Host "Pentru a debloca tot:"             Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 - ReportBasePath "$ReportBasePath" -UnblockAll" -ForegroundColor Cyan         }     } altfel, {         Write-Host "Nu s-a găsit niciun fișier de bucketuri blocat". -Prim planColor Verde     }     Write-Host ""     ieșire 0 }     

if ($UnblockBucket) {     Write-Host ""     if (Test-Cale $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Brut | ConvertFrom-Json | ConvertTo-Hashtable         dacă ($blocked. Conține($UnblockBucket)) {             $blocked. Eliminare($UnblockBucket)             $blocked | ConvertTo-Json -Adâncime 10 | Out-File $blockedBucketsPath -codificare UTF8 -Force             # Adăugați la lista aprobată de administrator pentru a preveni reblocarea             $adminApproved = @{}             if (Test-Path $adminApprovedPath) {                 $adminApproved = Get-Content $adminApprovedPath -Brut | ConvertFrom-Json | ConvertTo-Hashtable             }             $adminApproved[$UnblockBucket] = @{                 ApprovedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                 ApprovedBy = $env:USERNAME             }             $adminApproved | ConvertTo-Json -Adâncime 10 | Out-File $adminApprovedPath -codificare UTF8 -Force             Write-Host "Bucket deblocat: $UnblockBucket" - Verde prim plan             Write-Host "Adăugat la lista aprobată de administrator (nu va fi blocat automat din nou)" -Prim planColor Cyan         } altfel, {             Write-Host "Bucket negăsit: $UnblockBucket" -Culoare prim plan galben             Write-Host "Bucketuri disponibile:" - Gri gri prim plan             $blocked. Taste | ForEach-Object { Write-Host " $_" -Prim-planColor gri }         }     } altfel, {         Write-Host "Nu s-a găsit niciun fișier de bucketuri blocat". -Prim-planColor galben     }     Write-Host ""     ieșire 0 }                          

if ($UnblockAll) {     Write-Host ""     if (Test-Cale $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Brut | ConvertFrom-Json | ConvertTo-Hashtable         $count = $blocked. Conta         @{} | ConvertTo-Json | Out-File $blockedBucketsPath -codificare UTF8 -Force         Write-Host "S-au deblocat toate bucketurile $count." -Prim-planColor verde     } altfel, {         Write-Host "Nu s-a găsit niciun fișier bucket blocat". -Prim planColor galben     }     Write-Host ""     ieșire 0 }

# ============================================================================ # FUNCȚII AJUTOR # ============================================================================

function Get-RolloutState {     if (Test-Cale $rolloutStatePath) {         încercați {             $loaded = Get-Content $rolloutStatePath -Brut | ConvertFrom-Json | ConvertTo-Hashtable             # Validați existența proprietăților necesare             dacă ($null -eq $loaded. CurrentWave) {                 se lansează "Fișier de stare nevalid - CurrentWave lipsă"             }             # Asigurați-vă că WaveHistory este întotdeauna o matrice (remediază deserializarea JSON PS5.1)             dacă ($null -eq $loaded. WaveHistory) {                 $loaded. WaveHistory = @()             } elseif ($loaded. WaveHistory -isnot [matrice]) {                 $loaded. WaveHistory = @($loaded. WaveHistory)             }             $loaded de returnare         } captură {             Write-Log "RolloutState.json deteriorate au detectat: $($_. Exception.Message)" "WARN"             Write-Log "Faceți backup fișierului deteriorat și începeți de la zero" "AVERTIZARE"             $backupPath = "$rolloutStatePath.corrupted.$(Get-Date -Format 'yyyyMMdd-HHmmss')"             Move-Item $rolloutStatePath $backupPath -Force -ErrorAction SilentlyContinue         }     }     returnează @{         CurrentWave = 0         StartedAt = $null         LastAggregation = $null         TotalDevicesTargeted = 0         TotalDevicesUpdated = 0         Stare = "NotStarted"         WaveHistory = @()     } }

function Save-RolloutState {     param($State)     $State | ConvertTo-Json -Adâncime 10 | Out-File $rolloutStatePath -codificare UTF8 -Force }

function Get-WeekdayProjection {     <#     . SINOPSIS         Calcularea datei de finalizare proiectate pentru weekenduri (fără progres în sâmbătă/duminică)     #>     param(         [int]$RemainingDevices,         [dublu]$DevicesPerDay,         [dată-oră]$StartDate = (Get-Date)     )     dacă ($DevicesPerDay -le 0 -sau $RemainingDevices -le 0) {         returnează @{             ProjectedDate = $null             WorkingDaysNeeded = 0             CalendarDaysNeeded = 0         }     }     # Calculați zilele lucrătoare necesare (excluzând weekendurile)     $workingDaysNeeded = [matematică]::Ceiling($RemainingDevices / $DevicesPerDay)     # Efectuați conversia zilelor lucrătoare în zile calendaristice (adăugare weekenduri)     $currentDate = $StartDate.Data     $daysAdded = 0     $workingDaysAdded = 0     în timp ce ($workingDaysAdded -lt $workingDaysNeeded) {         $currentDate = $currentDate.AddDays(1)         $daysAdded++         # Numărați doar zilele lucrătoare         if ($currentDate.DayOfWeek -ne [DayOfWeek]::Saturday -and             $currentDate.DayOfWeek -ne [DayOfWeek]:Sunday) {             $workingDaysAdded++         }     }     returnează @{         ProjectedDate = $currentDate.ToString("yyyy-MM-dd")         WorkingDaysNeeded = $workingDaysNeeded         CalendarDaysNeeded = $daysAdded     } }                                  

function Save-RolloutSummary {     <#     . SINOPSIS         Salvați rezumatul lansării cu informații de proiecție pentru afișarea tabloului de bord     #>     param(         [hashtable]$State,         [int]$TotalDevices,         [int]$UpdatedDevices,         [int]$NotUpdatedDevices,         [dublu]$DevicesPerDay     )     $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     # Calculați proiecția receptivă la weekend     $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay     $summary = @{         GeneratAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")         RolloutStartDate = $State.StartedAt         LastAggregation = $State.LastAggregation         CurrentWave = $State.CurrentWave         Stare = $State.Stare         # Contor dispozitive         TotalDevices = $TotalDevices         UpdatedDevices = $UpdatedDevices         NotUpdatedDevices = $NotUpdatedDevices         PercentUpdated = if ($TotalDevices -gt 0) { [math]:Round(($UpdatedDevices / $TotalDevices) * 100, 1) } else { 0 }         # Măsurători de viteză         DevicesPerDay = [matematică]:Round($DevicesPerDay, 1)         TotalDevicesTargeted = $State.TotalDevicesTargeted         TotalWaves = $State.CurrentWave         # Proiecție receptivă la weekend         ProjectedCompletionDate = $projection. DatăProiectare         WorkingDaysRemaining = $projection. WorkingDaysNeeded         CalendarDaysRemaining = $projection. CalendarDaysNeeded         # Notă despre excludere weekend         ProjectionNote = "Finalizarea proiectată exclude weekendurile (sâmbătă/duminică)"     }     $summary | ConvertTo-Json -Adâncime 5 | Out-File $summaryPath -codificare UTF8 -Force     Write-Log "Rezumatul lansării salvat: $summaryPath" "INFORMAȚII"     $summary de returnare }                                                             

function Get-BlockedBuckets {     if (Test-Path $blockedBucketsPath) {         returnează Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     returnează @{} }

function Save-BlockedBuckets {     param($Blocked)     $Blocked | ConvertTo-Json -Adâncime 10 | Out-File $blockedBucketsPath -codificare UTF8 -Force }

function Get-AdminApproved {     if (Test-Cale $adminApprovedPath) {         returnare Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     returnează @{} }

function Get-DeviceHistory {     if (Test-Cale $deviceHistoryPath) {         returnare Get-Content $deviceHistoryPath -raw | ConvertFrom-Json | ConvertTo-Hashtable     }     returnează @{} }

function Save-DeviceHistory {     param($History)     $History | ConvertTo-Json -Adâncime 10 | Out-File $deviceHistoryPath -codificare UTF8 -Force }

function Save-ProcessingCheckpoint {     param(         [șir]$Stage,         [int]$Processed,         [int]$Total,         [hashtable]$Metrics = @{}     )

    $checkpoint = @{         Etapă = $Stage         UpdatedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         Procesate = $Processed         Total = $Total         Procent = dacă ($Total -gt 0) { [matematică]::Round(($Processed / $Total) * 100, 2) } altfel { 0 }         Metrici = $Metrics     }

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

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

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

    foreach ($device in $Devices) {         $hostname = dacă ($device. Hostname) { $device. Hostname } elseif ($device. HostName) { $device. HostName } altceva { $null }         dacă ($hostname) {             [nul]$hostSet.Add($hostname)         }

        $bucketKey = Get-BucketKey $device         dacă ($bucketKey) {             if ($bucketCounts.ContainsKey($bucketKey)) {                 $bucketCounts[$bucketKey]++             } altfel, {                 $bucketCounts[$bucketKey] = 1             }         }     }

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

function Write-Log {     param([șir]$Message, [șir]$Level = "INFO")     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     $color = comutator ($Level) {         "OK" { "Verde" }         "AVERTIZARE" { "Galben" }         "EROARE" { "Roșu" }         "BLOCAT" { "DarkRed" }         "WAVE" { "Cyan" }         { "Alb" } implicit     }     Write-Host "[$timestamp] [$Level] $Message" -Prim planColor $color     # De asemenea, log to file     $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMDd').log"     "[$timestamp] [$Level] $Message" | Out-File $logFile - Adăugare - codificare UTF8 }               

function Get-BucketKey {     param($Device)     # Utilizați BucketId de la JSON dispozitiv, dacă este disponibil (cod hash SHA256 din scriptul de detectare)     if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { returnează "$($Device.BucketId)" }     # Rezervă: construct de la producător|model|bios     $mfr = dacă ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } altcineva, { $Device.Manufacturer }     $model = dacă ($Device.WMI_Model) { $Device.WMI_Model } altcineva { $Device.Model }     $bios = dacă ($Device.BIOSDescription) { $Device.BIOSDescription } altcineva { $Device.BIOS }     returnează "$mfr|$model|$bios" }

# ============================================================================ # VIP / LISTA DE EXCLUDERE DE ÎNCĂRCARE # ============================================================================

function Get-ExcludedHostnames {     param(         [șir]$ExclusionFilePath,         [șir]$ADGroupName     )     $excluded = [System.Collections.Generic.HashSet[string]]:new([StringComparer]::OrdinalIgnoreCase)     # Încărcare din fișier (acceptă .txt sau .csv)     if ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) {         $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower()         dacă ($extension -eq ".csv") {             Format CSV # : așteaptă o coloană "Hostname" sau "ComputerName"             $csvData = Import-Csv $ExclusionFilePath             $hostCol = dacă ($csvData[0]. PSObject.Properties.Name -conține 'Hostname') { 'Hostname' }                        elseif ($csvData[0]. PSObject.Properties.Name -conține 'NumeComputer') { 'NumeComputer' }                        elseif ($csvData[0]. PSObject.Properties.Name -conține "Nume") { "Nume" }                        altfel, { $null }             dacă ($hostCol) {                 foreach ($row în $csvData) {                     if (![ șir]::IsNullOrWhiteSpace($row.$hostCol)) {                         [nul]$excluded. Add($row.$hostCol.Trim())                     }                 }             }         } altfel, {             # Text simplu: un nume de gazdă pe linie             Get-Content $ExclusionFilePath | ForEach-Object {                 $line = $_. Trim()                 dacă ($line -și -nu $line. StartsWith('#')) {                     [nul]$excluded. Adăugare($line)                 }             }         }         Write-Log "S-au încărcat $($excluded. Count) nume de gazdă din fișierul de excludere: $ExclusionFilePath" "INFO"     }     # Încărcare de la grupul de securitate AD     dacă ($ADGroupName) {         încercați {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }             foreach ($member în $groupMembers) {                 [nul]$excluded. Adăugare($member). Nume)             }             Write-Log "S-au încărcat computere $($groupMembers.Count) din grupul AD: $ADGroupName" "INFO"         } captură {             Write-Log "Imposibil de încărcat grupul AD '$ADGroupName': $_" "WARN"         }     }     returnare @($excluded) }                                                                             

# ============================================================================ # ALLOW LIST LOADING (Targeted Rollout) # ============================================================================

function Get-AllowedHostnames {     <#     . SINOPSIS         Încarcă numele de gazdă dintr-un fișier AllowList și/sau dintr-un grup AD pentru lansarea țintită.Atunci când este specificată o AllowList, DOAR aceste dispozitive vor fi incluse în implementare.#>     param(         [șir]$AllowFilePath,         [șir]$ADGroupName     )          $allowed = [System.Collections.Generic.HashSet[string]]:new([StringComparer]::OrdinalIgnoreCase)          # Încărcare din fișier (acceptă .txt sau .csv)     if ($AllowFilePath -and (Test-Path $AllowFilePath)) {         $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower()                  dacă ($extension -eq ".csv") {             Format CSV # : așteaptă o coloană "Hostname" sau "ComputerName"             $csvData = Import-Csv $AllowFilePath             dacă ($csvData.Count -gt 0) {                 $hostCol = dacă ($csvData[0]. PSObject.Properties.Name -conține 'Hostname') { 'Hostname' }                            elseif ($csvData[0]. PSObject.Properties.Name -conține "NumeComputer") { "NumeComputer" }                            elseif ($csvData[0]. PSObject.Properties.Name -conține "Nume") { "Nume" }                            altfel, { $null }                                  dacă ($hostCol) {                     foreach ($row în $csvData) {                         if (![ șir]::IsNullOrWhiteSpace($row.$hostCol)) {                             [nul]$allowed. Add($row.$hostCol.Trim())                         }                     }                 }             }         } altfel, {             # Text simplu: un nume de gazdă pe linie             Get-Content $AllowFilePath | ForEach-Object {                 $line = $_. Trim()                 dacă ($line -și -nu $line. StartsWith('#')) {                     [nul]$allowed. Adăugare($line)                 }             }         }                  Write-Log "S-a încărcat $($allowed. Count) hostnames from allow list file: $AllowFilePath" "INFO"     }          # Încărcare de la grupul de securitate AD     dacă ($ADGroupName) {         încercați {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'computer' }                          foreach ($member în $groupMembers) {                 [nul]$allowed. Adăugare($member). Nume)             }                          Write-Log "S-au încărcat computerele $($groupMembers.Count) din grupul de permisiuni AD: $ADGroupName" "INFO"         } captură {             Write-Log "Imposibil de încărcat grupul AD '$ADGroupName': $_" "WARN"         }     }          returnare @($allowed) }

# ============================================================================ # PROSPEȚIMEA ȘI MONITORIZAREA DATELOR # ============================================================================

function Get-DataFreshness {     <#     . SINOPSIS         Verifică cât de proaspete sunt datele de detectare examinând marcajele de timp ale fișierelor JSON.Returnează statistica privind momentul când punctele finale au fost raportate ultima dată.#>     param([șir]$JsonPath)     $jsonFiles = Get-ChildItem -Cale $JsonPath -Filtru "*.json" -ErrorAction SilentlyContinue     dacă ($jsonFiles.Count -eq 0) {         returnează @{             TotalFișiere = 0             FreshFiles = 0             StaleFiles = 0             NoDataFiles = 0             OldestFile = $null             NewestFile = $null             AvgAgeHours = 0             Avertisment = "Nu s-au găsit fișiere JSON - detectarea poate să nu fie implementată"         }     }     $now = Get-Date     $freshThresholdHours = 24 # Files actualizate în ultimele 24 de ore sunt "proaspete"     $staleThresholdHours = 72 # Files mai vechi de 72 de ore sunt "învechite"     $fresh = 0     $stale = 0     $ages = @()     pentru fiecare ($file din $jsonFiles) {         $ageHours = ($now - $file. LastWriteTime). Ore total         $ages += $ageHours         dacă ($ageHours -le $freshThresholdHours) {             $fresh++         } elseif ($ageHours -ge $staleThresholdHours) {             $stale++         }     }     $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object -Primul 1     $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descendent | Select-Object -Primul 1     $warning = $null     dacă ($stale -gt ($jsonFiles.Count * 0,5)) {         $warning = "Peste 50% din dispozitive au date învechite (>72 de ore) - GPO de detectare a verificării"     } elseif ($fresh -lt ($jsonFiles.Count * 0,3)) {         $warning = "Mai puțin de 30% din dispozitivele raportate recent - este posibil ca detectarea să nu ruleze"     }     returnează @{         TotalFiles = $jsonFiles.Count         FreshFiles = $fresh         StaleFiles = $stale         Fișe medii = $jsonFiles.Count - $fresh - $stale         OldestFile = $oldestFile.lastWriteTime         NewestFile = $newestFile.LastWriteTime         AvgAgeHours = [matematică]:Round(($ages | Measure-Object -Average). Medie, 1)         Avertisment = $warning     } }                                                 

function Test-DetectionGPODeployed {     <#     . SINOPSIS         Verifică dacă infrastructura de detectare/monitorizare este în vigoare.#>     param([șir]$JsonPath)     # Verificarea 1: calea JSON există     dacă (-nu (Cale-test $JsonPath)) {         returnează @{             IsDeployed = $false             Message = "Calea de intrare JSON nu există: $JsonPath"         }     }     # Check 2: Cel puțin unele fișiere JSON există     $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Conta     dacă ($jsonCount -eq 0) {         returnează @{             IsDeployed = $false             Mesaj = "Nu există fișiere JSON în $JsonPath - este posibil ca GPO-ul de detectare să nu fie implementat sau ca dispozitivele să nu fi raportat încă"         }     }     # Check 3: Files sunt rezonabil de recente (cel puțin unele în ultima săptămână)     $recentFiles = Get-ChildItem -Cale $JsonPath -Filtru "*.json" -ErrorAction SilentlyContinue |         Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) }     dacă ($recentFiles.Count -eq 0) {         returnează @{             IsDeployed = $false             Message = "Niciun fișier JSON actualizat în ultimele 7 zile - Detectarea GPO poate fi întreruptă sau dispozitive offline"         }     }     returnează @{         IsDeployed = $true         Message = "Detection appears active: $jsonCount files, $($recentFiles.Count) updated recent"         FișierCount = $jsonCount         RecentCount = $recentFiles.Count     } }                         

# ============================================================================ # URMĂRIREA DISPOZITIVELOR (PRIN HOSTNAME) # ============================================================================

function Update-DeviceHistory {     <#     . SINOPSIS         Urmărește dispozitivele după numele de gazdă, deoarece nu avem un identificator unic de computer.Notă: BucketId este unu-la-mai-mulți (aceeași configurație hardware = același bucket).Dacă un identificator unic este adăugat la colecția JSON, actualizați această funcție.#>     param(         [matrice]$CurrentDevices,         [hashtable]$DeviceHistory     )          pentru a căuta ($device în $CurrentDevices) {         $hostname = $device. Hostname         dacă (-nu $hostname) { continue }                  # Urmăriți dispozitivul după numele gazdei         $DeviceHistory[$hostname] = @{             Hostname = $hostname             BucketId = $device. Id bucket             Producător = $device. WMI_Manufacturer             Model = $device. WMI_Model             LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             Stare = $device. Stare actualizare         }     } }

# ============================================================================ # DETECTARE BUCKET BLOCATĂ (în funcție de accesibilitatea dispozitivului) # ============================================================================

<# . DESCRIEREA /     Logică de blocare:     - O găleată este blocată numai dacă:       1. Dispozitivul a fost țintit într-un val       2. MaxWaitHours a trecut de la începutul valului       3. Dispozitivul nu este accesibil (ping nu reușește)          - Dacă dispozitivul este accesibil, dar nu este încă actualizat, continuăm să așteptăm       (actualizarea poate fi în așteptarea repornirii - evenimentul 1808 se declansează numai după repornire)          - Dispozitivul inaccesibil indică faptul că ceva nu a funcționat corect și necesită investigații          Deblocarea:     - Utilizați -ListBlockedBuckets pentru a vedea bucketurile blocate     - Utilizați -DeblocareBucket "BucketKey" pentru a debloca un bucket specific     - Utilizați -Deblocare totală pentru a debloca toate bucketurile #>

function Test-DeviceReachable {     param(         [șir]$Hostname,         [string]$DataPath # Path to device JSON files     )     # Metoda 1: Check JSON file timestamp (cea mai rapidă - nu este necesară analiza fișierului)     # Dacă script-ul de detectare a rulat recent, fișierul a fost scris / actualizat, dovedind dispozitivul este în viață     dacă ($DataPath) {         $deviceFile = Get-ChildItem -Cale $DataPath -Filtru "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object -Primul 1         dacă ($deviceFile) {             $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). Ore total             dacă ($hoursSinceWrite -lt 72) { returnează $true }         }     }     # Metoda 2: Rezervă la ping (numai dacă JSON este învechit sau lipsă)     încercați {         $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue         $ping de returnare     } captură {         $false de returnare     } }          

function Update-BlockedBuckets {     param(         $RolloutState,         $BlockedBuckets,         $AdminApproved,         [matrice]$NotUpdatedDevices,         [hashtable]$NotUpdatedIndexes,         [int]$MaxWaitHours,         [bool]$DryRun = $false     )     $now = Get-Date     $newlyBlocked = @()     $stillWaiting = @()     $devicesToCheck = @()     $hostSet = dacă ($NotUpdatedIndexes -și $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     $bucketCounts = dacă ($NotUpdatedIndexes -și $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). BucketCounts }     # Colectați dispozitive care au depășit perioada de așteptare și tot nu au fost actualizate     foreach ($wave în $RolloutState.WaveHistory) {         dacă (-nu $wave. StartedAt) { continue }         $waveStart = [DatăTime]::P arse($wave. StartedAt)         $hoursSinceWave = ($now - $waveStart). Ore total         dacă ($hoursSinceWave -lt $MaxWaitHours) {             # Încă în perioada de așteptare - nu verificați încă             Continua         }         # Verificați fiecare dispozitiv din acest val         pentru a căuta ($deviceInfo în $wave. Dispozitive) {             $hostname = $deviceInfo.Hostname             $bucketKey = $deviceInfo.BucketKey             # Skip if bucket already blocked             dacă ($BlockedBuckets.Contains($bucketKey)) { continue }             # Skip if bucket is admin-approved AND wave started BEFORE approval             # (verificați doar dispozitivele vizate după aprobarea administratorului pentru a le rebloca)             if ($AdminApproved -and $AdminApproved.Contains($bucketKey)) {                 $approvalTime = [DatăTime]::P arse($AdminApproved[$bucketKey]. ApprovedAt)                 dacă ($waveStart -lt $approvalTime) {                     # Acest dispozitiv a fost direcționat înainte de aprobarea administratorului - omiteți                     Continua                 }                 # Wave a început după aprobare - aceasta este proaspătă de orientare, poate verifica             }             # Acest dispozitiv este încă în lista NotUpdated?             if ($hostSet.Contains($hostname)) {                 $devicesToCheck += @{                     Hostname = $hostname                     Cheie Bucket = $bucketKey                     WaveNumber = $wave. Număr undă                     HoursSinceWave = [matematică]:Round($hoursSinceWave, 1)                 }             }         }     }     if ($devicesToCheck.Count -eq 0) {         $newlyBlocked de returnare     }     Write-Log "Se verifică accesibilitatea dispozitivelor $($devicesToCheck.Count) în perioada de așteptare depășită..." "INFORMAȚII"     # Urmăriți erorile per bucket pentru luarea deciziilor     $bucketFailures = @{} # BucketKey -> @{ Imposibil de găsit=@(); În viață=@() }     # Verificați accesibilitatea fiecărui dispozitiv     foreach ($device în $devicesToCheck) {         $hostname = $device. Hostname         $bucketKey = $device. Cheie bucket         dacă ($DryRun) {             Write-Log "[DRYRUN] Ar verifica accesibilitatea $hostname" "INFO"             Continua         }         if (-not $bucketFailures.ContainsKey($bucketKey)) {             $bucketFailures[$bucketKey] = @{ Imposibil de găsit = @(); AliveButFailed = @(); WaveNumber = $device. Număr undă; HoursSinceWave = $device. HoursSinceWave }         }         $isReachable = Test-DeviceReachable -nume gazdă $hostname -DataPath $AggregationInputPath         dacă (-nu $isReachable) {             $bucketFailures[$bucketKey]. Imposibil de găsit += $hostname         } altfel, {             # Dispozitivul ESTE accesibil, dar nu este încă actualizat - poate fi o eroare temporară sau așteaptă repornirea             $bucketFailures[$bucketKey]. AliveButFailed += $hostname             $stillWaiting += $hostname         }     }     # Decizia pe bucket: bloca numai în cazul în care dispozitivele sunt cu adevărat imposibil de găsit     # Dispozitive vii cu erori = temporar, continuați implementarea     pentru a căuta ($bucketKey în $bucketFailures.Keys) {         $bf = $bucketFailures[$bucketKey]         $unreachableCount = $bf. Imposibil de găsit.Count         $aliveFailedCount = $bf. AliveButFailed.Count         # Verificați dacă acest bucket are succese (de la date actualizate despre dispozitive)         $bucketHasSuccesses = $stSuccessBuckets și $stSuccessBuckets.Conține($bucketKey)         dacă ($unreachableCount -gt 0 și $aliveFailedCount -eq 0) {             # TOATE dispozitivele cu erori sunt inaccesibile - blocați bucketul             dacă ($newlyBlocked -notcontains $bucketKey) {                 $BlockedBuckets[$bucketKey] = @{                     BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                     Motiv = "Toate dispozitivele $unreachableCount imposibil de găsit după $($bf. HoursSinceWave) ore"                     FailedDevices = ($bf. Imposibil de găsit -join ", ")                     WaveNumber = $bf. Număr undă                     DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } altfel { 0 }                 }                 $newlyBlocked += $bucketKey                 Write-Log "BUCKET BLOCAT: $bucketKey (dispozitive $unreachableCount imposibil de accesat: $($bf. Unreachable -join ', '))" "BLOCAT"             }         } elseif ($aliveFailedCount -gt 0) {             # Dispozitivele sunt în viață, dar nu sunt actualizate - eroare temporară, NU blocați             Write-Log "Bucket $($bucketKey.Substring(0, [Matematică]:Min(16, $bucketKey.Length)))...: $aliveFailedCount dispozitive în viață, dar în așteptare, $unreachableCount inaccesibile - NU se blochează (temporar)" "INFO"             dacă ($unreachableCount -gt 0) {                 Write-Log " Imposibil de găsit: $($bf. Imposibil de găsit -join ', ')" "WARN"             }             Write-Log " În viață, dar în așteptare: $($bf. AliveButFailed -join ', ')" "INFO"             # Urmăriți numărul de erori în starea de implementare pentru monitorizare             dacă (-nu $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} }             $RolloutState.TemporaryFailures[$bucketKey] = @{                 AliveButFailed = $bf. AliveButFailed                 Imposibil de găsit = $bf. Imposibil                 LastChecked = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             }         }     }     dacă ($stillWaiting.Count -gt 0) {         Write-Log "Dispozitive accesibile, dar în așteptarea actualizării (poate fi necesară repornirea): $($stillWaiting.Count)" "INFO"     }     $newlyBlocked de returnare }                                                                                                                                                                                  

# ============================================================================ # AUTO-DEBLOCARE: Deblocați bucketuri atunci când dispozitivele se actualizează cu succes # ============================================================================

function Update-AutoUnblockedBuckets {     <#     . DESCRIEREA /         Verifică dacă dispozitivele din bucketurile blocate s-au actualizat (evenimentul 1808).         Se deblochează automat dacă toate dispozitivele direcționate din bucket s-au actualizat.Dacă s-au actualizat doar UNELE dispozitive, anunță administratorul care poate debloca manual.                  Admin poate debloca manual utilizând:           .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "path" -UnblockBucket "BucketKey"     #>     param(         $BlockedBuckets,         $RolloutState,         [matrice]$NotUpdatedDevices,         [șir]$ReportBasePath,         [hashtable]$NotUpdatedIndexes,         [int]$LogSampleSize = 25     )     $autoUnblocked = @()     $bucketsToCheck = @($BlockedBuckets.Keys)     $hostSet = dacă ($NotUpdatedIndexes -și $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     foreach ($bucketKey în $bucketsToCheck) {         $bucketInfo = $BlockedBuckets[$bucketKey]         # Obțineți toate dispozitivele pe care le-am vizat din acest bucket istoric         $targetedDevicesInBucket = @()         foreach ($wave în $RolloutState.WaveHistory) {             $targetedDevicesInBucket += @($wave. Dispozitive | Where-Object { $_. BucketKey -eq $bucketKey })         }         dacă ($targetedDevicesInBucket.Count -eq 0) { continue }         # Verificați câte dispozitive țintă sunt încă în NotUpdated vs actualizat         $updatedDevices = @()         $stillPendingDevices = @()         pentru a căuta ($targetedDevice în $targetedDevicesInBucket) {             if ($hostSet.Contains($targetedDevice.Hostname)) {                 $stillPendingDevices += $targetedDevice.Hostname             } altfel, {                 $updatedDevices += $targetedDevice.Hostname             }         }         dacă ($updatedDevices.Count -gt 0 și $stillPendingDevices.Count -eq 0) {             # TOATE dispozitivele vizate s-au actualizat - auto-deblocare!             $BlockedBuckets.Remove($bucketKey)             $autoUnblocked += @{                 Cheie Bucket = $bucketKey                 UpdatedDevices = $updatedDevices                 AnteriorBlockedAt = $bucketInfo.BlockedAt                 Motiv = "Toate dispozitivele țintite $($updatedDevices.Count) s-au actualizat cu succes"             }             Write-Log "DEBLOCAT AUTOMAT: $bucketKey (Toate dispozitivele țintă $($updatedDevices.Count) s-au actualizat cu succes)" "OK"             # Incrementați numărul de undă OEM pentru OEM-ul acestui bucket (urmărire per-OEM)             $bucketOEM = dacă ($bucketKey -potrivire "\|") { ($bucketKey -scindare '\|')[0] } altceva { 'Necunoscut' } # Extragere OEM din cheia delimitată prin conducte sau implicită             dacă (-nu $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             $currentWave = dacă ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } altfel { 0 }             $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1             Write-Log numărul de unde "$bucketOEM" OEM incrementat la dispozitivele $($currentWave + 1) (următoarea alocare: dispozitive $([int][Matematice]::P ow(2, $currentWave + 1)))" "INFO"         }         elseif ($updatedDevices.Count -gt 0 și $stillPendingDevices.Count -gt 0) {             # UNELE dispozitive au fost actualizate, dar altele sunt încă în așteptare - notificați administratorul (o singură dată)             dacă (-nu $bucketInfo.UnblockCandidate) {                 $bucketInfo.UnblockCandidate = $true                 $bucketInfo.UpdatedDevices = $updatedDevices                 $bucketInfo.PendingDevices = $stillPendingDevices                 $bucketInfo.NotifiedAt = (Get-Date). ToString("yyyy-MM-dd HH:mm:ss")                 Write-Log "" "INFO"                 Write-Log "========== ACTUALIZARE PARȚIALĂ ÎN ========== BUCKET BLOCAT" "INFORMAȚII"                 Write-Log "Bucket: $bucketKey" "INFO"                 $updatedSample = @($updatedDevices | Select-Object -Primul $LogSampleSize)                 $pendingSample = @($stillPendingDevices | Select-Object -Primul $LogSampleSize)                 $updatedSuffix = dacă ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count - $LogSampleSize) mai mult)" } altceva { "" }                 $pendingSuffix = dacă ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count - $LogSampleSize) more)" } altceva { "" }                 Write-Log "Dispozitive actualizate ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "OK"                 Write-Log "Încă în așteptare ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN"                 Write-Log "" "INFO"                 Write-Log "Pentru a debloca manual acest bucket după verificare, rulați:" "INFORMAȚII"                 Write-Log ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath ""$ReportBasePath"" -UnblockBucket ""$bucketKey""" "INFO"                 Write-Log "=======================================================" "INFO"                 Write-Log "" "INFO"             }         }     }     $autoUnblocked de returnare }                                                                                          

# ============================================================================ # WAVE GENERATION (INLINED - exclude bucketurile blocate) # ============================================================================

function New-RolloutWave {     param(         [șir]$AggregationPath,         $BlockedBuckets,         $RolloutState,         [int]$MaxDevicesPerWave = 50,         [string[]]$AllowedHostnames = @(),         [șir[]]$ExcludedHostnames = @()     )     # Încărcarea datelor de agregare     $notUptodateCsv = Get-ChildItem -Cale $AggregationPath -Filtru "*NotUptodate*.csv" |          Where-Object { $_. Nume -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descendent |          Select-Object -Primul 1     dacă (-nu $notUptodateCsv) {         Write-Log "Nu s-a găsit niciun CSV NotUptodate" "ERROR"         $null de returnare     }     $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName)     # Normalize HostName -> Hostname for consistency (CSV uses HostName, code uses Hostname)     foreach ($device în $allNotUpdated) {         dacă ($device. PSObject.Properties['HostName'] -and -not $device. PSObject.Properties['Hostname']) {             $device | Add-Member -NotePropertyName 'Hostname' -NotePropertyValue $device. HostName -Force         }     }     # Filtrați bucketurile blocate     $eligibleDevices = @($allNotUpdated | Where-Object {         $bucketKey = Get-BucketKey $_         -not $BlockedBuckets.Contains($bucketKey)     })     # Filtrați doar pe dispozitivele permise (dacă se specifică AllowList)     # AllowList = lansarea țintită - doar aceste dispozitive vor fi luate în considerare     dacă ($AllowedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Hostname -in $AllowedHostnames         })         $allowedCount = $eligibleDevices.Count         Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO"     }     # Filtrați dispozitivele VIP/excluse (BlockList)     # BlockList se aplică DUPĂ AllowList     dacă ($ExcludedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Hostname -notin $ExcludedHostnames         })         $excludedCount = $beforeCount - $eligibleDevices.Count         dacă ($excludedCount -gt 0) {             Write-Log "Se exclude $excludedCount dispozitivele VIP/protejate de la implementare" "INFO"         }     }     dacă ($eligibleDevices.Count -eq 0) {         Write-Log "Nu a mai rămas niciun dispozitiv eligibil (toate actualizate sau blocate)" "OK"         $null de returnare     }     # Obțineți dispozitivele deja lansate (din valurile anterioare)     $devicesAlreadyInRollout = @()     if ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) {         $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object {             $_. Dispozitive | ForEach-Object { $_. Nume gazdă }         } | Where-Object { $_ })     }     Write-Log "Dispozitive aflate deja în implementare: $($devicesAlreadyInRollout.Count)" "INFO"     # Separați-vă de nivelul de încredere     $highConfidenceDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -eq "Nivel înalt de încredere" -și         $_. Hostname -notin $devicesAlreadyInRollout     })     # Acțiune necesară include:     # - Explicit "Acțiune necesară"     # - Nivel ConfidenceLevel gol/nul     # - ORICE valoare necunoscută/nerecunoscută ConfidenceLevel (tratată ca Acțiune necesară)     $knownSafeCategories = @(         "Încredere maximă",         "În pauză temporară",         "Sub observație",         "Sub observație - mai multe date necesare",         "Neacceptat",         "Neacceptat - Limitare cunoscută"     )     $actionRequiredDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -notin $knownSafeCategories -and         $_. Hostname -notin $devicesAlreadyInRollout     })     Write-Log "Încredere maximă (nu în implementare): $($highConfidenceDevices.Count)" "INFO"     Write-Log "Acțiune necesară (nu este în implementare): $($actionRequiredDevices.Count)" "INFO"     # Construieste dispozitive wave     $waveDevices = @()     # HIGH CONFIDENCE: Include ALL (în condiții de siguranță pentru lansare)     dacă ($highConfidenceDevices.Count -gt 0) {         Write-Log "Adding all $($highConfidenceDevices.Count) High Confidence devices" "WAVE"         $waveDevices += $highConfidenceDevices     } # ACȚIUNE NECESARĂ: Lansarea progresivă (pe bază de bucket cu OEM-spread pentru bucketuri cu succes zero)     # Strategie:     # - Bucket-uri cu 0 succese: Spread în întreaga OEM (1 per OEM -> 2 pe OEM -> 4 per OEM)     # - Buckets cu ≥1 succes: Dublu liber, fără restricție OEM     dacă ($actionRequiredDevices.Count -gt 0) {         # Încărcați numărul de succese ale bucketului de pe dispozitivele CSV actualizate (dispozitive care au fost actualizate cu succes)         $updatedCsv = Get-ChildItem -cale $AggregationPath -Filtru "*updated_devices*.csv" |             Sort-Object LastWriteTime -Descendent | Select-Object -Primul 1         $bucketStats = @{}         dacă ($updatedCsv) {             $updatedDevices = Import-Csv $updatedCsv.NumeComplet             # Numărați succesele per BucketId             $updatedDevices | ForEach-Object {                 $key = Get-BucketKey $_                 dacă ($key) {                     if (-not $bucketStats.ContainsKey($key)) {                         $bucketStats[$key] = @{ Succese = 0; În așteptare = 0; Total = 0 }                     }                     $bucketStats[$key]. Succese++                     $bucketStats[$key]. Total++                 }             }             Write-Log "S-au încărcat dispozitivele $($updatedDevices.Count) actualizate în bucketurile $($bucketStats.Count) " "INFO"         } altfel, {             # Rezervă: încercați ActionRequired_Buckets CSV             $bucketsCsv = Get-ChildItem -Cale $AggregationPath -Filtru "*ActionRequired_Buckets*.csv" |                 Sort-Object LastWriteTime -Descendent | Select-Object -Primul 1             dacă ($bucketsCsv) {                 Import-Csv $bucketsCsv.FullName | ForEach-Object {                     $key = dacă ($_. BucketId) { $_. BucketId } else { "$($_. Producător)|$($_. Model)|$($_. BIOS)" }                     $bucketStats[$key] = @{                         Succese = [int]$_. Succese                         În așteptare = [int]$_. Până                         Total = [int]$_. TotalDevice-uri                     }                 }             }         }         # Grupați dispozitivele notUpdated după bucket (Producător|Model|BIOS)         $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ }         # Bucket-uri separate: zero-succes vs a-succes         $zeroSuccessBuckets = @()         $hasSuccessBuckets = @()         pentru a căuta ($bucket în $buckets) {             $bucketKey = $bucket. Nume             $bucketDevices = @($bucket. Grup)             $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Nume gazdă })             # Numărați succesele din acest bucket             $stats = $bucketStats[$bucketKey]             $successes = dacă ($stats) { $stats. Succese } altceva { 0 }             # Găsiți dispozitive implementate în acest bucket din istoricul valurilor             $deployedToBucket = @()             foreach ($wave în $RolloutState.WaveHistory) {                 pentru a căuta ($device în $wave. Dispozitive) {                     dacă ($device. BucketKey -eq $bucketKey -și $device. Hostname) {                         $deployedToBucket += $device. Hostname                     }                 }             }             $deployedToBucket = @($deployedToBucket | Sort-Object -Unique)             # Verificați dacă toate dispozitivele implementate au raportat succes             $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames })             $confirmedSuccess = $deployedToBucket.Count - $stillPending.Count             # Dacă este în așteptare, omiteți acest bucket până când se confirmă tot             dacă ($stillPending.Count -gt 0) {                 $parts = $bucketKey -scindare "\|"                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " Bucket: $displayName - Deployed=$($deployedToBucket.Count), Confirmed=$confirmedSuccess, Pending=$($stillPending.Count) (waiting)" "INFO"                 Continua             }             # Eligibil rămas = dispozitive neimplementate încă             $devicesNotYetTargeted = @($bucketDevices | Where-Object {                 $_. Hostname -notin $deployedToBucket             })             dacă ($devicesNotYetTargeted.Count -eq 0) { continue }             # Clasificați după numărul de succese             $bucketInfo = @{                 Cheie Bucket = $bucketKey                 Dispozitive = $devicesNotYetTargeted                 ConfirmedSuccess = $confirmedSuccess                 Succese = $successes                 OEM = dacă ($bucket. Grup[0]. WMI_Manufacturer) { $bucket. Grup[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -scindare '\|')[0] } altceva { "Necunoscut" }             }             dacă ($successes -eq 0) {                 $zeroSuccessBuckets += $bucketInfo             } altfel, {                 $hasSuccessBuckets += $bucketInfo             }         }         # === BUCKETURI PROCES HAS-SUCCES (succes ≥1) ===         # Double the number of successes — if 14 succeeded, deploy 28 next         foreach ($bucketInfo în $hasSuccessBuckets) {             $nextBatchSize = $bucketInfo.Succese * 2             $nextBatchSize = [Matematică]::Min($nextBatchSize, $MaxDevicesPerWave)             $nextBatchSize = [Matematică]::Min($nextBatchSize, $bucketInfo.Devices.Count)             dacă ($nextBatchSize -gt 0) {                 $selectedDevices = @($bucketInfo.Devices | Select-Object -First $nextBatchSize)                 $waveDevices += $selectedDevices                 $parts = if ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Matematică]:Min(12, $bucketInfo.BucketKey.Length))) }                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x confirmat)" "INFO"             }         }         # === PROCESAȚI BUCKETURI ZERO-SUCCES (răspândite în producătorii OEM cu urmărirea per-OEM) ===         # Obiectiv: Răspândirea riscului între producătoriI OEM diferiți, urmărirea independentă a progresului per OEM         # Fiecare producător OEM progresează pe baza propriului istoric de succes:         # - OEM cu succese: Devine mai multe dispozitive următorul val (2^waveCount)         # - OEM fără succese: Rămâne la nivelul actual până la succesul confirmat         dacă ($zeroSuccessBuckets.Count -gt 0) {             # Inițializarea numărului de unde per-OEM, dacă nu există             dacă (-nu $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             # Grupați bucketurile de succes zero de către OEM             $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OEM }             $totalZeroSuccessAdded = 0             $oemsDeployedTo = @()             pentru a căuta ($oemGroup în $oemBuckets) {                 $oemName = $oemGroup.Name                 # Ia acest număr de undă OEM (începe de la 0)                 $oemWaveCount = dacă ($RolloutState.OEMWaveCounts[$oemName]) {                     $RolloutState.OEMWaveCounts[$oemName]                 } altfel{ 0 }                 # Calculați dispozitivele pentru ACEST OEM: 2^waveCount (1, 2, 4, 8...)                 $devicesForThisOEM = [int][Matematică]::P ow(2; $oemWaveCount)                 $devicesForThisOEM = [Matematică]:Max(1; $devicesForThisOEM)                 $oemDevicesAdded = 0                 # Alegeți din fiecare recipient de sub acest OEM                 foreach ($bucketInfo în $oemGroup.Group) {                     $remaining = $devicesForThisOEM - $oemDevicesAdded                     dacă ($remaining -le 0) { break }                     $toTake = [Matematică]::Min($remaining, $bucketInfo.Devices.Count)                     dacă ($toTake -gt 0) {                         $selectedDevices = @($bucketInfo.Devices | Select-Object -First $toTake)                         $waveDevices += $selectedDevices                         $oemDevicesAdded += $toTake                         $totalZeroSuccessAdded += $toTake                         $parts = if ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Matematică]:Min(12, $bucketInfo.BucketKey.Length))) }                         $displayName = "$($parts[0]) - $($parts[1])"                         Write-Log " [ZERO-SUCCESS] $displayName - Deploying=$toTake (undă OEM $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN"                     }                 }                 dacă ($oemDevicesAdded -gt 0) {                     Write-Log " OEM: $oemName - Wave $oemWaveCount, Dispozitive $oemDevicesAdded adăugate" "INFO"                     $oemsDeployedTo += $oemName                 }             }             # Urmăriți producătorii OEM pe care am implementat (pentru incrementare la următoarea verificare a succesului)             dacă ($oemsDeployedTo.Count -gt 0) {                 $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo                 Write-Log "Implementare fără succes: $totalZeroSuccessAdded dispozitive pentru toți producătorii OEM $($oemsDeployedTo.Count) " INFO"             }         }     }     dacă (@($waveDevices). Count -eq 0) {         $null de returnare     }     $waveDevices de returnare }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

# ============================================================================ # GPO DEPLOYMENT (INLINED - creează GPO, grup de securitate, link-uri) # ============================================================================

function Deploy-GPOForWave {     param(         [șir]$GPOName,         [șir]$TargetOU,         [șir]$SecurityGroupName,         [matrice]$WaveHostnames,         [bool]$DryRun = $false     )     # AdMX Politica: SecureBoot.admx - SecureBoot_AvailableUpdatesPolicy     # Cale de registry: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot     # Nume valoare: AvailableUpdatesPolicy     # Valoare activată: 22852 (0x5944) - Actualizați toate cheile Secure Boot + bootmgr     # Valoare dezactivată: 0     Nr. de eroare     # Utilizarea Politică de grup Preferences (GPP) pentru o implementare fiabilă a căii HKLM\SYSTEM     # GPP creează setări sub: Computer Configuration > Preferences > Windows Settings > Registry     $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot"     $RegistryValueName = "AvailableUpdatesPolicy"     $RegistryValue = 22852 # 0x5944 - se potrivește cu ADMX enabledValue     Write-Log "Se implementează GPO: $GPOName" "WAVE"     Write-Log "Registry: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X'))" "INFO"     dacă ($DryRun) {         Write-Log "[DRYRUN] Ar crea GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Ar crea un grup de securitate: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] adaugă $(@($WaveHostnames). Count) computere de grupat" "INFO"         Write-Log "[DRYRUN] leagă GPO la: $TargetOU" "INFO"         $true de returnare     }     încercați {         # Import module necesare         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } captură {         Write-Log "Nu s-a reușit importul modulelor necesare (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         $false de returnare     }     # Pasul 1: Crearea sau de a lua GPO     $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     dacă ($existingGPO) {         Write-Log "GPO există deja: $GPOName" "INFO"         $gpo = $existingGPO     } altfel, {         încercați {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "GPO creat: $GPOName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea GPO: $($_. Exception.Message)" "ERROR"             $false de returnare         }     }     # Pasul 2: Set registry value using Politică de grup Preferences (GPP)     # GPP este mai fiabil pentru HKLM \SYSTEM paths than Set-GPRegistryValue     încercați {         # Încercați mai întâi să eliminați orice preferință existentă pentru această valoare (pentru a evita dublurile)         Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue         # Crearea Preferință de registry GPP cu acțiunea "Replace"         # Înlocuire = Creare dacă nu există, Actualizați dacă există (cea mai fiabilă)         # Update = Actualizare numai dacă există (nu reușește dacă valoarea nu există)         Set-GPPrefRegistryValue -Name $GPOName '             -Computer contextual '             -Înlocuire acțiune '             -$RegistryKey cheie '             -ValueName $RegistryValueName '             -Tip DWord '             -Valoare $RegistryValue         Write-Log "Preferință de registry GPP configurată: $RegistryValueName = 0x5944 (Action=Replace)" "OK"     } captură {         Write-Log "GPP nu a reușit, încearcă Set-GPRegistryValue: $($_. Exception.Message)" "WARN"         # Rezervă la Set-GPRegistryValue (funcționează dacă ADMX este implementat)         încercați {             Set-GPRegistryValue -nume $GPOName '                 -Cheie $RegistryKey '                 -ValueName $RegistryValueName '                 -Tip DWord '                 -$RegistryValue de valori             Write-Log "Registry configurat prin Set-GPRegistryValue: $RegistryValueName = 0x5944" "OK"         } captură {             Write-Log "Nu s-a reușit setarea valorii de registry: $($_. Exception.Message)" "ERROR"             $false de returnare         }     }     # Pasul 3: Crearea sau obținerea unui grup de securitate     $existingGroup = Get-ADGroup -Filtru "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     dacă (-nu $existingGroup) {         încercați {             $group = New-ADGroup -Nume $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers targeted for Secure Boot rollout - $GPOName" '                 -PassThru             Write-Log "Grup de securitate creat: $SecurityGroupName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea grupului de securitate: $($_. Exception.Message)" "ERROR"             $false de returnare         }     } altfel, {         Write-Log "Există grupul Securitate: $SecurityGroupName" "INFORMAȚII"         $group = $existingGroup     }     # Pasul 4: Adăugarea computerelor la grupul de securitate     $added = 0     $failed = 0     pentru a căuta ($hostname în $WaveHostnames) {         încercați {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } captură {             $failed++         }     }     Write-Log "S-au adăugat computere $added la grupul de securitate ($failed nu s-au găsit în AD)" "OK"     # Pasul 5: Configurarea filtrării de securitate în GPO     încercați {         # Eliminați permisiunea implicită "Utilizatori autentificați" Aplicați permisiunea (păstrați citirea)         Set-GPPermission -Name $GPOName -TargetName "Utilizatori autentificați" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         # Adăugați aplicați permisiunea pentru grupul nostru de securitate         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Filtrare de securitate configurată pentru: $SecurityGroupName" "OK"     } captură {         Write-Log "Nu s-a reușit configurarea filtrării de securitate: $($_. Exception.Message)" "WARN"         Write-Log "GPO se poate aplica la toate computerele din OU legate - verificați manual" "AVERTIZARE"     }     # Pasul 6: Link GPO la OU (CRITICĂ pentru politica de aplicat)     dacă ($TargetOU) {         încercați {             $existingLink = Get-GPInheritance -Țintă $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             dacă (-nu $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "GPO legat la: $TargetOU" "OK"                 Write-Log "GPO se va aplica la următoarea gpupdate pe computerele țintă" "INFO"             } altfel, {                 Write-Log "GPO deja legat la OU țintă" "INFO"             }         } captură {             Write-Log "CRITIC: Nu s-a reușit legarea GPO la OU: $($_. Exception.Message)" "ERROR"             Write-Log "GPO a fost creat, dar NU LEGAT - NU se va aplica la orice computere!" "EROARE"             Write-Log "Remediere manuală necesară: New-GPLink -Nume "$GPOName" -Țintă "$TargetOU" -LinkEnabled Da" "ERROR"             $false de returnare         }     } altfel, {         Write-Log "AVERTISMENT: Nu s-a specificat TargetOU - GPO creat, dar NU ESTE LEGAT!" "EROARE"         Write-Log "Este necesară legarea manuală pentru ca GPO să aibă efect" "ERROR"         Write-Log "Rulare: New-GPLink -Nume "$GPOName" - Țintă "<-domeniul-DN>" -LinkEnabled Da" "EROARE"     }     # Pasul 7: Verificarea configurării GPO     Write-Log "Se verifică configurația GPO..." "INFORMAȚII"     încercați {         $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop         Write-Log "Stare GPO: $($gpoReport.GpoStatus)" "INFO"         # Verificați dacă este configurată setarea de registry         $regSettings = Get-GPRegistryValue -Name $GPOName -Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue         dacă (-nu $regSettings) {             # Încercați GPP registry check (cale diferită în GPO)             Write-Log "Se verifică preferințele de registry GPP..." "INFORMAȚII"         }     } captură {         Write-Log "Imposibil de verificat GPO: $($_. Exception.Message)" "WARN"     }     $true de returnare }                                                                                                

# ============================================================================ # WINCS DEPLOYMENT (Alternativ la AvailableUpdatesPolicy GPO) nr. ============================================================================ # Referință: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe Nr. de eroare # Comenzi WinCS (rulează pe punctul final sub context SISTEM): # Interogare: WinCsFlags.exe /query - F33E0C8E002 cheie # Se aplică: WinCsFlags.exe /apply --cheie "F33E0C8E002" # Resetare: WinCsFlags.exe /reset --key "F33E0C8E002" Nr. de eroare # Această metodă implementează un GPO cu o activitate programată care rulează WinCsFlags.exe /apply # ca SISTEM pe punctele finale țintă. Similar cu modul în care este implementat scriptul de detectare, # dar rulează o dată (la pornire), în loc de zi cu zi.

function Deploy-WinCSGPOForWave {     <#     . SINOPSIS         Implementați activarea bootării securizate WinCS prin activitatea programată GPO.. DESCRIEREA /         Creează un GPO care implementează o activitate planificată pentru a rula WinCsFlags.exe /apply         sub Context SISTEM la pornirea computerului. Grupul de securitate controlează direcționarea.. PARAMETER GPOName         Numele GPO-ului.. VALOARE ȚINTĂ PARAMETRU         OU pentru a lega GPO-ul la.. PARAMETER SecurityGroupName         Grup de securitate pentru filtrareA GPO.. PARAMETER WaveHostnames         Nume de gazdă de adăugat la grupul de securitate.. PARAMETER WinCSKey         Tasta WinCS de aplicat (implicită: F33E0C8E002).. PARAMETER DryRun         Dacă este adevărat, înregistrați doar ceea ce s-ar face.#>     param(         [Parametru(Obligatoriu = $true)]         [șir]$GPOName,                  [Parametru(Obligatoriu = $false)]         [șir]$TargetOU,                  [Parametru(Obligatoriu = $true)]         [șir]$SecurityGroupName,                  [Parametru(Obligatoriu = $true)]         [matrice]$WaveHostnames,                  [Parametru(Obligatoriu = $false)]         [șir]$WinCSKey = "F33E0C8E002",                  [Parametru(Obligatoriu = $false)]         [bool]$DryRun = $false     )          # Configurarea activității programate pentru WinCsFlags.exe     $TaskName = "SecureBoot-WinCS-Apply"     $TaskPath = "\Microsoft\Windows\SecureBoot\"     $TaskDescription = "Aplică configurația secure boot prin WinCS - cheie: $WinCSKey"          Write-Log "Se implementează WinCS GPO: $GPOName" "WAVE"     Write-Log "Activitatea va rula: WinCsFlags.exe /apply --key ""$WinCSKey""" "INFO"     Write-Log "Trigger: La pornirea sistemului (rulează o dată ca SISTEM)" "INFO"          dacă ($DryRun) {         Write-Log "[DRYRUN] Ar crea GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Ar crea un grup de securitate: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] ar adăuga $(@($WaveHostnames). Count) computere de grupat" "INFO"         Write-Log "[DRYRUN] Ar implementa activitatea planificată: $TaskName" "INFO"         Write-Log "[DRYRUN] Ar lega GPO la: $TargetOU" "INFO"         returnează @{             Succes = $true             GPOCreated = $false             GroupCreated = $false             ComputersAdded = 0         }     }          încercați {         # Import module necesare         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } captură {         Write-Log "Nu s-a reușit importul modulelor necesare (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }     }          # Pasul 1: Crearea sau de a lua GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     dacă ($gpo) {         Write-Log "GPO există deja: $GPOName" "INFO"     } altfel, {         încercați {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "GPO creat: $GPOName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea GPO: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }         }     }          # Pasul 2: Crearea XML-ul activității programate pentru implementarea GPO     # Aceasta creează o activitate care rulează WinCsFlags.exe /apply la pornire     $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Versiune activitate="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">  >InformațiiÎnregistrare <    >descriere </>$TaskDescription< descriere     WinCsFlags.exe1 autor>SYSTEM</Author>  >WinCsFlags.exe5 /RegistrationInfo  > Triggere WinCsFlags.exe7     WinCsFlags.exe9 BootTrigger>       <activat>adevărat</activat>       <întârziere>PT5M</Întârziere>     </BootTrigger>  ></Triggere  > <directori     <id principal="Autor">       <UserId>S-1-5-18</UserId>       <RunLevel>cea mai înaltă</RunLevel>     </principal>  ></principal  > setări <     <>MultipleInstancesPolicy IgnoreNew</MultipleInstancesPolicy>     <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>     <StopIfGoingOnBatteries><fals /StopIfGoingOnBatteries>     <AllowHardTerminate>true</AllowHardTerminate>     <StartAtunci când>adevărat</StartAtunci când>     <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>    >idleSettings <       <StopOnIdleEnd><fals /StopOnIdleEnd>       <RestartOnIdle><fals /RestartOnIdle>     </IdleSettings>     <AllowStartOnDemand>true</AllowStartOnDemand>     <activat></activat> activat     <<ascunsă>>ascunsă     <RunOnlyIfIdle>><fals /RunOnlyIfIdle     WinCsFlags.exe03 DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>     WinCsFlags.exe07 UseUnifiedSchedulingEngine><adevărat /UseUnifiedSchedulingEngine>     WinCsFlags.exe11 WakeToRun><fals /WakeToRun>     WinCsFlags.exe15 ExecutionTimeLimit>PT1H</ExecutionTimeLimit>     WinCsFlags.exe19 DeleteExpiredTaskAfter>P30D</DeleteExpiredTaskAfter>     WinCsFlags.exe23 prioritate>7</prioritate>  >WinCsFlags.exe27 /Setări   WinCsFlags.exe29 Actions Context="Author"WinCsFlags.exe30    >exec WinCsFlags.exe31      >WinCsFlags.exe<comandă WinCsFlags.exe33 /> de comandă       WinCsFlags.exe37 Argumente>/apply "$WinCSKey"WinCsFlags.exe39 /Argumente>     WinCsFlags.exe41 /Exec>  >WinCsFlags.exe43 /Acțiuni WinCsFlags.exe45 />de activitate " @

    # Step 3: Deploy scheduled task via GPO Preferences     # Stocați XML-ul activității în SYSVOL pentru activitatea imediată activități planificate GPO     încercați {         $gpoId = $gpo. Id.ToString()         $sysvolPath = "\\$(Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Policies\{$gpoId}\Machine\Preferences\ScheduledTasks"         if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Directory -Path $sysvolPath -Force | Nul în exterior         }         # Crearea ScheduledTasks.xml pentru GPP         $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">   <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U">       <Versiune activitate="1.3">        ><RegistrationInfo           <descriere>$TaskDescription</descriere>        ></RegistrationInfo        >de conturi principale <           <id principal="Autor">             <UserId>NT AUTHORITY\System</UserId>             <LogonType>S4U</LogonType>             <RunLevel>cea mai înaltă</RunLevel>           </principal>        ></Principals        > setări <          >idleSettings <             <durată><PT5M /Durată>             <WaitTimeout>PT1H</WaitTimeout>             <StopOnIdleEnd><fals /StopOnIdleEnd>             <RestartOnIdle><fals /RestartOnIdle>           </IdleSettings>           <>MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy           <DisallowStartIfOnBatteries><fals /DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries><fals /StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <StartAtunci când>adevărat</StartAtunci când>           <AllowStartOnDemand>true</AllowStartOnDemand>           <activat>adevărat</activat>           <><fals /> ascuns           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <prioritate>7</prioritate>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>        ></Setări        > Triggere <           <> TimeTrigger             <StartBoundary>$(Get-Date -Format 'yyyy-MM-dd')T00:00:00</StartBoundary>             <activat>adevărat</activat>          ></TimeTrigger        ></Triggere        > acțiuni <           <exec>            >WinCsFlags.exe<comandă </> comandă             <Argumente>/apply "$WinCSKey" cheie </Argumente>           </Exec>        ></Actions       </> de activitate    > </Properties  ></ImmediateTaskV2 >< /ScheduledTasks "@         $gppTaskXml | Out-File -FilePath ($sysvolPath "ScheduledTasks.xml de asociere") -Codificare UTF8 -Force         Write-Log "Activitate planificată implementată în GPO: $TaskName" "OK"     } captură {         Write-Log "Nu s-a reușit implementarea XML-ului activității programate: $($_. Exception.Message)" "WARN"         Write-Log "Revenirea la implementarea WinCS bazată pe registry" "INFO"         # Rezervă: Utilizarea WinCS registry abordare în cazul în care activitatea programată GPP nu reușește         # WinCS poate fi, de asemenea, declanșat prin cheia de registry         # (Implementarea depinde de WINCS registry API, dacă este disponibilă)     }     # Pasul 4: Crearea sau preluarea grupului de securitate     $group = Get-ADGroup -Filtru "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     dacă (-nu $group) {         încercați {             $group = New-ADGroup -Nume $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers targeted for Secure Boot WinCS rollout - $GPOName" '                 -PassThru             Write-Log "Grup de securitate creat: $SecurityGroupName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea grupului de securitate: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }         }     } altfel, {         Write-Log "Există un grup de securitate: $SecurityGroupName" "INFO"     }     # Pasul 5: Adăugarea computerelor la grupul de securitate     $added = 0     $failed = 0     foreach ($hostname în $WaveHostnames) {         încercați {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } captură {             $failed++         }     }     Write-Log "S-au adăugat computere $added la grupul de securitate ($failed nu s-au găsit în AD)" "OK"     # Pasul 6: Configurarea filtrării de securitate în GPO     încercați {         Set-GPPermission -Name $GPOName -TargetName "Utilizatori autentificați" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Filtrare de securitate configurată pentru: $SecurityGroupName" "OK"     } captură {         Write-Log "Nu s-a reușit configurarea filtrării de securitate: $($_. Exception.Message)" "WARN"     }     # Pasul 7: Link GPO la OU     dacă ($TargetOU) {         încercați {             $existingLink = Get-GPInheritance -Țintă $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             dacă (-nu $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "GPO legat la: $TargetOU" "OK"             } altfel, {                 Write-Log "GPO deja legat la OU țintă" "INFO"             }         } captură {             Write-Log "CRITIC: Nu s-a reușit legarea GPO la OU: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = "Linkul GPO nu a reușit: $($_. Excepție.Mesaj)" }         }     }     Write-Log "Implementarea WinCS GPO s-a terminat" "OK"     Write-Log "Mașinile vor rula WinCsFlags.exe la următoarea reîmprospătare GPO + repornire/pornire" "INFO"     returnează @{         Succes = $true         GPOCreated = $true         GroupCreated = $true         ComputersAdded = $added         ComputersFailed = $failed     } }                                                                                        

# Wrapper function to maintain compatibility with main loop funcție Deploy-WinCSForWave {     param(         [Parametru(Obligatoriu = $true)]         [matrice]$WaveHostnames,         [Parametru(Obligatoriu = $false)]         [șir]$WinCSKey = "F33E0C8E002",         [Parametru(Obligatoriu = $false)]         [șir]$WavePrefix = "SecureBoot-Rollout",         [Parametru(Obligatoriu = $false)]         [int]$WaveNumber = 1,         [Parametru(Obligatoriu = $false)]         [șir]$TargetOU,         [Parametru(Obligatoriu = $false)]         [bool]$DryRun = $false     )     $gpoName = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $securityGroup = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $result = Deploy-WinCSGPOForWave '         -GPOName $gpoName '         -TargetOU $TargetOU '         -SecurityGroupName $securityGroup '         -WaveHostnames $WaveHostnames '         -WinCSKey $WinCSKey '         -DryRun $DryRun     # Conversie la formatul de returnare așteptat     returnează @{         Succes = $result. Succes         Aplicat = $result. ComputersAdded         Ignorat = 0         Nu s-a reușit = dacă ($result. ComputersFailed) { $result. ComputersFailed } else { 0 }         Rezultate = @()     } }                                                            

# ============================================================================ # ACTIVAȚI IMPLEMENTAREA ACTIVITĂȚII nr. ============================================================================ # Implementați Enable-SecureBootUpdateTask.ps1 pe dispozitivele cu activitate programată dezactivată.# Utilizează un GPO cu o activitate planificată imediat, care rulează o dată.

function Deploy-EnableTaskGPO {     <#     . SINOPSIS         Implementați Enable-SecureBootUpdateTask.ps1 prin activitatea planificată GPO.. DESCRIEREA /         Creează un GPO care implementează o activitate planificată unică pentru a activa         Secure-Boot-Update activitate programată pe dispozitive țintă.. VALOARE ȚINTĂ PARAMETRU         OU pentru a lega GPO-ul la.. PARAMETER TargetHostnames         Numele de gazdă ale dispozitivelor cu activitate dezactivată (din raportul de agregare).. PARAMETER DryRun         Dacă este adevărat, înregistrați doar ceea ce s-ar face.#>     param(         [Parametru(Obligatoriu = $false)]         [șir]$TargetOU,                  [Parametru(Obligatoriu = $true)]         [matrice]$TargetHostnames,                  [Parametru(Obligatoriu = $false)]         [bool]$DryRun = $false     )          $GPOName = "SecureBoot-EnableTask-Remediation"     $SecurityGroupName = "SecureBoot-EnableTask-Devices"     $TaskName = "SecureBoot-EnableTask-OneTime"     $TaskDescription = "Activitate unică pentru a activa activitatea programată Secure-Boot-Update"          Write-Log "=" * 70 "INFO"     Write-Log "SE IMPLEMENTEAZĂ ACTIVARE REMEDIERE ACTIVITATE" "INFORMAȚII"     Write-Log "=" * 70 "INFO"     Write-Log "Dispozitive țintă: $($TargetHostnames.Count)" "INFO"     Write-Log "GPO: $GPOName" "INFO"     Write-Log "Grup de securitate: $SecurityGroupName" "INFORMAȚII"          dacă ($DryRun) {         Write-Log "[DRYRUN] Ar crea GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Ar crea un grup de securitate: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Ar adăuga computere $($TargetHostnames.Count) pentru a grupa" "INFO"         Write-Log "[DRYRUN] Ar implementa activitatea planificată unică pentru a activa Secure-Boot-Update" "INFO"         Write-Log "[DRYRUN] Ar lega GPO la: $TargetOU" "INFO"         returnează @{             Succes = $true             ComputersAdded = 0             DryRun = $true         }     }          încercați {         # Import module necesare         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory -ErrorAction Stop     } captură {         Write-Log "Nu s-a reușit importul modulelor necesare: $($_. Exception.Message)" "ERROR"         returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }     }          # Pasul 1: Crearea sau de a lua GPO     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     dacă ($gpo) {         Write-Log "GPO există deja: $GPOName" "INFO"     } altfel, {         încercați {             $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Task Enable Remediation - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')"             Write-Log "GPO creat: $GPOName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea GPO: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }         }     }          #Step 2: Deploy scheduled task XML to GPO SYSVOL     # Activitatea rulează o comandă PowerShell pentru a activa activitatea Secure-Boot-Update     încercați {         $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks"                  dacă (-nu (Cale-test $sysvolPath)) {             New-Item -ItemType Directory -Cale $sysvolPath -Force | Nul în exterior         }                  Comanda # PowerShell pentru a activa activitatea Secure-Boot-Update         $enableCommand = 'schtasks.exe /Change /TN "\Microsoft\Windows\PI\Secure-Boot-Update" /ENABLE 2>$null; if ($LASTEXITCODE -ne 0) { Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI\" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue | Enable-ScheduledTask }'                  # Encode command for safe XML embedding         $encodedCommand = [Conversie]::ToBase64String([Codificare.Text]:Unicode.GetBytes($enableCommand))                  $taskGuid = [guid]::NewGuid(). ToString("B"). ToUpper()                  # GPP Activitate planificată XML - Activitate imediată care rulează o dată         $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">   <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image=""0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="$taskGuid" removePolicy="1" userContext="0">     <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\SYSTEM" logonType="S4U">       <Versiune activitate="1.3">        >InformațiiÎnregistrare <          >descriere>$TaskDescription</descriere <         </RegistrationInfo>        > <conturi principale           id principal <="Autor">             <UserId>S-1-5-18</UserId>             <RunLevel>cel mai înalt</RunLevel>           </principal>         </Principals>        >Setări <          >idleSettings <             <Durată>PT5M</Durată>             <WaitTimeout>pt1H</WaitTimeout>             <StopOnIdleEnd><fals /StopOnIdleEnd>             <RestartOnIdle><fals /RestartOnIdle>           </IdleSettings>           <>MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <StopIfGoingOnBatteries><fals /StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <StartAtunci când>adevărat</StartAtunci când>           <AllowStartOnDemand>true</AllowStartOnDemand>           <activat>adevărat</activat>           <></>ascuns ascuns >           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <prioritate>7</prioritate>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>        ></Setări        > acțiuni <          >exec <            >comandă </>powershell.exe< de comandă             Argumente <>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Argumente>           </Exec>        ></acțiuni       </> de activitate    > </Properties  ></ImmediateTaskV2 >< /ScheduledTasks "@                  $gppTaskXml | Out-File -FilePath ($sysvolPath "ScheduledTasks.xml de asociere") -Codificare UTF8 -Force         Write-Log "S-a implementat activitatea planificată unică în GPO: $TaskName" "OK"              } captură {         Write-Log "Nu s-a reușit implementarea XML-ului activității programate: $($_. Exception.Message)" "ERROR"         returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }     }          # Pasul 3: Crearea sau obținerea unui grup de securitate     $group = Get-ADGroup -Filtru "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     dacă (-nu $group) {         încercați {             $group = New-ADGroup - $SecurityGroupName de nume '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers with disabled Secure-Boot-Update task - targeted for remediation" '                 -PassThru             Write-Log "Grup de securitate creat: $SecurityGroupName" "OK"         } captură {             Write-Log "Nu s-a reușit crearea grupului de securitate: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = $_. Excepție.Mesaj }         }     } altfel, {         Write-Log "Grupul securitate există: $SecurityGroupName" "INFO"     }          # Pasul 4: Adăugarea computerelor la grupul de securitate     $added = 0     $failed = 0     pentru a căuta ($hostname în $TargetHostnames) {         încercați {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } captură {             $failed++             Write-Log "Computer negăsit în AD: $hostname" "AVERTIZARE"         }     }     Write-Log "S-au adăugat computere $added la grupul de securitate ($failed nu s-a găsit în AD)" "OK"          # Pasul 5: Configurarea filtrării de securitate în GPO     încercați {         Set-GPPermission -Name $GPOName -TargetName "Utilizatori autentificați" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Filtrare de securitate configurată pentru: $SecurityGroupName" "OK"     } captură {         Write-Log "Nu s-a reușit configurarea filtrării de securitate: $($_. Exception.Message)" "WARN"     }          # Pasul 6: Link GPO la OU     dacă ($TargetOU) {         încercați {             $existingLink = Get-GPInheritance -Țintă $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }                          dacă (-nu $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "GPO legat la: $TargetOU" "OK"             } altfel, {                 Write-Log "GPO deja legat la OU țintă" "INFO"             }         } captură {             Write-Log "Nu s-a reușit legarea GPO la OU: $($_. Exception.Message)" "ERROR"             returnează @{ Success = $false; Eroare = "Linkul GPO nu a reușit: $($_. Excepție.Mesaj)" }         }     } altfel, {         Write-Log "Nu s-a specificat TargetOU - GPO va trebui să fie legat manual" "WARN"     }          Write-Log "" "INFO"     Write-Log "ENABLE TASK DEPLOYMENT COMPLETE" "OK"     Write-Log "Dispozitivele vor rula activitatea de activare la următoarea reîmprospătare GPO (gpupdate)" "INFO"     Write-Log "Activitatea rulează o dată ca SISTEM și activează Secure-Boot-Update" "INFO"     Write-Log "" "INFO"          returnează @{         Succes = $true         ComputersAdded = $added         ComputersFailed = $failed         GPOName = $GPOName         SecurityGroup = $SecurityGroupName     } }

# ============================================================================ # ACTIVAȚI ACTIVITATEA PE DISPOZITIVELE DEZACTIVATE nr. ============================================================================ dacă ($EnableTaskOnDisabled) {     Write-Host ""     Write-Host ("=" * 70) - Culoare prim plan galben     Write-Host " ENABLE TASK REMEDIATION - Fix disabled Scheduled Tasks" -ForegroundColor Yellow     Write-Host ("=" * 70) -Prim planColor galben     Write-Host ""     # Găsiți dispozitivele cu activitate dezactivată din datele de agregare     dacă (-nu $AggregationInputPath) {         Write-Host "ERROR: -AggregationInputPath is required to identify devices with disabled task" -ForegroundColor Red         Write-Host "Utilizare: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <cale> -ReportBasePath <cale>" -Prim-prim planColor gri         ieșire 1     }     Write-Host "Se scanează după dispozitive cu activitatea Secure-Boot-Update dezactivată..." -ForegroundColor Cyan     # Încărcați fișiere JSON și găsiți dispozitive cu activitate dezactivată     $jsonFiles = Get-ChildItem -Cale $AggregationInputPath -Filtru "*.json" -Recurse -ErrorAction SilentlyContinue |                  Where-Object { $_. Name -notmatch "ScanHistory|RolloutState|RolloutPlan" }     $disabledTaskDevices = @()     foreach ($file în $jsonFiles) {         încercați {             $device = Get-Content $file. FullName -Raw | ConvertFrom-Json             dacă ($device. SecureBootTaskEnabled -eq $false -sau                 $device. SecureBootTaskStatus -eq "Dezactivat" -sau                 $device. SecureBootTaskStatus -eq 'NotFound') {                 # Includeți doar dispozitivele care nu au fost actualizate deja (niciun eveniment 1808)                 dacă ([int]$device. Eveniment1808Count -eq 0) {                     $disabledTaskDevices += $device. Hostname                 }             }         } captură {             # Ignorare fișiere nevalide         }     }     $disabledTaskDevices = $disabledTaskDevices | Select-Object - unic     dacă ($disabledTaskDevices.Count -eq 0) {         Write-Host ""         Write-Host "Nu s-au găsit dispozitive cu activitatea Secure-Boot-Update dezactivată". -Prim-planColor Verde         Write-Host "Toate dispozitivele au activitatea activată sau au fost actualizate deja". -Prim-planColor gri         ieșire 0     }     Write-Host ""     Write-Host dispozitive "Found $($disabledTaskDevices.Count) with disabled task:" -ForegroundColor Yellow     $disabledTaskDevices | Select-Object -Primele 20 | ForEach-Object { Write-Host " - $_" -Prim-planColor gri }     dacă ($disabledTaskDevices.Count -gt 20) {         Write-Host " ... și $($disabledTaskDevices.Count - 20) mai mult" -Prim-planColor gri     }     Write-Host ""     # Implementați GPO-ul de activitate activat     $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun     dacă ($result. Succes) {         Write-Host ""         Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green         Write-Host " Computere adăugate la grupul de securitate: $($result. ComputersAdded)" -Prim-planColor Cyan         dacă ($result. ComputersFailed -gt 0) {             Write-Host " Computerele nu se găsesc în AD: $($result. ComputersFailed)" -Prim-planColor galben         }         Write-Host ""         Write-Host "PAȘII URMĂTORI:" -Culoare prim plan alb         Write-Host " 1.                                              Dispozitivele vor primi GPO la următoarea reîmprospătare (gpupdate /force)" -Prim-planColor Gray         Write-Host " 2. Activitatea unică va activa Secure-Boot-Update" -ForegroundColor Gray         Write-Host " 3. Rulați din nou agregarea pentru a verifica dacă activitatea este activată acum" -Prim planColor Gri     } altfel, {         Write-Host ""         Write-Host "FAILED: Could not deploy Enable Task GPO" -ForegroundColor Red         Write-Host "Eroare: $($result. Eroare)" -Prim-planColor roșu     }          ieșire 0 }

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

Write-Host "" Write-Host ("=" * 80) -Prim-planColor Cyan Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - IMPLEMENTARE CONTINUĂ" -ForegroundColor Cyan Write-Host ("=" * 80) -Prim planColor Cyan Write-Host ""

if ($DryRun) {     Write-Host "[DRY RUN MODE]" -ForegroundColor Magenta }

if ($UseWinCS) {     Write-Host "[MODUL WinCS]" -Prim-planColor galben     Write-Host "Utilizarea WinCsFlags.exe în loc de GPO/AvailableUpdatesPolicy" -Culoare prim plan galben     Write-Host "Cheie WinCS: $WinCSKey" -Culoare prim plan gri     Write-Host "" }

Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Cale de intrare: $AggregationInputPath" "INFO" Write-Log "Cale raport: $ReportBasePath" "INFO" dacă ($UseWinCS) {     Write-Log "Metodă de implementare: WinCS (WinCsFlags.exe /apply --key ""$WinCSKey")" "INFO" } altfel, {     Write-Log "Metodă de implementare: GPO (AvailableUpdatesPolicy)" "INFO" }

# Resolve TargetOU - default to domain root for domain-wide coverage # Necesar doar pentru metoda de implementare GPO (WinCS nu necesită AD/GPO) dacă (-nu $UseWinCS -și -not $TargetOU) {     încercați {         # Încercați mai multe metode pentru a obține DN de domeniu         $domainDN = $null         # Metoda 1: Get-ADDomain (necesită RSAT-AD-PowerShell)         încercați {             Import-Module ActiveDirectory -ErrorAction Stop             $domainDN = (Get-ADDomain -ErrorAction Stop). Nume distins         } captură {             Write-Log "Get-ADDomain nu a reușit: $($_. Exception.Message)" "WARN"         }         # Metoda 2: Utilizați RootDSE prin ADSI         dacă (-nu $domainDN) {             încercați {                 $rootDSE = [ADSI]"LDAP://RootDSE"                 $domainDN = $rootDSE.defaultNamingContext.ToString()             } captură {                 Write-Log "ADSI RootDSE nu a reușit: $($_. Exception.Message)" "WARN"             }         }         # Metoda 3: Analiza de la computer de domeniu de membru         dacă (-nu $domainDN) {             încercați {                 $domain = [System.DirectoryServices.ActiveDirectory.Domain]:GetComputerDomain()                 $domainDN = "DC=" + ($domain. Nume -înlocuire '\.', ',DC=')             } captură {                 Write-Log "GetComputerDomain nu a reușit: $($_. Exception.Message)" "WARN"             }         }         dacă ($domainDN) {             $TargetOU = $domainDN             Write-Log "Țintă: Rădăcină domeniu ($domainDN) - GPO va aplica la nivel de domeniu prin filtrarea grupurilor de securitate" "INFO"         } altfel, {             Write-Log "Nu s-a putut determina DN domeniu - GPO va fi creat, dar NU LEGAT!" "EROARE"             Write-Log "Specificați parametrul -TargetOU sau legați GPO manual după creare" "ERROR"             $TargetOU = $null         }     } captură {         Write-Log "Nu s-a putut obține DN domeniu - GPO va fi creat, dar nu legat.                                     Legați manual, dacă este necesar." "AVERTIZARE"         Write-Log "Eroare: $($_. Exception.Message)" "WARN"         $TargetOU = $null     } } altfel, {     Write-Log "OU țintă: $TargetOU" "INFORMAȚII" }

Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Interval sondaj: $PollIntervalMinutes minute" "INFORMAȚII" dacă ($LargeScaleMode) {     Write-Log "LargeScaleMode activat (dimensiune lot: $ProcessingBatchSize, eșantion jurnal: $DeviceLogSampleSize)" "INFO" }

# ============================================================================ # VERIFICARE CERINȚĂ PRELIMINARĂ: Verificați dacă detectarea este implementată și funcționează # ============================================================================

Write-Host "" Write-Log "Se verifică cerințele preliminare..." "INFORMAȚII"

$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath dacă (-nu $detectionCheck.IsDeployed) {     Write-Log $detectionCheck.Mesaj "EROARE"     Write-Host ""     Write-Host "OBLIGATORIU: implementați mai întâi infrastructura de detectare:" -Culoare prim plan galben     Write-Host " 1. Rulare: Deploy-GPO-SecureBootCollection.ps1 -OUPath 'OU=...' -OutputPath '\\server\SecureBootLogs$'" -ForegroundColor Cyan     Write-Host " 2. Așteptați ca dispozitivele să raporteze (12-24 de ore)" -Prim-planColor Cyan     Write-Host " 3. Rulați din nou acest orchestrator" -Prim-planColor Cyan     Write-Host ""     dacă (-nu $DryRun) {         Reveni     } } altfel, {     Write-Log $detectionCheck.Mesaj "OK" }

# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Prospețime date: $($freshness. TotalFiles) fișiere, $($freshness. FreshFiles) proaspăt (<24h), $($freshness. StaleFiles) învechit (>72h)" "INFO" dacă ($freshness. Avertisment) {     Write-Log $freshness. Avertisment "AVERTIZARE" }

# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() dacă ($AllowListPath -sau $AllowADGroup) {     $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup     dacă ($allowedHostnames.Count -gt 0) {         Write-Log "AllowList: NUMAI dispozitivele $($allowedHostnames.Count) vor fi luate în considerare pentru implementare" "INFO"     } altfel, {         Write-Log "AllowList specificat, dar nu s-au găsit dispozitive - acest lucru va bloca toate lansările!" "AVERTIZARE"     } }

# Load VIP/exclusion list (BlockList) $excludedHostnames = @() dacă ($ExclusionListPath -sau $ExcludeADGroup) {     $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup     dacă ($excludedHostnames.Count -gt 0) {         Write-Log "Excludere VIP: dispozitivele $($excludedHostnames.Count) vor fi ignorate din implementare" "INFO"     } }

# Load state $rolloutState = Get-RolloutState $blockedBuckets = Get-BlockedBuckets $adminApproved = Get-AdminApproved $deviceHistory = Get-DeviceHistory

if ($rolloutState.Status -eq "NotStarted") {     $rolloutState.Status = "InProgress"     $rolloutState.StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     Write-Log "Se începe lansarea nouă" "WAVE" }

Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Bucketuri blocate: $($blockedBuckets.Count)" "INFO"

# Main loop - runs until all eligible devices are updated $iterationCount = 0 în timp ce ($true) {     $iterationCount++     Write-Host ""     Write-Host ("=" * 80) - Culoare prim plan alb     Write-Log "=== ITERAȚIE $iterationCount ===" "WAVE"     Write-Host ("=" * 80) - Culoare prim plan alb     # Pasul 1: Rulați agregarea     Write-Log "Pasul 1: Rularea agregării..." "INFORMAȚII"     # Orchestrator reutilizează întotdeauna un singur folder (LargeScaleMode) pentru a evita balonarea discului     # Administratorii care rulează agregatorul obțin manual folderele temporale pentru instantanee punct la timp     $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current"     # Verificați prospețimea datelor înainte de a agregare     $freshness = $AggregationInputPath -JsonPath Get-DataFreshness     Write-Log "Prospețime date: $($freshness. FreshFiles)/$($freshness. TotalFiles) dispozitive raportate în ultimele 24h" "INFO"     dacă ($freshness. Avertisment) {         Write-Log $freshness. Avertisment "AVERTIZARE"     }     $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1"     $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json"     $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     if (Test-Path $aggregateScript) {         dacă (-nu $DryRun) {             # Orchestratorul folosește întotdeauna redarea în flux + incrementală pentru eficiență             # Aggregator auto-elevate la PS7, dacă este disponibil pentru cea mai bună performanță             $aggregateParams = @{                 InputPath = $AggregationInputPath                 Cale Ieșire = $aggregationPath                 StreamingMode = $true                 IncrementalMode = $true                 SkipReportIfUnchanged = $true                 ParallelThreads = 8             }             # Transmiteți rezumatul lansării, dacă există (pentru date despre viteză/proiecție)             if (Test-Cale $rolloutSummaryPath) {                 $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath             }             & $aggregateScript @aggregateParams             # Afișați comanda pentru a genera tabloul de bord HTML complet cu tabelele de dispozitive             Write-Host ""             Write-Host "Pentru a genera tabloul de bord HTML complet cu tabelele Producător/Model, rulați:" -Prim-planColor galben             Write-Host " $aggregateScript -InputPath ""$AggregationInputPath"" -OutputPath ""$aggregationPath""" -Culoare prim plan galben             Write-Host ""         } altfel, {             Write-Log "[DRYRUN] ar rula agregarea" "INFO"             # În DryRun, utilizați direct datele de agregare existente din ReportBasePath             $aggregationPath = $ReportBasePath         }     }     $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     # Pasul 2: Încărcați starea curentă a dispozitivului     Write-Log "Pasul 2: Se încarcă starea dispozitivului..." "INFORMAȚII"     $notUptodateCsv = Get-ChildItem -Cale $aggregationPath -Filtru "*NotUptodate*.csv" -ErrorAction SilențiosContinue |          Where-Object { $_. Nume -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descendent |          Select-Object -Primul 1     dacă (-nu $notUptodateCsv -și -not $DryRun) {         Write-Log "Nu s-au găsit date de agregare.                                            În așteptare..." "AVERTIZARE"         Start-Sleep -secunde ($PollIntervalMinutes * 60)         Continua     }     $notUpdatedDevices = dacă ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } altceva { @() }     Write-Log "Dispozitive ne actualizate: $($notUpdatedDevices.Count)" "INFO"     $notUpdatedIndexes = Get-NotUpdatedIndexes - dispozitive $notUpdatedDevices     # Pasul 3: Actualizați istoricul dispozitivelor (urmărire după nume gazdă)     Write-Log "Pasul 3: Se actualizează istoricul dispozitivelor..." "INFORMAȚII"     Update-DeviceHistory -CurrentDevices $notUpdatedDevices -DeviceHistory $deviceHistory     $deviceHistory istoric Save-DeviceHistory     # Pasul 4: Verificați pentru bucketuri blocate (dispozitive inaccesibile)     $existingBlockedCount = $blockedBuckets.Count     Write-Log "Pasul 4: Se verifică bucketurile blocate (dispozitivele care au depășit perioada de așteptare)..." "INFORMAȚII"     dacă ($existingBlockedCount -gt 0) {         Write-Log "Bucketuri blocate în prezent de la rulările anterioare: $existingBlockedCount" "INFO"     }     dacă ($adminApproved.Count -gt 0) {         Write-Log "bucketuri aprobate Admin (nu vor fi blocate din nou): $($adminApproved.Count)" "INFO"     }     $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState -BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun     dacă ($newlyBlocked.Count -gt 0) {         Save-BlockedBuckets $blockedBuckets blocat         Write-Log "Bucketuri nou blocate (această iterație): $($newlyBlocked.Count)" "BLOCAT"     }     # Pasul 4b: Auto-deblocare bucketuri în cazul în care dispozitivele au actualizat     $autoUnblocked = Update-AutoUnblockedBuckets -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize     dacă ($autoUnblocked.Count -gt 0) {         Save-BlockedBuckets $blockedBuckets blocat         Write-Log "Bucketuri deblocate automat (dispozitive actualizate): $($autoUnblocked.Count)" "OK"     }     # Pasul 5: Calcularea dispozitivelor eligibile rămase     $eligibleCount = 0     foreach ($device în $notUpdatedDevices) {         $bucketKey = Get-BucketKey $device         dacă (-nu $blockedBuckets.Contains($bucketKey)) {             $eligibleCount++         }     }     Write-Log "Dispozitive eligibile rămase: $eligibleCount" "INFORMAȚII"     Write-Log "Bucketuri blocate: $($blockedBuckets.Count)" "INFO"     # Pasul 6: Verificarea completării     dacă ($eligibleCount -eq 0) {         Write-Log "ROLLOUT COMPLETE - Toate dispozitivele eligibile au fost actualizate!" "OK"         $rolloutState.Status = "Terminat"         $rolloutState.CompletedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         $rolloutState stare Save-RolloutState         Pauză     }     # Pasul 6: Generați și implementați următorul val     Write-Log "Pasul 6: Generarea valului de lansare..." "INFORMAȚII"     $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames     # Verificați dacă avem dispozitive de implementat ($waveDevices ar putea fi $null, goale sau cu dispozitive reale)     $hasDevices = $waveDevices și @($waveDevices | Where-Object { $_ }). Count -gt 0     dacă ($hasDevices) {         # Numărul de incrementare al valului doar atunci când avem de fapt dispozitive de implementat         $rolloutState.CurrentWave++         Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Count) dispozitive" "WAVE"         # Implementarea GPO utilizând funcția schițată         $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $hostnames = @($waveDevices | ForEach-Object {             dacă ($_. Hostname) { $_. Hostname } elseif ($_. HostName) { $_. HostName } altceva { $null }         } | Where-Object { $_ })         # Salvați fișierul de nume de gazdă pentru referință/auditare         $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt"         $hostnames | Out-File $hostnamesFile -codificare UTF8         # Validați că avem nume de gazdă pentru implementare         dacă ($hostnames. Count -eq 0) {             Write-Log "Nu s-au găsit nume de gazdă valide în valul $($rolloutState.CurrentWave) - este posibil ca dispozitivelor să le lipsească proprietatea Hostname" "WARN"             Write-Log "Se ignoră implementarea pentru acest val - verificați datele dispozitivului" "AVERTIZARE"             Încă mai așteptați înainte de următoarea iterație             dacă (-nu $DryRun) {                 Write-Log "Se doarme timp de $PollIntervalMinutes minute înainte de a reîncerca..." "INFORMAȚII"                 Start-Sleep -secunde ($PollIntervalMinutes * 60)             }             Continua         }         Write-Log "Se implementează în $($hostnames. Count) hostnames in Wave $($rolloutState.CurrentWave)" "INFO"         # Implementarea utilizând metoda WinCS sau GPO bazată pe parametrul -UseWinCS         dacă ($UseWinCS) {             # Metoda WinCS: Crearea GPO cu activitate programată pentru a rula WinCsFlags.exe ca SISTEM pe fiecare punct final             Write-Log "Utilizarea metodei de implementare WinCS (cheie: $WinCSKey)" "WAVE"             $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames '                 -WinCSKey $WinCSKey '                 -WavePrefix $WavePrefix '                 -WaveNumber $rolloutState.CurrentWave '                 -TargetOU $TargetOU '                 -DryRun:$DryRun             dacă (-nu $wincsResult.Success) {                 Write-Log "Implementarea WinCS a avut erori - Aplicat: $($wincsResult.Applied), nu a reușit: $($wincsResult.Failed)" "WARN"             } altfel, {                 Write-Log "Implementarea WinCS a reușit - Aplicată: $($wincsResult.Applied), ignorată: $($wincsResult.omis)" "OK"             }             # Salvați rezultatele WinCS pentru audit             $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json"             $wincsResult | ConvertTo-Json -Adâncime 5 | Out-File $wincsResultFile - codificare UTF8         } altfel, {             Metoda GPO #: Creați GPO cu setarea de registry AvailableUpdatesPolicy             $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun             dacă (-nu $gpoResult) {                 Write-Log "Implementarea GPO nu a reușit - va reîncerca următoarea iterație" "EROARE"             }         }         # Record wave in state         $waveRecord = @{             WaveNumber = $rolloutState.CurrentWave             StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             DeviceCount = @($waveDevices). Conta             Dispozitive = @($waveDevices | ForEach-Object {                 @{                     Hostname = if ($_. Hostname) { $_. Hostname } elseif ($_. HostName) { $_. HostName } altceva { $null }                     BucketKey = Get-BucketKey $_                 }             })         }         # Asigurați-vă că WaveHistory este întotdeauna o matrice înainte de adăugare (previne problemele de îmbinare hashtable)         $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord)         $rolloutState.TotalDevicesTargeted += @($waveDevices). Conta         $rolloutState stare Save-RolloutState         Write-Log implementat "Wave $($rolloutState.CurrentWave).                                                                                                                                                                                        Se așteaptă minutele $PollIntervalMinutes..." "OK"     } altfel, {         # Afișați starea dispozitivelor implementate care așteaptă actualizări         Write-Log "" "INFO"         Write-Log "========== TOATE DISPOZITIVELE IMPLEMENTATE - SE AȘTEAPTĂ ========== DE STARE" "INFORMAȚII"                  # Obțineți toate dispozitivele implementate din istoricul valurilor         $allDeployedLookup = @{}         foreach ($wave în $rolloutState.WaveHistory) {             pentru a căuta ($device în $wave. Dispozitive) {                 dacă ($device. Hostname) {                     $allDeployedLookup[$device. Hostname] = @{                         Hostname = $device. Hostname                         BucketKey = $device. Cheie bucket                         ImplementatAt = $wave. StartedAt                         WaveNumber = $wave. Număr undă                     }                 }             }         }         $allDeployedDevices = @($allDeployedLookup.Values)                  dacă ($allDeployedDevices.Count -gt 0) {             # Aflați ce dispozitive implementate sunt încă în așteptare (în lista NotUpdated)             $stillPendingCount = 0             $noLongerPendingCount = 0             $pendingSample = @()             foreach ($deployed în $allDeployedDevices) {                 if ($notUpdatedIndexes.HostSet.Contains($deployed. Hostname)) {                     $stillPendingCount++                     dacă ($pendingSample.Count -lt $DeviceLogSampleSize) {                         $pendingSample += $deployed. Hostname                     }                 } altfel, {                     $noLongerPendingCount++                 }             }                          # Get actual Updated counts from aggregation - differentiate Event 1808 vs UEFICA2023Status             $summaryCsv = Get-ChildItem -cale $aggregationPath -Filtru "*Rezumat*.csv" |                  Sort-Object LastWriteTime -Descendent | Select-Object -Primul 1             $actualUpdated = 0             $totalDevicesFromSummary = 0             $event 1808Count = 0             $uefiStatusUpdated = 0             $needsRebootSample = @()                          dacă ($summaryCsv) {                 $summary = Import-Csv $summaryCsv.FullName | Select-Object -Primul 1                 dacă ($summary. Actualizat) { $actualUpdated = [int]$summary. Actualizat }                 dacă ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices }             }                          # Calculați viteza din istoricul valurilor (dispozitive actualizate pe zi)             $devicesPerDay = 0             dacă ($rolloutState.StartedAt -și $actualUpdated -gt 0) {                 $startDate = [dată-oră]::P arse($rolloutState.StartedAt)                 $daysElapsed = ((Get-Date) - $startDate). TotalDays                 dacă ($daysElapsed -gt 0) {                     $devicesPerDay = $actualUpdated/ $daysElapsed                 }             }                          # Salvați rezumatul lansării cu proiecții receptive la weekend             # Utilizați numărul notUptodate al agregatorului (exclude dispozitivele SB OFF) pentru consecvență             $notUpdatedCount = dacă ($summary și $summary. NotUptodate) { [int]$summary. NotUptodate } altceva { $totalDevicesFromSummary - $actualUpdated }             Save-RolloutSummary -stare $rolloutState '                 -TotalDevices $totalDevicesFromSummary '                 -UpdatedDevices $actualUpdated '                 -NotUpdatedDevices $notUpdatedCount '                 -DevicesPerDay $devicesPerDay                          # Verificați datele brute pentru dispozitivele cu UEFICA2023Status=Actualizat, dar nu există evenimentul 1808 (necesită repornire)             $dataFiles = Get-ChildItem -Cale $AggregationInputPath -Filtru "*.json" -ErrorAction SilentlyContinue             $totalDataFiles = @($dataFiles). Conta             $batchSize = [Matematică]:Max(500, $ProcessingBatchSize)             dacă ($LargeScaleMode) {                 $batchSize = [Matematică]:Max(2000, $ProcessingBatchSize)             }

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

                    foreach ($file in $batchFiles) {                         încercați {                             $deviceData = Get-Content $file. FullName -Raw | ConvertFrom-Json                             $hostname = $deviceData.Hostname                             dacă (-nu $hostname) { continue }                             $has 1808 = [int]$deviceData.Event1808Count -gt 0                             $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Actualizat"                             dacă ($has 1808) {                                 $event 1808Count++                             } elseif ($hasUefiUpdated) {                                 $uefiStatusUpdated++                                 dacă ($needsRebootSample.Count -lt $DeviceLogSampleSize) {                                     $needsRebootSample += $hostname                                 }                             }                         } captură { }                     }                                                          

                    Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{                         Event1808Count = $event 1808Count                         UEFIUpdatedAwaitingReboot = $uefiStatusUpdated                     }                 }             }             Write-Log "Total implementat: $($allDeployedDevices.Count)" "INFO"             Write-Log "Actualizat (eveniment 1808 confirmat): $event 1808Count" "OK"             dacă ($uefiStatusUpdated -gt 0) {                 Write-Log "Actualizat (UEFICA2023Status=Updated, se așteaptă repornirea): $uefiStatusUpdated" "OK"                 $rebootSuffix = dacă ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated - $DeviceLogSampleSize) mai mult)" } altceva { "" }                 Write-Log " Dispozitivele care necesită repornire pentru evenimentul 1808 (eșantion): $($needsRebootSample -join ', ')$rebootSuffix" "INFO"                 Write-Log "Aceste dispozitive vor raporta evenimentul 1808 după următoarea repornire" "INFO"             }             Write-Log "Nu mai este în așteptare: $noLongerPendingCount (include SecureBoot OFF, dispozitive lipsă)" "INFO"             Write-Log "Stare în așteptare: $stillPendingCount" "INFORMAȚII"             dacă ($stillPendingCount -gt 0) {                 $pendingSuffix = dacă ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount - $DeviceLogSampleSize) mai mult)" } altceva { "" }                 Write-Log "Dispozitive în așteptare (eșantion): $($pendingSample -join ', ')$pendingSuffix" "WARN"             }         } altfel, {             Write-Log "Nu s-au implementat încă dispozitive" "INFO"         }         Write-Log "================================================================" "INFO"         Write-Log "" "INFO"     }     # Așteptați înainte de următoarea iterație     dacă (-nu $DryRun) {         Write-Log "Dormi $PollIntervalMinutes minute..." "INFORMAȚII"         Start-Sleep -secunde ($PollIntervalMinutes * 60)     } altfel, {         Write-Log "[DRYRUN] Ar aștepta $PollIntervalMinutes minute" "INFO"         break # Ieșiți după o iterație în stare uscată     } }                               

# ============================================================================ # REZUMAT FINAL # ============================================================================

Write-Host "" Write-Host ("=" * 80) - Prim-planColor verde Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -Prim-planColor Verde Write-Host ("=" * 80) - Prim-planColor verde Write-Host ""

$finalState = Get-RolloutState $finalBlocked = Get-BlockedBuckets

Write-Host "Status:              $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Valuri totale: $($finalState.CurrentWave)" Write-Host "Dispozitive direcționate: $($finalState.TotalDevicesTargeted)" Write-Host "Bucketuri blocate: $($finalBlocked.Count)" -Prim-planColor $(dacă ($finalBlocked.Count -gt 0) { "Roșu" } altceva { "Verde" }) Write-Host "Dispozitive urmărite: $($deviceHistory.Count)" -Prim-planColor gri Write-Host ""

if ($finalBlocked.Count -gt 0) {     Write-Host "BUCKETURI BLOCATE (necesită revizuire manuală):" -Prim planColor Roșu     pentru a căuta ($key în $finalBlocked.Keys) {         $info = $finalBlocked[$key]         Write-Host " - $key" - Roșu prim plan         Write-Host " Motiv: $($info. Motiv)" -Prim-planColor gri     }     Write-Host ""     Write-Host "Fișier bucketuri blocate: $blockedBucketsPath" -Culoare prim plan galben }

Write-Host "" Write-Host "Fișiere de stare:" -Prim-planColor Cyan Write-Host " Stare implementare: $rolloutStatePath" Write-Host "Bucketuri blocate: $blockedBucketsPath" Write-Host " Istoricul dispozitivelor: $deviceHistoryPath" Write-Host ""  

​​​​​​​

Aveți nevoie de ajutor suplimentar?

Doriți mai multe opțiuni?

Explorați avantajele abonamentului, navigați prin cursurile de instruire, aflați cum să vă securizați dispozitivul și multe altele.