Nukopijuokite ir įklijuokite šį scenarijaus pavyzdį ir modifikuokite, kiek reikia jūsų aplinkai:
<# . ANOTACIJA Nepertraukiamas saugiosios įkrovos diegimo valdymo modulis, kuris paleidžiamas, kol diegimas bus baigtas.
.DESCRIPTION Šis scenarijus suteikia visapusį automatizavimą saugiosios įkrovos sertifikato išleidimą: 1. Generuoja diegimo bangas pagal agregavimo duomenis 2. Kiekvienai bangai sukuria AD grupes ir GPO. 3. Monitoriai, skirti įrenginio naujinimams (1808 įvykis) 4. Aptinka užblokuotas talpyklas (nepasiekiamus įrenginius) 5. Automatiškai progresuoja į paskesnę bangą 6. Veikia, kol atnaujinsITE VISUS reikalavimus atitinkančius įrenginius Baigimo kriterijai: - Nėra likusių įrenginių: Būtinas veiksmas, didelis pasitikėjimas, stebėjimas, laikinai pristabdyta - Nepatenka į aprėptį (pagal dizainą): nepalaikoma, saugi įkrova išjungta - Veikia nuolat, kol bus baigtas - be savavališko bangų limito Diegimo strategija: - DIDELIS PASITIKĖJIMAS: visi įrenginiai pirmos bangos (saugus) - BŪTINA IMTIS VEIKSMŲ: Progresinis dvigubas (1→2→4→8...) Blocking Logic: - Po MaxWaitHours, orkestras pings įrenginius, kurie nebuvo atnaujinti - Jei įrenginys yra NEPASIEKIAMAS (ryšio užklausa nepavyksta), → talpykla užblokuota tyrimui - Jei įrenginys yra REACHABLE, bet neatnaujintas → laukti (gali tekti perkrauti) - Užblokuotos talpyklos neįtraukiamos, kol administratorius juos atblokuoja Automatinis atblokavimas: - Jei vėliau užblokuotoje talpykloje esantis įrenginys rodomas kaip atnaujintas (1808 įvykis), segmentas automatiškai atblokuojamas ir vykdomas diegimas - Tai apdoroja įrenginius, kurie buvo laikinai neprisijungę, bet grįžo Įrenginio sekimas: - Seka įrenginius pagal pagrindinio kompiuterio vardą (daro prielaidą, kad pavadinimai diegimo metu nesikeičia) - Pastaba: JSON rinkinyje nėra unikalaus kompiuterio ID; įtraukite, kad sektumėte geriau
.PARAMETER AggregationInputPath Kelias į neapdorotus JSON įrenginio duomenis (nuo scenarijaus aptikimo)
.PARAMETER ReportBasePath Pagrindinis agregavimo ataskaitų kelias
.PARAMETER TargetOU Distinguished Name of the OU to link GPO.Pasirinktinai – jei nenurodyta, GPO yra susietas su domeno šaknimi, kad apimtų visą domeną.Saugos grupės filtravimas užtikrina, kad strategija būtų taikoma tik tiksliniams įrenginiams.
.PARAMETER MaxWaitHours Valandos laukti, kol įrenginiai bus atnaujinti prieš tikrinant pasiekiamumą.Po šio laiko įrenginiai, kurie nėra atnaujinti, pateikiami ryšio užklausas.Nepatikimi įrenginiai blokuoja talpyklą.Numatytoji reikšmė: 72 (3 dienos)
.PARAMETER PollIntervalMinutes Minučių skaičius tarp būsenos patikrų. Numatytoji reikšmė: 1 440 (1 diena)
.PARAMETER AllowListPath Failo, kuriame yra pagrindinių kompiuterių vardai, kelias į ALLOW, skirtą įdiegti (tikslinis diegimas).Palaiko .txt (vienas pagrindinio kompiuterio vardas vienoje eilutėje) arba .csv (su stulpeliu Hostname/ComputerName/Name).Kai nurodyta, tik šie įrenginiai bus įtraukti į diegimą.BlockList vis dar taikomas po AllowList.
.PARAMETER AllowADGroup AD saugos grupės, kurioje yra ALLOW kompiuterio paskyrų, pavadinimas.Pvz.: "SecureBoot-Pilot-Computers" arba "Wave1-Devices" Kai nurodyta, tik šios grupės įrenginiai bus įtraukti į diegimą.Suderinkite su "AllowListPath" ir failui, ir AD pagrįstam taikomam taikymui.
.PARAMETER ExclusionListPath Failo, kuriame yra pagrindinio kompiuterio vardai, kelias į EXCLUDE iš "Rollout" (VIP / vadovų įrenginiai).Palaiko .txt (vienas pagrindinio kompiuterio vardas vienoje eilutėje) arba .csv (su stulpeliu Hostname/ComputerName/Name).Šie įrenginiai niekada nebus įtraukti į jokias diegimo bangas.BlockList taikomas PO AllowList filtravimo. . PARAMETER ExcludeADGroup AD saugos grupės, kurioje yra kompiuterio paskyrų, kurių nenorite įtraukti, pavadinimas.Pvz.: VIP kompiuteriai arba vadovų įrenginiai Sujunkite su ExclusionListPath ir failams, ir AD pagrįstoms išimtims.
.PARAMETER UseWinCS Naudokite "WinCS" ("Windows" konfigūravimo sistemą), o ne GPO/AvailableUpdatesPolicy."WinCS" diegia saugiosios įkrovos įgalinimą paleisdama WinCsFlags.exe tiesiogiai kiekviename galiniame punkte.WinCsFlags.exe vykdomas sistemos kontekste naudojant suplanuotą užduotį.Šis metodas naudingas: - Greitesni išėmimai (tiesioginis efektas ir laukiama GPO apdorojimo) - Prie domeno neprijungti įrenginiai - Aplinkos be AD / GPO infrastruktūros Nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe
.PARAMETER WinCSKey "WinCS" raktas, naudojamas įgalinus saugiąją įkrovą.Numatytoji reikšmė: F33E0C8E002 Šis raktas atitinka saugiosios įkrovos diegimo konfigūraciją. . PARAMETER DryRun Rodyti, ką reikėtų daryti neatlikont pakeitimų
.PARAMETER ListBlockedBuckets Rodyti visas šiuo metu užblokuotas talpyklas ir išeiti
.PARAMETER UnblockBucket Atblokuokite konkretų talpyklą klavišu ir išeikite
.PARAMETER UnblockAll Atblokuokite visas talpyklas ir išeikite
.PARAMETER EnableTaskOnDisabled Visuotinai diegti Enable-SecureBootUpdateTask.ps1 visuose įrenginiuose, kuriuose išjungta suplanuota užduotis.Sukuria GPO su vienkartine suplanuota užduotimi, kuri paleidžia parinktį Įgalinti scenarijų su -Quiet.Tai naudinga norint pataisyti įrenginius, kuriuose išjungta užduotis Saugiosios įkrovos naujinimas.
.EXAMPLE .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath E:\SecureBootReports ' -TargetOU "OU=Workstations,DC=contoso,DC=com"
.EXAMPLE # Sąrašas užblokuotų talpyklų .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets
.EXAMPLE # Atblokuoti konkretų segmentą .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"
.EXAMPLE # Neįtraukti VIP įrenginių iš naujo naudojant teksto failą .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath E:\SecureBootReports ' -ExclusionListPath C:\Admin\VIP-Devices.txt
.EXAMPLE # Neįtraukti įrenginių AD saugos grupėje (pvz., vadovų nešiojamieji kompiuteriai) .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath E:\SecureBootReports ' -ExcludeADGroup VIP kompiuteriai
.EXAMPLE # Naudokite "WinCS" ("Windows" konfigūravimo sistemą), o ne GPO/AvailableUpdatesPolicy # WinCsFlags.exe veikia sistemos kontekste kiekviename pabaigos taško per suplanuotą užduotį .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath E:\SecureBootReports ' -UseWinCS ' -WinCSKey "F33E0C8E002" #>
[CmdletBinding()] param( [Parametras(Privalomas = $false)] [eilutė]$AggregationInputPath, [Parametras(Privalomas = $false)] [eilutė]$ReportBasePath, [Parametras(Privalomas = $false)] [eilutė]$TargetOU, [Parametras(Privalomas = $false)] [eilutė]$WavePrefix = "SecureBoot-Rollout", [Parametras(Privalomas = $false)] [sveikasis skaičius]$MaxWaitHours = 72, [Parametras(Privalomas = $false)] [sveikasis skaičius]$PollIntervalMinutes = 1440,
[Parameter(Mandatory = $false)] [sveikasis skaičius]$ProcessingBatchSize = 5000,
[Parameter(Mandatory = $false)] [sveikasis skaičius]$DeviceLogSampleSize = 25,
[Parameter(Mandatory = $false)] [jungiklis]$LargeScaleMode, #============================================================================ # AllowList / BlockList parametrai # ============================================================================ # AllowList = įtraukti tik šiuos įrenginius (tikslinis diegimas) # BlockList = neįtraukti šių įrenginių (jie niekada nebus pateikti) # Apdorojimo tvarka: AllowList pirmiausia (jei nurodyta), tada BlockList [Parametras(Privalomas = $false)] [eilutė]$AllowListPath, [Parametras(Privalomas = $false)] [eilutė]$AllowADGroup, [Parametras(Privalomas = $false)] [eilutė]$ExclusionListPath, [Parametras(Privalomas = $false)] [eilutė]$ExcludeADGroup, #============================================================================ # WinCS ("Windows" konfigūravimo sistemos) parametrai #============================================================================ # WinCS yra "AvailableUpdatesPolicy" GPO diegimo alternatyva. # Jis naudoja WinCsFlags.exe kiekviename galiniame punkte, kad įgalintų saugiosios įkrovos diegimą.# WinCsFlags.exe veikia galinio punkto SISTEMOS kontekste.Nr. nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe [Parametras(Privalomas = $false)] [jungiklis]$UseWinCS, [Parametras(Privalomas = $false)] [eilutė]$WinCSKey = "F33E0C8E002", [Parametras(Privalomas = $false)] [jungiklis]$DryRun, [Parametras(Privalomas = $false)] [jungiklis]$ListBlockedBuckets, [Parametras(Privalomas = $false)] [eilutė]$UnblockBucket, [Parametras(Privalomas = $false)] [jungiklis]$UnblockAll, [Parametras(Privalomas = $false)] [jungiklis]$EnableTaskOnDisabled )
$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Diegimo ir stebėjimo pavyzdžiai"
# ============================================================================ # PRIKLAUSOMYBĖS TIKRINIMAS # ============================================================================
function Test-ScriptDependencies { param( [Parametras(Privalomas = $true)] [eilutė]$ScriptDirectory, [Parametras(Privalomas = $true)] [eilutė[]$RequiredScripts ) $missingScripts = @() foreach ($script in $RequiredScripts) { $scriptPath = Join-Path $ScriptDirectory $script jei (-not (testo kelias $scriptPath)) { $missingScripts += $script } } jei ($missingScripts.Count -gt 0) { Write-Host "" Write-Host (=" * 70) - priekinio planopalva raudona Write-Host " MISSING DEPENDENCIES" -ForegroundColor Red Write-Host (=" * 70) - priekinio planopalva raudona Write-Host " Write-Host "Nerasta šių būtinų scenarijų:" -Priekinio planopalva Geltona foreach ($script in $missingScripts) { Write-Host " - $script" - Priekinio planopalva balta } Write-Host Write-Host "Atsisiųskite naujausius scenarijus iš:" -ForegroundColor Cyan Write-Host " URL: $DownloadUrl" - Priekinio planopalva balta Write-Host " Pereiti į: "$DownloadSubPage" - priekinio planopalva balta Write-Host Write-Host "Išskleisti visus scenarijus į tą patį katalogą ir paleisti iš naujo." -Priekinio planopalva Geltona Write-Host grąžinimo $false } grąžinimo $true }
# Required scripts for orchestrator $requiredScripts = @( "Aggregate-SecureBootData.ps1", "Enable-SecureBootUpdateTask.ps1", "Deploy-GPO-SecureBootCollection.ps1", "Detect-SecureBootCertUpdateStatus.ps1" )
if (-not (Test-ScriptDependencies -ScriptDirectory $PSScriptRoot -RequiredScripts $requiredScripts)) { išeiti iš 1 }
# ============================================================================ # PARAMETRO TIKRINIMAS # ============================================================================
# Admin commands only need ReportBasePath $isAdminCommand = $ListBlockedBuckets arba $UnblockBucket arba $UnblockAll arba $EnableTaskOnDisabled
if (-not $ReportBasePath) { Write-Host "KLAIDA: Reikia "-ReportBasePath". -Priekinio planopalva raudona išeiti iš 1 }
if (-not $isAdminCommand -and -not $AggregationInputPath) { Write-Host "KLAIDA: -AggregationInputPath būtinas diegiant (nereikia -ListBlockedBuckets, -UnblockBucket, -UnblockAll)" -ForegroundColor Red išeiti iš 1 }
# ============================================================================ # GPO DETECTION - CHECK FOR DETECTION GPO # ============================================================================
if (-not $isAdminCommand -and -not $DryRun) { $CollectionGPOName = "SecureBoot-EventCollection" # Patikrinkite, ar yra "GroupPolicy" modulis jei (Get-Module -ListAvailable -Name GroupPolicy) { Import-Module GroupPolicy - ErrorAction SilentlyContinue Write-Host "Tikrinama, ar yra aptikimo GPO..." - Geltona priekinio plano spalva išbandykite { # Patikrinkite, ar GPO yra $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue jei ($existingGpo) { Write-Host " Rasta aptikimo GPO: $CollectionGPOName" -Priekinio planopalva žalia } dar { Write-Host Write-Host ("=" * 70) - priekinio planopalva geltona Write-Host " ĮSPĖJIMAS: APTIKIMO GPO NERASTAS" - priekinio planopalva geltona Write-Host ("=" * 70) – geltona priekinio plano spalva Write-Host Write-Host "Aptikimo GPO "$CollectionGPOName" nerastas." -Priekinio planopalva geltona Write-Host "Be šio GPO nebus renkami jokie įrenginio duomenys." -Priekinio planopalva geltona Write-Host " Write-Host "To deploy the Detection GPO, run:" -ForegroundColor Cyan Write-Host ".\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domain> -AutoDetectOU" -ForegroundColor White Write-Host Write-Host "Vis tiek tęsti? (Y/N)" - priekinio planopalva geltona $response = skaitymo pagrindinis kompiuteris if ($response -notmatch '^[Yy]') { Write-Host "Nutraukiama. Pirmiausia įdiekite aptikimo GPO." -ForegroundColor Red išeiti iš 1 } } } sugauti { Write-Host " Nepavyksta patikrinti GPO: $($_. Exception.Message)" -ForegroundColor Yellow } } dar { Write-Host " GroupPolicy modulis nepasiekiamas – GPO patikros praleidimas" -Priekinio planopalva pilka } Write-Host }
# ============================================================================ # BŪSENOS FAILŲ KELIAI # ============================================================================
$stateDir = Join-Path $ReportBasePath "RolloutState" jei (-not (testo kelias $stateDir)) { New-Item -ItemType katalogas -kelias $stateDir -Force | Nu neapibrėžta reikšmė (Out-Null) }
$rolloutStatePath = Join-Path $stateDir "RolloutState.json" $blockedBucketsPath = Join-Path $stateDir "BlockedBuckets.json" $adminApprovedPath = Join-Path $stateDir "AdminApprovedBuckets.json" $deviceHistoryPath = Join-Path $stateDir "DeviceHistory.json" $processingCheckpointPath = Join-Path $stateDir "ProcessingCheckpoint.json"
# ============================================================================ # PS 5.1 SUDERINAMUMAS: ConvertTo-Maišos lentelė #============================================================================ # ConvertFrom-Json -AsHashtable yra tik PS7+. Tai suteikia suderinamumą.
function ConvertTo-Hashtable { param( [Parameter(ValueFromPipeline = $true)] $InputObject ) procesas { jei ($null -eq $InputObject) { grąžinti @{} } jei ($InputObject -is [System.Collections.IDictionary]) { return $InputObject } jei ($InputObject - yra [PSCustomObject]) { # Naudokite [užsakyta] nuosekliam raktų užsakymui ir saugiam pasikartojančiam apdorojimui $hash = [užsakyta]@{} foreach ($prop in $InputObject.PSObject.Properties) { # Indexed assignment safely handles duplicates by overwriting $hash[$prop. Pavadinimas] = ConvertTo-Hashtable $prop. Vertė } grąžinimo $hash } jei ($InputObject - yra [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { grįžti @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ }) } grąžinimo $InputObject } }
# ============================================================================ # ADMINISTRAVIMO KOMANDOS: Sąrašas / atblokuoti talpyklas # ============================================================================
if ($ListBlockedBuckets) { Write-Host " Write-Host ("=" * 80) – geltona priekinio plano spalva Write-Host " BLOCKED BUCKETS" -Priekinio planopalva Geltona Write-Host ("=" * 80) – priekinio planopalva geltona Write-Host if (Test-Path $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę jei ($blocked. Skaičius -eq 0) { Write-Host "Nėra užblokuotų talpyklų". -Priekinio planopalva Žalia } dar { Write-Host "Total blocked: $($blocked. Count)" -Priekinio planopalva raudona Write-Host ($key $blocked. Klavišai) { $info = $blocked[$key] Write-Host "Bucket: $key" -Priekinio planopalva raudona Write-Host " užblokuota: $($info. BlockedAt)" -Priekinio planopalva pilka Write-Host " Priežastis: $($info. Reason)" -Priekinio planopalva pilka Write-Host " Nepavykęs įrenginys: $($info. FailedDevice)" -Priekinio planopalva pilka Write-Host " Paskutinį kartą pranešta: $($info. LastReported)" -Priekinio planopalva pilka Write-Host " Banga: $($info. WaveNumber)" -Priekinio planopalva pilka Write-Host " Įrenginiai talpykloje: $($info. DevicesInBucket)" -ForegroundColor Gray Write-Host } Write-Host "Norėdami atblokuoti talpyklą:" Write-Host .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY' -ForegroundColor Cyan Write-Host Write-Host "Norėdami atblokuoti viską:" Write-Host ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockAll" -ForegroundColor Cyan } } dar { Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Žalia } Write-Host išeiti iš 0 }
if ($UnblockBucket) { Write-Host if (Test-Path $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Neapdorota | ConvertFrom-Json | Konvertuoti į maišos lentelę jei ($blocked. Yra($UnblockBucket)) { $blocked. Šalinti($UnblockBucket) $blocked | ConvertTo-Json gylis 10 | Out-File $blockedBucketsPath - Kodavimas UTF8 -Force # Įtraukti į administratoriaus patvirtintą sąrašą, kad nebūtų blokuojamas iš naujo $adminApproved = @{} if (Test-Path $adminApprovedPath) { $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę } $adminApproved[$UnblockBucket] = @{ ApprovedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" ApprovedBy = $env:USERNAME } $adminApproved | ConvertTo-Json gylis 10 | Out-File $adminApprovedPath - Kodavimas UTF8 -Force Write-Host "Atblokuota talpykla: $UnblockBucket" – priekinio planopalva žalia Write-Host "Įtraukta į administratoriaus patvirtintą sąrašą (nebus automatiškai blokuojama iš naujo)" -ForegroundColor Cyan } dar { Write-Host "Talpykla nerasta: $UnblockBucket" - Priekinio planopalva Geltona Write-Host "Available buckets:" -ForegroundColor Gray $blocked. Raktai | ForEach-Object { Write-Host " $_" -Priekinio planopalva Pilka } } } dar { Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Geltona } Write-Host išeiti iš 0 }
if ($UnblockAll) { Write-Host if (Test-Path $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Neapdorota | ConvertFrom-Json | Konvertuoti į maišos lentelę $count = $blocked. Skaičius @{} | ConvertTo-Json | Out-File $blockedBucketsPath - Kodavimas UTF8 -Force Write-Host "Atblokuota visos $count talpyklos". - Priekinio planocolor žalia } dar { Write-Host "Nerasta blokuojamų talpyklų failų". -Priekinio planopalva Geltona } Write-Host išeiti iš 0 }
# ============================================================================ # PAGALBININKAS FUNKCIJOS # ============================================================================
function Get-RolloutState { if (Test-Path $rolloutStatePath) { išbandykite { $loaded = Get-Content $rolloutStatePath -Žalias | ConvertFrom-Json | Konvertuoti į maišos lentelę # Patikrinti būtinas ypatybes yra jeigu ($null -eq $loaded. CurrentWave) { throw "Neleistinas būsenos failas – trūksta CurrentWave" } # Įsitikinkite, kad WaveHistory visada yra masyvas (išsprendžia PS5.1 JSON išdėstymo eilutėmis panaikinimas) jeigu ($null -eq $loaded. WaveHistory) { $loaded. WaveHistory = @() } elseif ($loaded. WaveHistory -isnot [masyvas]) { $loaded. WaveHistory = @($loaded. "WaveHistory") } grąžinimo $loaded } sugauti { Write-Log "Aptikta sugadintų RolloutState.json: $($_. Exception.Message)" "WARN" Write-Log "Atsarginės sugadinto failo kopijos kūrimas ir naujo paleidimo" WARN $backupPath = "$rolloutStatePath.corrupted.$(Get-Date -Format 'yyyyMMdd-HHmmss')" Move-Item $rolloutStatePath $backupPath –Force - ErrorAction SilentlyContinue } } grąžinti @{ CurrentWave = 0 StartedAt = $null LastAggregation = $null TotalDevicesTargeted = 0 TotalDevicesUpdated = 0 Būsena = "NotStarted" WaveHistory = @() } }
function Save-RolloutState { param($State) $State | ConvertTo-Json gylis 10 | Out-File $rolloutStatePath - Kodavimas UTF8 -Force }
function Get-WeekdayProjection { <# . ANOTACIJA Apskaičiuokite numatomas baigimo datas savaitgaliais (nėra eigos š/s) #> param( [sveikasis skaičius]$RemainingDevices, [double]$DevicesPerDay, [datetime]$StartDate = (Get-Date) ) jei ($DevicesPerDay -le 0 arba $RemainingDevices -le 0) { grąžinti @{ ProjectedDate = $null WorkingDaysNeeded = 0 CalendarDaysNeeded = 0 } } # Apskaičiuokite reikiamas darbo dienas (išskyrus savaitgalius) $workingDaysNeeded = [matematika]::Ceiling($RemainingDevices / $DevicesPerDay) # Darbo dienų konvertavimas į kalendorines dienas (įtraukti savaitgalius) $currentDate = $StartDate.Date $daysAdded = 0 $workingDaysAdded = 0 o ($workingDaysAdded -lt $workingDaysNeeded) { $currentDate = $currentDate.AddDays(1) $daysAdded++ # Skaičiuoti tik darbo dienas jei ($currentDate.DayOfWeek -ne [DayOfWeek]::Šeštadienis -ir $currentDate.DayOfWeek -ne [DayOfWeek]::Sunday) { $workingDaysAdded++ } } grąžinti @{ ProjectedDate = $currentDate.ToString("yyyy-MM-dd") WorkingDaysNeeded = $workingDaysNeeded CalendarDaysNeeded = $daysAdded } }
function Save-RolloutSummary { <# . ANOTACIJA Įrašyti diegimo suvestinę su ataskaitų srities rodymo projekcijos informacija #> param( [maišos lentelė]$State, [sveikasis skaičius]$TotalDevices, [sveikasis skaičius]$UpdatedDevices, [sveikasis skaičius]$NotUpdatedDevices, [dvigubas]$DevicesPerDay ) $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json" # Apskaičiuokite savaitgalio projekciją $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay $summary = @{ GeneratedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss") RolloutStartDate = $State.StartedAt LastAggregation = $State.LastAggregation CurrentWave = $State.CurrentWave Būsena = $State.Status # Įrenginių skaičius TotalDevices = $TotalDevices UpdatedDevices = $UpdatedDevices NotUpdatedDevices = $NotUpdatedDevices PercentUpdated = jei ($TotalDevices -gt 0) { [matematika]::Round(($UpdatedDevices / $TotalDevices) * 100, 1) } dar { 0 } # greičio metrika DevicesPerDay = [matematika]::Round($DevicesPerDay, 1) TotalDevicesTargeted = $State.TotalDevicesTargeted TotalWaves = $State.CurrentWave # Savaitgalio projekcija ProjectedCompletionDate = $projection. Projekto data WorkingDaysRemaining = $projection. WorkingDaysNeeded CalendarDaysRemaining = $projection. CalendarDaysNeeded # Pastaba apie savaitgalio išimtį ProjectionNote = "Projected completion excludes weekends (Sat/Sun)" } $summary | ConvertTo-Json gylis 5 | Out-File $summaryPath - Kodavimas UTF8 -Force Write-Log "Rollout summary saved: $summaryPath" "INFO" grąžinimo $summary }
function Get-BlockedBuckets { if (Test-Path $blockedBucketsPath) { return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę } grąžinti @{} }
function Save-BlockedBuckets { param($Blocked) $Blocked | ConvertTo-Json gylis 10 | Out-File $blockedBucketsPath - Kodavimas UTF8 -Force }
function Get-AdminApproved { if (Test-Path $adminApprovedPath) { return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę } grąžinti @{} }
function Get-DeviceHistory { if (Test-Path $deviceHistoryPath) { return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | Konvertuoti į maišos lentelę } grąžinti @{} }
function Save-DeviceHistory { param($History) $History | ConvertTo-Json gylis 10 | Out-File $deviceHistoryPath - Kodavimas UTF8 -Force }
function Save-ProcessingCheckpoint { param( [eilutė]$Stage, [sveikasis skaičius]$Processed, [sveikasis skaičius]$Total, [maišos lentelė]$Metrics = @{} )
$checkpoint = @{ Etapas = $Stage UpdatedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" Apdorota = $Processed Iš viso = $Total Procentas = jei ($Total -gt 0) { [matematika]::Round(($Processed / $Total) * 100, 2) } dar { 0 } Metrika = $Metrics }
$checkpoint | ConvertTo-Json -Depth 6 | Out-File $processingCheckpointPath -Encoding UTF8 -Force }
function Get-NotUpdatedIndexes { param([masyvas]$Devices)
$hostSet = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $bucketCounts = @{}
foreach ($device in $Devices) { $hostname = jei ($device. Pagrindinio kompiuterio vardas) { $device. Hostname } elseif ($device. HostName) { $device. HostName } dar { $null } jei ($hostname) { [negalioja]$hostSet.Add($hostname) }
$bucketKey = Get-BucketKey $device jei ($bucketKey) { jei ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey]++ } dar { $bucketCounts[$bucketKey] = 1 } } }
return @{ HostSet = $hostSet BucketCounts = $bucketCounts } }
function Write-Log { param([eilutė]$Message, [eilutė]$Level = "INFO") $timestamp = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" $color = jungiklis ($Level) { "OK" { "Green" } "WARN" { "Yellow" } "ERROR" { "Red" } "BLOCKED" { "DarkRed" } "WAVE" { "Cyan" } numatytoji reikšmė { "Baltas" } } Write-Host $color "[$timestamp] [$Level] $Message" # Taip pat įeiti į failą $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMMdd').log" "[$timestamp] [$Level] $Message" | Out-File $logFile – pridėti - kodavimas UTF8 }
function Get-BucketKey { param($Device) # Naudokite BucketId iš įrenginio JSON, jei yra (SHA256 maiša iš aptikimo scenarijaus) if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { return "$($Device.BucketId)" } # Atsarginė kopija: gamintojo konstrukcija|modelis|bios $mfr = jei ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } dar { $Device.Manufacturer } $model = jei ($Device.WMI_Model) { $Device.WMI_Model } dar { $Device.Model } $bios = jei ($Device.BIOSDescription) { $Device.BIOSDescription } dar { $Device.BIOS } pateikti $mfr|$model|$bios" }
# ============================================================================ # VIP / IŠIMČIŲ SĄRAŠO ĮKĖLIMAS # ============================================================================
function Get-ExcludedHostnames { param( [eilutė]$ExclusionFilePath, [eilutė]$ADGroupName ) $excluded = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) # Įkelti iš failo (palaiko .txt arba .csv) jei ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) { $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower() jei ($extension -eq ".csv") { # CSV formatas: tikisi stulpelio "Hostname" arba "ComputerName" $csvData = Import-Csv $ExclusionFilePath $hostCol = jei ($csvData[0]. PSObject.Properties.Name - yra "Hostname") { "Hostname" } elseif ($csvData[0]. PSObject.Properties.Name – yra "ComputerName") { "ComputerName" } elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' } else { $null } jei ($hostCol) { foreach ($row in $csvData) { jei (![ eilutė]::IsNullOrWhiteSpace($row.$hostCol)) { [negalioja]$excluded. Add($row.$hostCol.Trim()) } } } } dar { # Paprastasis tekstas: vienas pagrindinio kompiuterio vardas vienoje eilutėje Get-Content $ExclusionFilePath | ForEach-Object { $line = $_. Trim() jei ($line -and -not $line. StartsWith('#')) { [negalioja]$excluded. Add($line) } } } Write-Log "Įkelta $($excluded. Skaičius) pagrindinio kompiuterio vardai iš išimčių failo: $ExclusionFilePath" "INFO" } # Įkelti iš AD saugos grupės jei ($ADGroupName) { išbandykite { $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop | Where-Object { $_.objectClass -eq 'computer' } foreach ($member in $groupMembers) { [negalioja]$excluded. Add($member. Vardas ir pavardė) } Write-Log "Įkelti $($groupMembers.Count) kompiuteriai iš AD grupės: $ADGroupName" "INFO" } sugauti { Write-Log "Nepavyko įkelti AD grupės "$ADGroupName": $_" "WARN" } } grįžti @($excluded) }
# ============================================================================ # ALLOW SĄRAŠO ĮKĖLIMAS (tikslinis diegimas) # ============================================================================
function Get-AllowedHostnames { <# . ANOTACIJA Įkelia pagrindinio kompiuterio vardus iš AllowList failo ir (arba) AD grupės tiksliniam išleidimą.Kai nurodytas AllowList, TIK šie įrenginiai bus įtraukti į diegimą.#> param( [eilutė]$AllowFilePath, [eilutė]$ADGroupName ) $allowed = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) # Įkelti iš failo (palaiko .txt arba .csv) jei ($AllowFilePath -and (Test-Path $AllowFilePath)) { $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower() jei ($extension -eq ".csv") { # CSV formatas: tikisi stulpelio "Hostname" arba "ComputerName" $csvData = Import-Csv $AllowFilePath jei ($csvData.Count -gt 0) { $hostCol = jei ($csvData[0]. PSObject.Properties.Name -contains "Hostname") { "Hostname" } elseif ($csvData[0]. PSObject.Properties.Name – yra "ComputerName") { "ComputerName" } elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' } else { $null } jei ($hostCol) { foreach ($row in $csvData) { jei (![ eilutė]::IsNullOrWhiteSpace($row.$hostCol)) { [negalioja]$allowed. Add($row.$hostCol.Trim()) } } } } } dar { # Paprastasis tekstas: vienas pagrindinio kompiuterio vardas vienoje eilutėje Get-Content $AllowFilePath | ForEach-Object { $line = $_. Trim() jei ($line -and -not $line. StartsWith('#')) { [negalioja]$allowed. Add($line) } } } Write-Log "Įkelta $($allowed. Skaičius) pagrindinių kompiuterių vardai iš leidžiamų sąrašo failų: $AllowFilePath" "INFO" } # Įkelti iš AD saugos grupės jei ($ADGroupName) { išbandykite { $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop | Where-Object { $_.objectClass -eq 'computer' } foreach ($member in $groupMembers) { [negalioja]$allowed. Add($member. Vardas ir pavardė) } Write-Log "Įkelti $($groupMembers.Count) kompiuteriai iš AD allow grupės: $ADGroupName" "INFO" } sugauti { Write-Log "Nepavyko įkelti AD grupės "$ADGroupName": $_" "WARN" } } grįžti @($allowed) }
# ============================================================================ # DUOMENŲ NAUJUMAS IR STEBĖJIMAS # ============================================================================
function Get-DataFreshness { <# . ANOTACIJA Tikrina, ar aptikimo duomenys yra nauji, patikrindama JSON failo laiko žymas.Grąžina statistiką apie tai, kada paskutinį kartą buvo pateikti pabaigos taškai.#> param([eilutė]$JsonPath) $jsonFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue jei ($jsonFiles.Count -eq 0) { grąžinti @{ TotalFiles = 0 FreshFiles = 0 StaleFiles = 0 NoDataFiles = 0 OldestFile = $null NewestFile = $null AvgAgeHours = 0 Įspėjimas = "Nerasta JSON failų – aptikimas gali būti neįdiegtas" } } $now = Gavimo data $freshThresholdHours = 24 # Files atnaujinta per paskutines 24 valandas yra "fresh" $staleThresholdHours = 72 # Files senesni nei 72 valandos yra "pasenusi" $fresh = 0 $stale = 0 $ages = @() foreach ($file in $jsonFiles) { $ageHours = ($now – $file. "LastWriteTime"). Bendra valandų suma $ages += $ageHours jei ($ageHours -le $freshThresholdHours) { $fresh++ } elseif ($ageHours -ge $staleThresholdHours) { $stale++ } } $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object – pirmas 1 $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 $warning = $null jei ($stale -gt ($jsonFiles.Count * 0.5)) { $warning = "Daugiau nei 50 % įrenginių turi pasenusius duomenis (>72 val.) - patikrinti aptikimo GPO" } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) { $warning = "Mažiau nei 30 % neseniai praneštų įrenginių – aptikimas gali neveikti" } grąžinti @{ TotalFiles = $jsonFiles.Count FreshFiles = $fresh StaleFiles = $stale MediumFiles = $jsonFiles.Count - $fresh - $stale OldestFile = $oldestFile.LastWriteTime NewestFile = $newestFile.LastWriteTime AvgAgeHours = [matematika]:Round(($ages | Measure-Object -Average). Vidurkis, 1) Įspėjimas = $warning } }
function Test-DetectionGPODeployed { <# . ANOTACIJA patikrina, ar veikia aptikimo ir (arba) stebėjimo infrastruktūra.#> param([eilutė]$JsonPath) # 1 patikrinimas: JSON kelias yra jei (-not (testo kelias $JsonPath)) { grąžinti @{ IsDeployed = $false Message = "JSON įvesties kelio nėra: $JsonPath" } } # 2 patikrinimas: yra bent kai kurie JSON failai $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Skaičius jei ($jsonCount -eq 0) { grąžinti @{ IsDeployed = $false Message = "No JSON files in $JsonPath - Detection GPO may not be deployed or devices haven't reported yet" } } # Patikrinkite 3: Files yra gana neseniai (bent jau praėjusią savaitę) $recentFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue | Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) } jei ($recentFiles.Count -eq 0) { grąžinti @{ IsDeployed = $false Message = "No JSON files updated in last 7 days - Detection GPO may be broken or devices offline" } } grąžinti @{ IsDeployed = $true Message = "Detection appears active: $jsonCount files, $($recentFiles.Count) updated recently" FileCount = $jsonCount RecentCount = $recentFiles.Count } }
# ============================================================================ # ĮRENGINIO SEKIMAS (PAGAL PAGRINDINIO KOMPIUTERIO VARDĄ) # ============================================================================
function Update-DeviceHistory { <# . ANOTACIJA Seka įrenginius pagal pagrindinio kompiuterio vardą, nes neturime unikalaus kompiuterio identifikatoriaus.Pastaba: BucketId yra "vienas su daugeliu" (ta pati aparatūros konfigūracija = ta pati talpykla).Jei Į JSON rinkinį įtraukiamas unikalus identifikatorius, atnaujinkite šią funkciją.#> param( [masyvas]$CurrentDevices, [maišos lentelė]$DeviceHistory ) foreach ($device in $CurrentDevices) { $hostname = $device. Hostname jei (-not $hostname) { continue } # Sekti įrenginį pagal pagrindinio kompiuterio vardą $DeviceHistory[$hostname] = @{ Pagrindinio kompiuterio vardas = $hostname BucketId = $device. "BucketId" Gamintojas = $device. WMI_Manufacturer Modelis = $device. WMI_Model LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Būsena = $device. UpdateStatus } } }
# ============================================================================ # UŽBLOKUOTAS TALPYKLOS APTIKIMAS (atsižvelgiant į įrenginio pasiekiamumą) # ============================================================================
<# . APRAŠYMAS / KONTROLĖ Blocking Logic: - Talpykla blokuojama, TIK jei: 1. Įrenginys buvo nukreiptas į bangą 2. MaxWaitHours praėjo nuo bangos pradžios 3. Įrenginys NEPASIEKIAMAS (ryšio užklausa nepavyksta) - Jei įrenginys pasiekiamas, bet dar neatnaujintas, vis dar laukiame (Naujinimas gali būti laukia paleidimo iš naujo – įvykis 1808 sužadinama tik paleidus iš naujo) - Nepasiekiamas įrenginys rodo, kad kažkas nutiko ir reikia atlikti tyrimą Atblokavimo: - Naudokite -ListBlockedBuckets norėdami pamatyti užblokuotas talpyklas - Norėdami atblokuoti konkretų segmentą, naudokite -UnblockBucket "BucketKey" - Naudokite -UnblockAll, kad atblokuotumėte visus talpyklas #>
function Test-DeviceReachable { param( [eilutė]$Hostname, [eilutė]$DataPath # Kelias į įrenginio JSON failus ) # 1 metodas: Patikrinkite JSON failo laiko žymą (greičiausia – nereikia analizuoti failo) # Jei aptikimo scenarijus vyko neseniai, failas buvo parašytas / atnaujintas, įrodant, kad įrenginys yra gyvas jei ($DataPath) { $deviceFile = Get-ChildItem -Path $DataPath -Filter "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object – pirmas 1 jei ($deviceFile) { $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). Bendra valandų suma jei ($hoursSinceWrite -lt 72) { grąžinti $true } } } # 2 metodas: Atsarginė ryšio užklausa (tik jei JSON yra pasenusi arba jos nėra) išbandykite { $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue grąžinimo $ping } sugauti { grąžinimo $false } }
function Update-BlockedBuckets { param( $RolloutState, $BlockedBuckets, $AdminApproved, [masyvas]$NotUpdatedDevices, [maišos lentelė]$NotUpdatedIndexes, [sveikasis skaičius]$MaxWaitHours, [Bulio logika]$DryRun = $false ) $now = Gavimo data $newlyBlocked = @() $stillWaiting = @() $devicesToCheck = @() $hostSet = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet } $bucketCounts = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts} dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). BucketCounts } # Rinkti įrenginius, kurie yra praėjusio laukimo laikotarpio ir vis dar neatnaujinti foreach ($wave in $RolloutState.WaveHistory) { jei (ne $wave. StartedAt) { continue } $waveStart = [DateTime]::P arse($wave. Darbo pradžia) $hoursSinceWave = ($now – $waveStart). Bendra valandų suma jei ($hoursSinceWave -lt $MaxWaitHours) { # Vis dar laukimo laikotarpiu – dar netikrinę Toliau } # Patikrinkite kiekvieną įrenginį iš šios bangos ($deviceInfo programoje "$wave". Įrenginiai) { $hostname = $deviceInfo.Hostname $bucketKey = $deviceInfo.BucketKey # Praleisti, jei talpykla jau užblokuota jei ($BlockedBuckets.Contains($bucketKey)) { continue } # Praleisti, jei talpykla yra administratoriaus patvirtinta IR banga pradėta PRIEŠ patvirtinimą # (patikrinkite tik tuos įrenginius, kuriems taikomas po administratoriaus patvirtinimo dėl pakartotinio blokavimo) jei ($AdminApproved ir $AdminApproved.Contains($bucketKey)) { $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. PatvirtintaAt) jei ($waveStart -lt $approvalTime) { # Šis įrenginys buvo nukreiptas prieš administratoriaus patvirtinimą – praleisti Toliau } # Banga pradėta po patvirtinimo – tai yra naujas taikymas, galite patikrinti } # Ar šis įrenginys vis dar yra NotUpdated sąraše? jei ($hostSet.Contains($hostname)) { $devicesToCheck += @{ Hostname = $hostname BucketKey = $bucketKey WaveNumber = $wave. "WaveNumber" HoursSinceWave = [math]::Round($hoursSinceWave, 1) } } } } jei ($devicesToCheck.Count -eq 0) { grąžinimo $newlyBlocked } Write-Log "$($devicesToCheck.Count) įrenginių pasiekiamumo tikrinimas praėjusio laukimo laikotarpio..." "INFO" # Track failures per bucket for decision-making $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Gyvas = @() } # Patikrinkite kiekvieno įrenginio pasiekiamumą foreach ($device in $devicesToCheck) { $hostname = $device. Hostname $bucketKey = $device. "BucketKey" jei ($DryRun) { Write-Log "[DRYRUN] Patikrins $hostname pasiekiamumą" "INFO" Toliau } jei (-not $bucketFailures.ContainsKey($bucketKey)) { $bucketFailures[$bucketKey] = @{ Nepasiekiamas = @(); AliveButFailed = @(); WaveNumber = $device. "WaveNumber"; HoursSinceWave = $device. HoursSinceWave } } $isReachable = Test-DeviceReachable -Hostname $hostname -DataPath $AggregationInputPath jei (-not $isReachable) { $bucketFailures[$bucketKey]. Nepasiekiamas += $hostname } dar { # Įrenginys YRA pasiekiamas, bet dar neatnaujintas – gali būti laikina triktis arba laukiama perkrovimo $bucketFailures[$bucketKey]. AliveButFailed += $hostname $stillWaiting += $hostname } } # Sprendimas už talpyklą: blokuoti tik jei įrenginiai tikrai UNREACHABLE # Alive devices with failures = temporary, continue rollout foreach ($bucketKey in $bucketFailures.Keys) { $bf = $bucketFailures[$bucketKey] $unreachableCount = $bf. Nepasiekiamas.skaičius $aliveFailedCount = $bf. AliveButFailed.Count # Patikrinkite, ar šioje talpykloje yra sėkmingų įvykių (iš atnaujintų įrenginių duomenų) $bucketHasSuccesses = $stSuccessBuckets ir $stSuccessBuckets.Contains($bucketKey) jei ($unreachableCount -gt 0 -ir $aliveFailedCount -eq 0) { # VISI įrenginiai su klaida nepasiekiami – užblokuokite talpyklą jei ($newlyBlocked -notcontains $bucketKey) { $BlockedBuckets[$bucketKey] = @{ BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Reason = "All $unreachableCount device(s) unreachable after $($bf. HoursSinceWave) valandos" FailedDevices = ($bf. Nepasiekiama -join ", ") WaveNumber = $bf. "WaveNumber" DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } dar { 0 } } $newlyBlocked += $bucketKey Write-Log "BUCKET BLOCKED: $bucketKey ($unreachableCount device(s) unreachable: $($bf. Nepasiekiamas -join ', '))" "BLOCKED" } } elseif ($aliveFailedCount -gt 0) { # Įrenginiai yra gyvi, bet neatnaujinami – laikina triktis, NEBLOKUOKITE Write-Log "Bucket $($bucketKey.Substring(0, [Math]::Min(16, $bucketKey.Length)))...: $aliveFailedCount įrenginys (-iai) gyvas (-i), bet laukia, $unreachableCount nepasiekiamas – NOT blocking (temporary)" "INFO" jei ($unreachableCount -gt 0) { Write-Log " Nepasiekiama: $($bf. Nepasiekiamas -join ', ')" "WARN" } Write-Log " Gyvas, bet laukia: $($bf. AliveButFailed - join ', ')" "INFO" # Track failure count in rollout state for monitoring jei (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} } $RolloutState.TemporaryFailures[$bucketKey] = @{ AliveButFailed = $bf. AliveButFailed Nepasiekiama = $bf. Nepasiekiamas LastChecked = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" } } } jei ($stillWaiting.Count -gt 0) { Write-Log "Įrenginiai pasiekiami, bet laukia naujinimo (gali reikėti perkrauti): $($stillWaiting.Count)" "INFO" } grąžinimo $newlyBlocked }
# ============================================================================ # AUTO-UNBLOCK: atblokuoti talpyklas, kai įrenginiai sėkmingai atnaujinami # ============================================================================
function Update-AutoUnblockedBuckets { <# . APRAŠYMAS / KONTROLĖ Tikrina, ar atnaujinti įrenginiai užblokuotose talpyklose (1808 įvykis). Automatiškai atblokuoja, jei atnaujinti VISI tiksliniai įrenginiai talpykloje.Jei tik atnaujinti KAI KURIE įrenginiai, praneša administratoriui, kuris gali atblokuoti rankiniu būdu. Administratorius gali atblokuoti rankiniu būdu, naudodami: .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "kelias" -UnblockBucket "BucketKey" #> param( $BlockedBuckets, $RolloutState, [masyvas]$NotUpdatedDevices, [eilutė]$ReportBasePath, [maišos lentelė]$NotUpdatedIndexes, [sveikasis skaičius]$LogSampleSize = 25 ) $autoUnblocked = @() $bucketsToCheck = @($BlockedBuckets.Keys) $hostSet = jei ($NotUpdatedIndexes ir $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } dar { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet } foreach ($bucketKey in $bucketsToCheck) { $bucketInfo = $BlockedBuckets[$bucketKey] # Gaukite visus mūsų pritaikytus įrenginius iš šios talpyklos istoriškai $targetedDevicesInBucket = @() foreach ($wave in $RolloutState.WaveHistory) { $targetedDevicesInBucket += @($wave. Įrenginiai | Where-Object { $_. BucketKey -eq $bucketKey }) } jei ($targetedDevicesInBucket.Count -eq 0) { continue } # Patikrinkite, kiek tikslinių įrenginių vis dar yra "NotUpdated" ir naujinama $updatedDevices = @() $stillPendingDevices = @() foreach ($targetedDevice in $targetedDevicesInBucket) { jei ($hostSet.Contains($targetedDevice.Hostname)) { $stillPendingDevices += $targetedDevice.Hostname } dar { $updatedDevices += $targetedDevice.Hostname } } jei ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -eq 0) { # VISI tiksliniai įrenginiai atnaujinti - automatiškai atblokuoti! $BlockedBuckets.Remove($bucketKey) $autoUnblocked += @{ BucketKey = $bucketKey UpdatedDevices = $updatedDevices PreviouslyBlockedAt = $bucketInfo.BlockedAt Reason = "All $($updatedDevices.Count) targeted device(s) successfully updated" } Write-Log "AUTO-UNBLOCKED: $bucketKey (All $($updatedDevices.Count) targeted device(s) updated successfully)" "OK" # Papildančiojo OĮG bangų skaičius šios talpyklos OĮG (OĮG sekimas) $bucketOEM = jei ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } dar { 'Unknown' } # Extract OEM from pipe-delimited key or default jei (-not $RolloutState.OEMWaveCounts) { $RolloutState.OEMWaveCounts = @{} } $currentWave = jei ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } dar { 0 } $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1 Write-Log " OEM '$bucketOEM' wave count incremented to $($currentWave + 1) (next allocation: $([int][Math]::P ow(2, $currentWave + 1)) devices)" "INFO" } elseif ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -gt 0) { # KAI kurie įrenginiai atnaujinti, bet kiti vis dar laukia – praneškite administratoriui (tik vieną kartą) jei (-not $bucketInfo.UnblockCandidate) { $bucketInfo.UnblockCandidate = $true $bucketInfo.UpdatedDevices = $updatedDevices $bucketInfo.PendingDevices = $stillPendingDevices $bucketInfo.NotifiedAt = (Gavimo data). ToString("mmmm-MM-dd HH:mm:ss") Write-Log "INFO" Write-Log "========== DALINIS NAUJINIMAS UŽBLOKUOTOJE TALPYKLOJE ==========" "INFO" Write-Log "Bucket: $bucketKey" "INFO" $updatedSample = @($updatedDevices | Select-Object -First $LogSampleSize) $pendingSample = @($stillPendingDevices | Select-Object -Pirmasis $LogSampleSize) $updatedSuffix = if ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count – $LogSampleSize) daugiau)" } dar { "" } $pendingSuffix = if ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count – $LogSampleSize) daugiau)" } dar { "" } Write-Log "Atnaujinti įrenginiai ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "OK" Write-Log "Still pending ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN" Write-Log "INFO" Write-Log "To manually unblock this bucket after verification, run:" "INFO" Write-Log ".\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'" "INFO" Write-Log "=======================================================" "INFO" Write-Log "INFO" } } } grąžinimo $autoUnblocked }
# ============================================================================ # WAVE GENERATION (INLINED - neapima užblokuotų talpyklų) # ============================================================================
function New-RolloutWave { param( [eilutė]$AggregationPath, $BlockedBuckets, $RolloutState, [sveikasis skaičius]$MaxDevicesPerWave = 50, [string[]]$AllowedHostnames = @(), [eilutė[]$ExcludedHostnames = @() ) # Įkelti agregavimo duomenis $notUptodateCsv = Get-ChildItem -Path $AggregationPath -Filter "*NotUptodate*.csv" | Where-Object { $_. Pavadinimas -notlike "*Buckets*" } | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 jei (-not $notUptodateCsv) { Write-Log "NotUptodate CSV nerasta" "KLAIDA" grąžinimo $null } $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName) # Normalize HostName -> Hostname for consistency (CSV naudoja HostName, kodas naudoja Hostname) foreach ($device in $allNotUpdated) { jei ($device. PSObject.Properties['HostName'], o ne $device. PSObject.Properties['Hostname']) { $device | Add-Member -NotePropertyName Hostname -NotePropertyValue $device. HostName – priverstinai } } # Išfiltruokite užblokuotas talpyklas $eligibleDevices = @($allNotUpdated | Where-Object { $bucketKey = Get-BucketKey $_ -not $BlockedBuckets.Contains($bucketKey) }) # Filtruoti tik leidžiamiems įrenginiams (jei nurodyta AllowList) # AllowList = tikslinis diegimas – bus svarstomi tik šie įrenginiai jei ($AllowedHostnames.Count -gt 0) { $beforeCount = $eligibleDevices.Count $eligibleDevices = @($eligibleDevices | Where-Object { $_. Pagrindinio kompiuterio vardas $AllowedHostnames }) $allowedCount = $eligibleDevices.Count Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO" } # Filtruoti VIP / neįtrauktus įrenginius (BlockList) # BlockList taikomas AFTER AllowList jei ($ExcludedHostnames.Count -gt 0) { $beforeCount = $eligibleDevices.Count $eligibleDevices = @($eligibleDevices | Where-Object { $_. Pagrindinio kompiuterio vardas –notin $ExcludedHostnames }) $excludedCount = $beforeCount – $eligibleDevices.count jei ($excludedCount -gt 0) { Write-Log "Excluded $excludedCount VIP/protected devices from rollout" "INFO" } } jei ($eligibleDevices.Count -eq 0) { Write-Log "Nėra likusių tinkamų įrenginių (visi atnaujinti arba užblokuoti)" "Gerai" grąžinimo $null } # Gauti įrenginius, kurie jau įdiegti (iš ankstesnių bangų) $devicesAlreadyInRollout = @() jei ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) { $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object { $_. Įrenginiai | ForEach-Object { $_. Pagrindinio kompiuterio vardas } } | Where-Object { $_ }) } Write-Log "Įrenginiai, kurie jau įdiegti: $($devicesAlreadyInRollout.Count)" "INFO" # Atskirti patikimumo lygiu $highConfidenceDevices = @($eligibleDevices | Where-Object { $_. ConfidenceLevel -eq "Didelis pasitikėjimas" - ir $_. Pagrindinio kompiuterio vardas –notin $devicesAlreadyInRollout }) # Būtinas veiksmas apima: # - Aiškus "Būtinas veiksmas" # – tuščias / nulinis ConfidenceLevel # – BET kuri nežinoma / neatpažinta ConfidenceLevel reikšmė (laikoma būtinuoju veiksmu) $knownSafeCategories = @( "Didelis pasitikėjimas", "Laikinai pristabdyta", "Stebima", "Stebima – reikia daugiau duomenų", "Nepalaikoma", "Not Supported - Known Limitation" ) $actionRequiredDevices = @($eligibleDevices | Where-Object { $_. ConfidenceLevel -notin $knownSafeCategories -and $_. Hostname -notin $devicesAlreadyInRollout }) Write-Log "High Confidence (not in rollout): $($highConfidenceDevices.Count)" "INFO" Write-Log "Būtinas veiksmas (neįdiegtas): $($actionRequiredDevices.Count)" "INFO" # Komponavimo versijos bangų įrenginiai $waveDevices = @() # DIDELIS PASITIKĖJIMAS: įtraukti VISKĄ (saugu įdiegti) jei ($highConfidenceDevices.Count -gt 0) { Write-Log "Adding all $($highConfidenceDevices.Count) High Confidence devices" "WAVE" $waveDevices += $highConfidenceDevices } # BŪTINA IMTIS VEIKSMŲ: progresyvus diegimas (segmentas pagrįstas OĮG skaidiniu, skirtu nulinio sėkmės talpykloms) # Strategija: # - Talpyklos su 0 sėkmingų įvykių: OĮG (po 1 kiekvienam OĮG –> 2 kiekvienam OĮG –> 4 kiekvienam OĮG) # - Talpyklos su ≥1 sėkmingu: dukart nemokamai be OĮG apribojimų jei ($actionRequiredDevices.Count -gt 0) { # Įkėlimo talpyklos sėkmės skaičius iš atnaujintų įrenginių CSV (sėkmingai atnaujinti įrenginiai) $updatedCsv = Get-ChildItem -Path $AggregationPath -Filter "*updated_devices*.csv" | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 $bucketStats = @{} jei ($updatedCsv) { $updatedDevices = Import-Csv $updatedCsv.FullName # Skaičiuoti sėkmingus pasiekimus per BucketId $updatedDevices | ForEach-Object { $key = Get-BucketKey $_ jei ($key) { jei (-not $bucketStats.ContainsKey($key)) { $bucketStats[$key] = @{ Successes = 0; Laukiama = 0; Iš viso = 0 } } $bucketStats[$key]. Successes++ $bucketStats[$key]. Total++ } } Write-Log "Įkelti $($updatedDevices.Count) atnaujinti įrenginiai $($bucketStats.Count) talpyklose" "INFO" } dar { # Atsarginė priemonė: išbandykite ActionRequired_Buckets CSV $bucketsCsv = Get-ChildItem -Path $AggregationPath -Filter "*ActionRequired_Buckets*.csv" | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 jei ($bucketsCsv) { Import-Csv $bucketsCsv.FullName | ForEach-Object { $key = jei ($_. BucketId) { $_. BucketId } dar { "$($_. Gamintojas)|$($_. Modelis)|$($_. BIOS)" } $bucketStats[$key] = @{ Sėkmės = [int]$_. Sėkmės Laukiama = [int]$_. Laukiama Iš viso = [sveikasis skaičius]$_. TotalDevices } } } } # Grupė NotUpdated įrenginiai pagal talpyklą (Gamintojas|Modelis |BIOS) $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ } # Atskirkite talpyklas: nulinis sėkmingas ir sėkmingas $zeroSuccessBuckets = @() $hasSuccessBuckets = @() foreach ($bucket in $buckets) { $bucketKey = $bucket. Vardas, pavadinimas $bucketDevices = @($bucket. grupė) $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Pagrindinio kompiuterio vardas }) # Skaičiuoti sėkmingus pasiekimus šioje talpykloje $stats = $bucketStats[$bucketKey] $successes = jei ($stats) { $stats. Sėkmingos veiklos } dar { 0 } # Rasti įrenginius, įdiegtus į šią talpyklą iš bangų istorijos $deployedToBucket = @() foreach ($wave in $RolloutState.WaveHistory) { ($device $wave. Įrenginiai) { jei ($device. BucketKey -eq $bucketKey ir $device. Pagrindinio kompiuterio vardas) { $deployedToBucket += $device. Hostname } } } $deployedToBucket = @($deployedToBucket | Sort-Object -Unique) # Patikrinkite, ar VISI įdiegti įrenginiai pranešė apie sėkmingą sėkmę $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames }) $confirmedSuccess = $deployedToBucket.Count – $stillPending.Count # Jei laukiama, praleiskite šią talpyklą, kol visi patvirtins jei ($stillPending.Count -gt 0) { $parts = $bucketKey -split '\|' $displayName = "$($parts[0]) - $($parts[1])" Write-Log " segmentas: $displayName – deployed=$($deployedToBucket.Count), confirmed=$confirmedSuccess, pending=$($stillPending.Count) (waiting)" "INFO" Toliau } # Likę tinkami = įrenginiai dar neįdiegti $devicesNotYetTargeted = @($bucketDevices | Where-Object { $_. Hostname -notin $deployedToBucket }) jei ($devicesNotYetTargeted.Count -eq 0) { continue } # Skirstyti pagal sėkmingų įvykių skaičių $bucketInfo = @{ BucketKey = $bucketKey Įrenginiai = $devicesNotYetTargeted ConfirmedSuccess = $confirmedSuccess Successes = $successes OEM = jei ($bucket. Grupė[0]. WMI_Manufacturer) { $bucket. Grupė[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } dar { 'Nežinoma' } } jei ($successes -eq 0) { $zeroSuccessBuckets += $bucketInfo } dar { $hasSuccessBuckets += $bucketInfo } } # === SĖKMINGO PROCESO TALPYKLOS (≥1 pavyko) === # Dvigubas sėkmingų įvykių skaičius – jei pavyko 14, įdiekite 28 kitą foreach ($bucketInfo in $hasSuccessBuckets) { $nextBatchSize = $bucketInfo.Successes * 2 $nextBatchSize = [Matematika]:Min($nextBatchSize, $MaxDevicesPerWave) $nextBatchSize = [Matematika]:Min($nextBatchSize, $bucketInfo.Devices.Count) jei ($nextBatchSize -gt 0) { $selectedDevices = @($bucketInfo.Devices | Select-Object -First $nextBatchSize) $waveDevices += $selectedDevices $parts = jei ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length))) } $displayName = "$($parts[0]) - $($parts[1])" Write-Log " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x confirmed)" "INFO" } } # === APDOROTI NULINIO PASISEKIMO TALPYKLAS (paskirstyti tarp OĮG su OĮG sekimu) === # Tikslas: rizikos skleidimas skirtinguose OĮG, atskirai stebėti OĮG eigą # Kiekvienas OĮG vykdomas atsižvelgiant į jo sėkmingų įvykių istoriją: # - OĮG su sėkmės: gauna daugiau įrenginių kitą bangą (2^waveCount) # – OĮG be sėkmingų įvykių: likite dabartiniame lygyje, kol pasiseks jei ($zeroSuccessBuckets.Count -gt 0) { # Inicijuoti OĮG bangų skaičių, jei toks nėra jei (-not $RolloutState.OEMWaveCounts) { $RolloutState.OEMWaveCounts = @{} } # Grupės nepavykę segmentai pagal OĮG $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OĮG } $totalZeroSuccessAdded = 0 $oemsDeployedTo = @() foreach ($oemGroup in $oemBuckets) { $oemName = $oemGroup.Name # Gauti šio OĮG bangų skaičių (prasideda nuo 0) $oemWaveCount = jei ($RolloutState.OEMWaveCounts[$oemName]) { $RolloutState.OEMWaveCounts[$oemName] Dar } { 0 } # Skaičiuoti šio OĮG įrenginius: 2^waveCount (1, 2, 4, 8...) $devicesForThisOEM = [int][Matematika]::P ow(2, $oemWaveCount) $devicesForThisOEM = [Matematika]::Max(1, $devicesForThisOEM) $oemDevicesAdded = 0 # Pasirinkite iš kiekvienos talpyklos šiame OĮG foreach ($bucketInfo in $oemGroup.Group) { $remaining = $devicesForThisOEM - $oemDevicesAdded jei ($remaining -le 0) { break } $toTake = [Matematika]:Min($remaining, $bucketInfo.Devices.Count) jei ($toTake -gt 0) { $selectedDevices = @($bucketInfo.Devices | Select-Object -First $toTake) $waveDevices += $selectedDevices $oemDevicesAdded += $toTake $totalZeroSuccessAdded += $toTake $parts = jei ($bucketInfo.BucketKey -match '\|') { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length))) } $displayName = "$($parts[0]) - $($parts[1])" Write-Log " [ZERO-SUCCESS] $displayName - Deploying=$toTake (OEM wave $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN" } } jei ($oemDevicesAdded -gt 0) { Write-Log " OEM: $oemName - Wave $oemWaveCount, Added $oemDevicesAdded devices" "INFO" $oemsDeployedTo += $oemName } } # Sekti, į kuriuos OĮG įdiegėme (norint padidinti kitą sėkmingą patikrą) jei ($oemsDeployedTo.Count -gt 0) { $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo Write-Log "Zero-success deployment: $totalZeroSuccessAdded devices across $($oemsDeployedTo.Count) OĮG" "INFO" } } } jei (@($waveDevices). Skaičius -eq 0) { grąžinimo $null } grąžinimo $waveDevices }
# ============================================================================ # GPO DEPLOYMENT (INLINED - sukuria GPO, saugos grupę, saitus) # ============================================================================
function Deploy-GPOForWave { param( [eilutė]$GPOName, [eilutė]$TargetOU, [eilutė]$SecurityGroupName, [masyvas]$WaveHostnames, [bulio logika]$DryRun = $false ) # ADMX strategija: SecureBoot.admx - SecureBoot_AvailableUpdatesPolicy # Registro kelias: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot # reikšmės pavadinimas: AvailableUpdatesPolicy # Enabled Value: 22852 (0x5944) - Atnaujinti visus saugiosios įkrovos kodus + bootmgr # Išjungta reikšmė: 0 2010 # Using Grupės strategija Preferences (GPP) for reliable HKLM\SYSTEM path deployment # GPP sukuria parametrus dalyje: Computer Configuration > Preferences > Windows Settings > Registry $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" $RegistryValueName = "AvailableUpdatesPolicy" $RegistryValue = 22852 # 0x5944 – atitinka ADMX enabledValue Write-Log "GPO diegimas: $GPOName" WAVE" Write-Log "Registras: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X')))" "INFO" jei ($DryRun) { Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO" Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO" Write-Log "[DRYRUN] Pridės $(@($WaveHostnames). Skaičius) kompiuterių grupavimas "INFO" Write-Log "[DRYRUN] susies GPO su: $TargetOU" "INFO" grąžinimo $true } išbandykite { # Importuoti reikiamus modulius Import-Module GroupPolicy - ErrorAction Stop Import-Module ActiveDirectory - ErrorAction Stop } sugauti { Write-Log "Nepavyko importuoti būtinų modulių (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR" grąžinimo $false } # 1 veiksmas: sukurkite arba gaukite GPO $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue jei ($existingGPO) { Write-Log "GPO jau yra: $GPOName" "INFO" $gpo = $existingGPO } dar { išbandykite { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Sukurtas GPO: $GPOName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR" grąžinimo $false } } # 2 veiksmas: nustatykite registro reikšmę naudodami Grupės strategija Preferences" (GPP) # GPP yra patikimesnis HKLM\SYSTEM keliams nei Set-GPRegistryValue išbandykite { # Pirmiausia pabandykite pašalinti bet kurią esamą šios reikšmės nuostatą (kad išvengtumėte dublikatų) Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue # Sukurti GPP registro nuostatą su veiksmu "Pakeisti" # Pakeisti = Kurti, jei nėra, naujinti, jei toks yra (patikimiausias) # Naujinimas = tik naujinimas, jei toks yra (nepavyksta, jei reikšmės nėra) Set-GPPrefRegistryValue pavadinimas $GPOName ' -Konteksto kompiuteris ' -Veiksmo keitimas ' - Rakto $RegistryKey ' -ValueName $RegistryValueName ' -Įveskite DWord ' -Reikšmės $RegistryValue Write-Log "Configured GPP registry preference: $RegistryValueName = 0x5944 (Action=Replace)" "OK" } sugauti { Write-Log "GPP nepavyko, bandoma Set-GPRegistryValue: $($_. Exception.Message)" "WARN" # Grįžti į Set-GPRegistryValue (veikia, jei įdiegtas ADMX) išbandykite { Set-GPRegistryValue - pavadinimas $GPOName ' -Rakto $RegistryKey ' -ValueName $RegistryValueName ' -Įveskite DWord ' Reikšmės $RegistryValue Write-Log "Sukonfigūruotas registras naudojant Set-GPRegistryValue: $RegistryValueName = 0x5944" "Gerai" } sugauti { Write-Log "Nepavyko nustatyti registro reikšmės: $($_. Exception.Message)" "ERROR" grąžinimo $false } } # 3 veiksmas: saugos grupės kūrimas arba gavimas $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue jei (-not $existingGroup) { išbandykite { $group = New-ADGroup -Name $SecurityGroupName ' -GroupCategory Sauga ' -GroupScope DomainLocal ' -Aprašas "Kompiuteriai, skirti saugiosios įkrovos išleidimą - $GPOName" ' -PassThru Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR" grąžinimo $false } } dar { Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO" $group = $existingGroup } # 4 veiksmas: kompiuterių įtraukimas į saugos grupę $added = 0 $failed = 0 foreach ($hostname in $WaveHostnames) { išbandykite { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue $added++ } sugauti { $failed++ } } Write-Log "Įtraukti $added kompiuteriai į saugos grupę ($failed nerasta AD)" "Gerai" # 5 veiksmas: konfigūruokite GPO saugos filtravimą išbandykite { # Pašalinti numatytąją parinktį "Autentifikuoti vartotojai" Taikyti teises (toliau skaityti) Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue # Įtraukti teisę taikyti mūsų saugos grupei Set-GPPermission – pavadinimas $GPOName – TargetName $SecurityGroupName – TargetType grupė – PermissionLevel GpoApply – ErrorAction stabdyti Write-Log "Konfigūruotas saugos filtravimas: $SecurityGroupName" } sugauti { Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN" Write-Log "GPO gali būti taikomas visiems kompiuteriams, susieti OU - patikrinti rankiniu būdu" "WARN" } # 6 veiksmas: susieti GPO su OU (LABAI SVARBU, kad strategija būtų taikoma) jei ($TargetOU) { išbandykite { $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName -eq $GPOName } jei (-not $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop Write-Log "Susietas GPO su: $TargetOU" "Gerai" Write-Log "GPO bus taikomas kitame gpupdate tiksliniuose kompiuteriuose" "INFO" } dar { Write-Log "GPO jau susietas su paskirties OU" "INFO" } } sugauti { Write-Log "KRITINIS: nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR" Write-Log "GPO buvo sukurtas, bet NE SUSIETAS – jis NEBUS taikomas jokiems kompiuteriams!" "KLAIDA" Write-Log "Būtina taisyti rankiniu būdu: New-GPLink – pavadinimas "$GPOName" – tikslinė "$TargetOU" - "LinkEnabled" taip" "KLAIDA" grąžinimo $false } } dar { Write-Log "ĮSPĖJIMAS: Nenurodytas TargetOU – GPO sukurtas, bet NE SUSIETAS!" "KLAIDA" Write-Log "Rankinis susiejimas būtinas, kad GPO įsigaliotų" "KLAIDA" Write-Log "Run: New-GPLink -Name '$GPOName' -Target '<Your-Domain-DN>' -LinkEnabled Yes" "ERROR" } # 7 veiksmas: patikrinkite GPO konfigūraciją Write-Log "Tikrinama GPO konfigūracija..." "INFO" išbandykite { $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop Write-Log "GPO būsena: $($gpoReport.GpoStatus)" "INFO" # Patikrinkite, ar sukonfigūruotas registro parametras $regSettings = Get-GPRegistryValue - Name $GPOName - Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue jei (-not $regSettings) { # Išbandykite GPP registro patikrą (kitas kelias GPO) Write-Log "GPP registro nuostatų tikrinimas..." "INFO" } } sugauti { Write-Log "Nepavyko patikrinti GPO: $($_. Exception.Message)" "WARN" } grąžinimo $true }
# ============================================================================ # WINCS DEPLOYMENT ("AvailableUpdatesPolicy GPO" alternatyva) #============================================================================ Nr. nuoroda: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe 2010 # WinCS komandos (vykdyti galinio punkto sistemos kontekste): # užklausa: WinCsFlags.exe /query – rakto F33E0C8E002 # Taikyti: WinCsFlags.exe /apply --key "F33E0C8E002" # Reset: WinCsFlags.exe /reset --key "F33E0C8E002" 2010 # Šis metodas įdiegia GPO su suplanuota užduotimi, kuri vykdo WinCsFlags.exe /apply # kaip SYSTEM tiksliniuose pabaigos taškuose. Panašiai kaip diegiamas aptikimo scenarijus, #, bet paleidžiama vieną kartą (paleisties metu), o ne kasdien.
function Deploy-WinCSGPOForWave { <# . ANOTACIJA Įdiekite "WinCS" saugiosios įkrovos įgalinimą per GPO suplanuotą užduotį.. APRAŠYMAS / KONTROLĖ Sukuria GPO, kuris įdiegia suplanuotą užduotį vykdyti WinCsFlags.exe /apply sistemos kontekste kompiuterio paleisties metu. Taikoma saugos grupės valdikliams.. PARAMETRO GPOName GPO pavadinimas.. PARAMETER TargetOU OU, kad susietumėte SU GPO.. PARAMETER SecurityGroupName GPO filtravimo saugos grupė.. PARAMETER WaveHostnames Pagrindinio kompiuterio vardai, įtraukti į saugos grupę.. PARAMETER WinCSKey Taikomas "WinCS" raktas (numatytasis: F33E0C8E002).. PARAMETER DryRun Jei teisinga, rašykite tik tai, ką reikėtų padaryti.#> param( [Parametras(Privalomas = $true)] [eilutė]$GPOName, [Parametras(Privalomas = $false)] [eilutė]$TargetOU, [Parametras(Privalomas = $true)] [eilutė]$SecurityGroupName, [Parametras(Privalomas = $true)] [masyvas]$WaveHostnames, [Parametras(Privalomas = $false)] [eilutė]$WinCSKey = "F33E0C8E002", [Parametras(Privalomas = $false)] [bulio logika]$DryRun = $false ) # suplanuotos užduoties konfigūracija, skirta WinCsFlags.exe $TaskName = "SecureBoot-WinCS-Apply" $TaskPath = "\Microsoft\Windows\SecureBoot\" $TaskDescription = "Taiko saugiosios įkrovos konfigūraciją per WinCS – raktas: $WinCSKey" Write-Log "WinCS GPO diegimas: $GPOName" WAVE" Write-Log "Užduotis bus vykdoma: WinCsFlags.exe /apply --key ""$WinCSKey"" "INFO" Write-Log "Trigger: At system startup (runs once as SYSTEM)" "INFO" jei ($DryRun) { Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO" Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO" Write-Log "[DRYRUN] pridės $(@($WaveHostnames). Skaičius) kompiuterių grupavimas "INFO" Write-Log [DRYRUN] Įdiegtų suplanuotą užduotį: $TaskName" "INFO" Write-Log "[DRYRUN] susies GPO su: $TargetOU" "INFO" grąžinti @{ Success = $true GPOCreated = $false GroupCreated = $false KompiuteriaiĮtraukti = 0 } } išbandykite { # Importuoti reikiamus modulius Import-Module GroupPolicy - ErrorAction Stop Import-Module ActiveDirectory - ErrorAction Stop } sugauti { Write-Log "Nepavyko importuoti būtinų modulių (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } # 1 veiksmas: sukurkite arba gaukite GPO $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue jei ($gpo) { Write-Log "GPO jau yra: $GPOName" "INFO" } dar { išbandykite { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Sukurtas GPO: $GPOName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } } # 2 veiksmas: GPO diegimo suplanuotos užduoties XML kūrimas # Tai sukuria užduotį, kuri paleidžia WinCsFlags.exe /apply paleisties metu $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <aprašo>$TaskDescription</aprašo> WinCsFlags.exe1 author>SYSTEM</Author> WinCsFlags.exe5 /RegistrationInfo> WinCsFlags.exe7 paleidikliai> WinCsFlags.exe9 BootTrigger> <Įgalintas>teisingas</ įgalintas> <delsos>PT5M</ delsos> </BootTrigger> >< / paleidikliai <principals> <Principal id="Author"> <UserId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> parametrų>< <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>klaidingas</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <PradėtiKasgalima>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>klaidingas</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Įgalintas>teisingas</ įgalintas> <paslėptas>klaidingas</paslėptas> <RunOnlyIfIdle>false</RunOnlyIfIdle> WinCsFlags.exe03 DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession> WinCsFlags.exe07 UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine> WinCsFlags.exe11 WakeToRun>false</WakeToRun> WinCsFlags.exe15 ExecutionTimeLimit>PT1H</ExecutionTimeLimit> WinCsFlags.exe19 DeleteExpiredTaskAfter>P30D</DeleteExpiredTaskAfter> WinCsFlags.exe23 7 prioriteto></ prioriteto> WinCsFlags.exe27 /Settings> WinCsFlags.exe29 Actions Context="Author"WinCsFlags.exe30 WinCsFlags.exe31 Exec> WinCsFlags.exe33 Command>WinCsFlags.exe</Command> WinCsFlags.exe37 argumentai>/apply --key "$WinCSKey"WinCsFlags.exe39 /Arguments> WinCsFlags.exe41 /Exec> WinCsFlags.exe43 /Actions> WinCsFlags.exe45 / užduočių> " @
# Step 3: Deploy scheduled task via GPO Preferences # Saugoti užduoties XML SYSVOL GPO suplanuotų užduočių tiesioginėms užduotims išbandykite { $gpoId = $gpo. Id.ToString() $sysvolPath = "\\$((Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Policies\{$gpoId}\Machine\Preferences\ScheduledTasks" jei (-not (testo kelias $sysvolPath)) { New-Item -ItemType katalogas -kelias $sysvolPath -Force | Nu neapibrėžta reikšmė (Out-Null) } # Sukurkite GPP ScheduledTasks.xml $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"> <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName "image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}"> <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U"> <Task version="1.3"> <RegistrationInfo> <aprašo>$TaskDescription</aprašo> </RegistrationInfo> <principals> <Principal id="Author"> <UserId>NT AUTHORITY\System</UserId> <LogonType>S4U</LogonType> <RunLevel>HighestAvailable</RunLevel> </ pagrindinis> </Principals> parametrų>< <IdleSettings> <trukmė>PT5M</Duration> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle">klaidingas</RestartOnIdle> </IdleSettings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <PradėtiKasgalima>true</StartWhenAvailable> <AllowStartOnDemand>true</AllowStartOnDemand> <Įgalintas>true</Enabled> <paslėptas>klaidingas</paslėptas> <ExecutionTimeLimit>PT1H</ExecutionTimeLimit> <Priority>7</Priority> <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter> </ parametrų> <paleidiklių> <TimeTrigger> <StartBoundary>$(Get-Date -Format 'yyyy-MM-dd')T00:00:00</StartBoundary> <Įgalintas>teisingas</ įgalintas> </TimeTrigger> </Triggers> veiksmų>< <Exec> <Command>WinCsFlags.exe</Command> <argumentai>/apply --key "$WinCSKey"</Arguments> </Exec> </Actions> </ užduočių> <apgyvendinimo įstaigos> </ImmediateTaskV2> </ScheduledTasks> "@ $gppTaskXml | Out-File -FilePath (sujungimo kelio $sysvolPath "ScheduledTasks.xml") - Utf8 kodavimas -Force Write-Log "Deployed scheduled task to GPO: $TaskName" "OK" } sugauti { Write-Log "Nepavyko įdiegti suplanuotos užduoties XML: $($_. Exception.Message)" "WARN" Write-Log "Grįžimas prie registru pagrįsto "WinCS" diegimo" "INFO" # Atsarginė: Naudoti WinCS registro metodas, jei GPP suplanuota užduotis nepavyksta # WinCS taip pat gali būti paleistas per registro raktą # (Diegimas priklauso nuo "WinCS" registro API, jei yra) } # 4 veiksmas: saugos grupės kūrimas arba gavimas $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue jei (-not $group) { išbandykite { $group = New-ADGroup -Name $SecurityGroupName ' -GroupCategory Sauga ' -GroupScope DomainLocal ' -Aprašymas "Kompiuteriai, skirti saugiosios įkrovos WinCS rollout - $GPOName" ' -PassThru Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } } dar { Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO" } # 5 veiksmas: kompiuterių įtraukimas į saugos grupę $added = 0 $failed = 0 foreach ($hostname in $WaveHostnames) { išbandykite { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue $added++ } sugauti { $failed++ } } Write-Log "Įtraukti $added kompiuteriai į saugos grupę ($failed nerasta AD)" "Gerai" # 6 veiksmas: konfigūruokite GPO saugos filtravimą išbandykite { Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue Set-GPPermission – pavadinimas $GPOName – TargetName $SecurityGroupName – TargetType grupė – PermissionLevel GpoApply – ErrorAction – sustabdymas Write-Log "Konfigūruotas saugos filtravimas: $SecurityGroupName" } sugauti { Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN" } # 7 veiksmas: susieti GPO su OU jei ($TargetOU) { išbandykite { $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName -eq $GPOName } jei (-not $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop Write-Log "Susietas GPO su: $TargetOU" "Gerai" } dar { Write-Log "GPO jau susietas su paskirties OU" "INFO" } } sugauti { Write-Log "KRITINIS: nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = "GPO saitas nepavyko: $($_. Exception.Message)" } } } Write-Log "WinCS GPO diegimas baigtas" "Gerai" Write-Log "Machines will run WinCsFlags.exe at next GPO refresh + reboot/startup" "INFO" grąžinti @{ Success = $true GPOCreated = $true GroupCreated = $true ComputersAdded = $added ComputersFailed = $failed } }
# Wrapper function to maintain compatibility with main loop funkcija Deploy-WinCSForWave { param( [Parametras(Privalomas = $true)] [masyvas]$WaveHostnames, [Parametras(Privalomas = $false)] [eilutė]$WinCSKey = "F33E0C8E002", [Parametras(Privalomas = $false)] [eilutė]$WavePrefix = "SecureBoot-Rollout", [Parametras(Privalomas = $false)] [sveikasis skaičius]$WaveNumber = 1, [Parametras(Privalomas = $false)] [eilutė]$TargetOU, [Parametras(Privalomas = $false)] [bulio logika]$DryRun = $false ) $gpoName = "${WavePrefix}-WinCS-Wave${WaveNumber}" $securityGroup = "${WavePrefix}-WinCS-Wave${WaveNumber}" $result = Deploy-WinCSGPOForWave ' -GPOName $gpoName ' -TargetOU $TargetOU ' -SecurityGroupName $securityGroup ' -WaveHostnames $WaveHostnames ' -WinCSKey $WinCSKey ' -DryRun $DryRun # Konvertuoti į numatomą grąžinimo formatą grąžinti @{ Success = $result. Sėkmės Pritaikė = $result. Įtraukti kompiuteriai Praleista = 0 Nepavyko = jei ($result. Kompiuteriai nepavyko) { $result. Kompiuteriai nepavyko } dar { 0 } Rezultatai = @() } }
# ============================================================================ # ĮGALINTI UŽDUOTIES DIEGIMĄ #============================================================================ # Visuotinai diegti Enable-SecureBootUpdateTask.ps1 įrenginiuose, kuriuose išjungta suplanuota užduotis.# Naudoja GPO su nedelsiant vykdoma suplanuota užduotimi, kuri vykdoma vieną kartą.
function Deploy-EnableTaskGPO { <# . ANOTACIJA Diegti Enable-SecureBootUpdateTask.ps1 naudojant GPO suplanuotą užduotį.. APRAŠYMAS / KONTROLĖ Sukuria GPO, kuris įdiegia vienkartinę suplanuotą užduotį, kad įjungtų Saugiosios įkrovos naujinimo suplanuota užduotis tiksliniuose įrenginiuose.. PARAMETER TargetOU OU, kad susietumėte SU GPO.. PARAMETER TargetHostnames Įrenginių su išjungta užduotimi pagrindinio kompiuterio vardai (iš agregavimo ataskaitos).. PARAMETER DryRun Jei teisinga, rašykite tik tai, ką reikėtų padaryti.#> param( [Parametras(Privalomas = $false)] [eilutė]$TargetOU, [Parametras(Privalomas = $true)] [masyvas]$TargetHostnames, [Parametras(Privalomas = $false)] [bulio logika]$DryRun = $false ) $GPOName = "SecureBoot-EnableTask-Remediation" $SecurityGroupName = "SecureBoot-EnableTask-Devices" $TaskName = "SecureBoot-EnableTask-OneTime" $TaskDescription = "Vienkartinė užduotis įgalinti saugiosios įkrovos naujinimo suplanuotą užduotį" Write-Log "=" * 70 "INFO" Write-Log "DEPLOYING ENABLE TASK REMEDIATION" "INFO" Write-Log "=" * 70 "INFO" Write-Log "Paskirties įrenginiai: $($TargetHostnames.Count)" "INFO" Write-Log "GPO: $GPOName" "INFO" Write-Log "Saugos grupė: $SecurityGroupName" "INFO" jei ($DryRun) { Write-Log "[DRYRUN] sukurs GPO: $GPOName" "INFO" Write-Log "[DRYRUN] Sukurs saugos grupę: $SecurityGroupName" "INFO" Write-Log "[DRYRUN] Pridės $($TargetHostnames.Count) kompiuterius prie grupės "INFO" Write-Log "[DRYRUN] Įdiegtų vienkartinę suplanuotą užduotį, kad įgalintų saugiosios įkrovos naujinimą" "INFO" Write-Log "[DRYRUN] Susies GPO su: $TargetOU" "INFO" grąžinti @{ Success = $true KompiuteriaiĮtraukti = 0 DryRun = $true } } išbandykite { # Importuoti reikiamus modulius Import-Module GroupPolicy - ErrorAction Stop Import-Module ActiveDirectory - ErrorAction Stop } sugauti { Write-Log "Nepavyko importuoti būtinų modulių: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } # 1 veiksmas: sukurkite arba gaukite GPO $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue jei ($gpo) { Write-Log "GPO jau yra: $GPOName" "INFO" } dar { išbandykite { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Task Enable Remediation - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Sukurtas GPO: $GPOName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti GPO: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } } # 2 veiksmas: įdiekite suplanuotos užduoties XML GPO SYSVOL # Užduotis vykdo "PowerShell" komandą, kad įgalintų saugiosios įkrovos naujinimo užduotį išbandykite { $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks" jei (-not (testo kelias $sysvolPath)) { New-Item -ItemType katalogas -Kelias $sysvolPath -Force | Nu neapibrėžta reikšmė (Out-Null) } # PowerShell command to enable the Secure-Boot-Update task $enableCommand = 'schtasks.exe /Change /TN "\Microsoft\Windows\PI\Secure-Boot-Update" /ENABLE 2>$null; if ($LASTEXITCODE -ne 0) { Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI\" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue | Enable-ScheduledTask }' #Encode command for safe XML embedding $encodedCommand = [Konvertuoti]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableCommand)) $taskGuid = [guid]::NewGuid(). ToString("B"). Priseku() # GPP suplanuotos užduoties XML – tiesioginė užduotis, vykdoma vieną kartą $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"> <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image=""changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="$taskGuid" removePolicy="1" userContext="0"> <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\SYSTEM" logonType="S4U"> <Task version="1.3"> <RegistrationInfo> <aprašo>$TaskDescription</aprašo> </RegistrationInfo> <principals> <Principal id="Author"> <UserId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> parametrų>< <IdleSettings> <trukmė>PT5M</ trukmės> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>klaidingas</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <PradėtiKokausti>true</StartWhenAvailable> <AllowStartOnDemand>true</AllowStartOnDemand> <Įgalintas>teisingas</ įgalintas> <paslėptas>klaidingas</paslėptas> <ExecutionTimeLimit>PT1H</ExecutionTimeLimit> <priority>7</Priority> <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter> </ parametrų> veiksmų>< <Exec> <Command>powershell.exe</Command> <argumentai>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Arguments> </Exec> </Actions> </ užduočių> <apgyvendinimo įstaigos> </ImmediateTaskV2> </ScheduledTasks> "@ $gppTaskXml | Out-File -FilePath (prisijungimo kelio $sysvolPath "ScheduledTasks.xml") - Utf8 kodavimas -Force Write-Log "Įdiegta vienkartinė suplanuota užduotis GPO: $TaskName" "Gerai" } sugauti { Write-Log "Nepavyko įdiegti suplanuotos užduoties XML: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } # 3 veiksmas: saugos grupės kūrimas arba gavimas $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue jei (-not $group) { išbandykite { $group = New-ADGroup -Name $SecurityGroupName ' -GroupCategory Sauga ' -GroupScope DomainLocal ' -Aprašas "Kompiuteriai su išjungta saugiosios įkrovos naujinimo užduotimi – tikslinis taisymas" " -PassThru Write-Log "Sukurta saugos grupė: $SecurityGroupName" "Gerai" } sugauti { Write-Log "Nepavyko sukurti saugos grupės: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = $_. Exception.Message } } } dar { Write-Log "Saugos grupė yra: $SecurityGroupName" "INFO" } # 4 veiksmas: kompiuterių įtraukimas į saugos grupę $added = 0 $failed = 0 foreach ($hostname in $TargetHostnames) { išbandykite { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName - Members $computer -ErrorAction SilentlyContinue $added++ } sugauti { $failed++ Write-Log "Computer not found in AD: $hostname" "WARN" } } Write-Log "$added kompiuteriai įtraukti į saugos grupę ($failed nerasta AD)" "Gerai" # 5 veiksmas: konfigūruokite GPO saugos filtravimą išbandykite { Set-GPPermission -Name $GPOName -TargetName "Autentifikuoti vartotojai" -TargetType grupė -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop Write-Log "Konfigūruotas saugos filtravimas, skirtas: $SecurityGroupName" "Gerai" } sugauti { Write-Log "Nepavyko sukonfigūruoti saugos filtravimo: $($_. Exception.Message)" "WARN" } # 6 veiksmas: susieti GPO su OU jei ($TargetOU) { išbandykite { $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName -eq $GPOName } jei (-not $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Taip -ErrorAction Stop Write-Log "Susietas GPO su: $TargetOU" "Gerai" } dar { Write-Log "GPO jau susietas su paskirties OU" "INFO" } } sugauti { Write-Log "Nepavyko susieti GPO su OU: $($_. Exception.Message)" "ERROR" return @{ Success = $false; Klaida = "GPO saitas nepavyko: $($_. Exception.Message)" } } } dar { Write-Log "No TargetOU specified - GPO will be be manually linked" "WARN" } Write-Log "INFO" Write-Log "ENABLE TASK DEPLOYMENT COMPLETE" (ĮGALINTI UŽDUOTIES DIEGIMĄ BAIGTAS) "OK" Write-Log "Įrenginiai paleis įgalintą užduotį kito GPO atnaujinimo metu (gpupdate)" "INFO" Write-Log "Užduotis vykdoma vieną kartą kaip SISTEMA ir įgalina saugiosios įkrovos naujinimą" "INFO" Write-Log "INFO" grąžinti @{ Success = $true ComputersAdded = $added ComputersFailed = $failed GPOName = $GPOName SecurityGroup = $SecurityGroupName } }
# ============================================================================ # ĮGALINTI UŽDUOTĮ IŠJUNGTOSE ĮRENGINIUOSE #============================================================================ jei ($EnableTaskOnDisabled) { Write-Host Write-Host ("=" * 70) – priekinio planopalva geltona Write-Host " ENABLE TASK REMEDIATION - Fixing Disabled Scheduled Tasks" -ForegroundColor Yellow Write-Host ("=" * 70) – geltona priekinio plano spalva Write-Host # Rasti įrenginius, kuriuose išjungta užduotis iš agregavimo duomenų jei (-not $AggregationInputPath) { Write-Host "KLAIDA: -AggregationInputPath reikia identifikuoti įrenginius su išjungta užduotimi" -ForegroundColor Red Write-Host "Naudojimas: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <kelias> -ReportBasePath <kelias>" -ForegroundColor Gray išeiti iš 1 } Write-Host "Nuskaitomi įrenginiai, kuriuose išjungta saugiosios įkrovos naujinimo užduotis..." -ForegroundColor Cyan # Įkelti JSON failus ir rasti įrenginius su išjungta užduotimi $jsonFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_. Pavadinimas -notmatch "ScanHistory|RolloutState|RolloutPlan" } $disabledTaskDevices = @() foreach ($file in $jsonFiles) { išbandykite { $device = Get-Content $file. FullName – žalias | KonvertuotiFrom-Json jei ($device. SecureBootTaskEnabled - eq $false arba $device. SecureBootTaskStatus - eq išjungtas arba $device. SecureBootTaskStatus -eq 'NotFound') { # Įtraukti tik tuos įrenginius, kurie dar neatnaujinti (nėra 1808 įvykio) jei ([int]$device. Event1808Count -eq 0) { $disabledTaskDevices += $device. Hostname } } } sugauti { # Praleisti neleistinus failus } } $disabledTaskDevices = $disabledTaskDevices | Select-Object unikalus jei ($disabledTaskDevices.Count -eq 0) { Write-Host Write-Host "Nerasta įrenginių, kuriuose išjungta saugiosios įkrovos naujinimo užduotis." -Priekinio planopalva Žalia Write-Host "Visi įrenginiai turi įgalintą arba jau atnaujintą užduotį". -ForegroundColor Gray išeiti iš 0 } Write-Host Write-Host "Rasta $($disabledTaskDevices.Count) įrenginių su išjungta užduotimi:" -Priekinio planopalva geltona $disabledTaskDevices | Select-Object pirmosios 20 | ForEach-Object { Write-Host " - $_" -Priekinio planopalva Pilka } jei ($disabledTaskDevices.Count -gt 20) { Write-Host " ... ir $($disabledTaskDevices.Count – 20) daugiau" -Priekinio plano spalva Pilka } Write-Host # Visuotinis užduoties įgalinimas GPO $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun jei ($result. Pavyko) { Write-Host Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green Write-Host " Kompiuteriai, įtraukti į saugos grupę: $($result. ComputersAdded)" -ForegroundColor Cyan jei ($result. Kompiuteriai nepavyko -gt 0) { Write-Host " Ad kompiuterių nerasta: $($result. ComputersFailed)" -ForegroundColor Yellow } Write-Host Write-Host "KITI VEIKSMAI:" - Priekinio planopalva balta Write-Host " 1. Įrenginiai gaus GPO kitą kartą atnaujinus (gpupdate /force)" -ForegroundColor Gray Write-Host " 2. Vienkartinė užduotis įgalins saugiosios įkrovos naujinimą "-Priekinio planopalva pilka Write-Host " 3. Iš naujo paleiskite agregavimą, kad patikrintumėte, ar užduotis dabar įgalinta" -ForegroundColor Gray } dar { Write-Host Write-Host "NEPAVYKO: nepavyko įdiegti įgalinti užduoties GPO" -Priekinio planopalva raudona Write-Host "Klaida: $($result. Klaida)" -Priekinio planopalva raudona } išeiti iš 0 }
# ============================================================================ # MAIN ORCHESTRATION LOOP # ============================================================================
Write-Host "" Write-Host ("=" * 80) - Priekinio planopalvos žydra Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - CONTINUOUS DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 80) - Priekinio planopalvos žydra Write-Host
if ($DryRun) { Write-Host "[SAUSO VYKDYMO REŽIMAS]" - Priekinio planocolor purpurinė }
if ($UseWinCS) { Write-Host "[WinCS REŽIMAS]" - priekinio planopalva geltona Write-Host "Using WinCsFlags.exe instead of GPO/AvailableUpdatesPolicy" -ForegroundColor Yellow Write-Host "WinCS raktas: $WinCSKey" - Priekinio planopalva pilka Write-Host }
Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Įvesties kelias: $AggregationInputPath" "INFO" Write-Log "Ataskaitos kelias: $ReportBasePath" "INFO" jei ($UseWinCS) { Write-Log "Diegimo metodas: WinCS (WinCsFlags.exe /apply --key ""$WinCSKey")" "INFO" } dar { Write-Log "Diegimo metodas: GPO (AvailableUpdatesPolicy)" "INFO" }
# Resolve TargetOU - default to domain root for domain-wide coverage # Reikia tik GPO diegimo metodui ("WinCS" nereikia AD / GPO) jei (-not $UseWinCS -and -not $TargetOU) { išbandykite { # Išbandykite kelis būdus, kad gautumėte domeno DN $domainDN = $null # 1 metodas: Get-ADDomain (būtina RSAT-AD-PowerShell) išbandykite { Import-Module ActiveDirectory - ErrorAction Stop $domainDN = (Get-ADDomain -ErrorAction Stop). Išskirtinis vardas } sugauti { Write-Log "Get-ADDomain nepavyko: $($_. Exception.Message)" "WARN" } # 2 metodas: Naudokite RootDSE per ADSI jei (-not $domainDN) { išbandykite { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext.ToString() } sugauti { Write-Log "ADSI RootDSE nepavyko: $($_. Exception.Message)" "WARN" } } # 3 būdas: išanalizuoti iš kompiuterio domeno narystės jei (-not $domainDN) { išbandykite { $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain() $domainDN = "DC=" + ($domain. Name -replace '\.', ',DC=') } sugauti { Write-Log "GetComputerDomain nepavyko: $($_. Exception.Message)" "WARN" } } jei ($domainDN) { $TargetOU = $domainDN Write-Log "Target: Domain Root ($domainDN) - GPO will apply domain-wide via security group filtering" "INFO" } dar { Write-Log "Nepavyko nustatyti domeno DN – GPO bus sukurtas, bet NESUSIETAS!" "KLAIDA" Write-Log "Nurodykite -TargetOU parametrą arba susiekite GPO rankiniu būdu po sukūrimo" "KLAIDA" $TargetOU = $null } } sugauti { Write-Log "Nepavyko gauti domeno DN – GPO bus sukurtas, bet nesusietas. Saitas rankiniu būdu, jei reikia." "WARN" Write-Log "Klaida: $($_. Exception.Message)" "WARN" $TargetOU = $null } } dar { Write-Log "Target OU: $TargetOU" "INFO" }
Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Apklausos intervalas: $PollIntervalMinutes minutės" "INFO" jei ($LargeScaleMode) { Write-Log "LargeScaleMode enabled (paketo dydis: $ProcessingBatchSize, žurnalo pavyzdys: $DeviceLogSampleSize)" "INFO" }
# ============================================================================ # BŪTINŲJŲ SĄLYGŲ PATIKRA: patikrinkite, ar aptikimas įdiegtas ir veikia # ============================================================================
Write-Host "" Write-Log "Tikrinamos būtinosios sąlygos..." "INFO"
$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath jei (-not $detectionCheck.IsDeployed) { Write-Log $detectionCheck.Message "ERROR" Write-Host Write-Host "REQUIRED: Deploy detection infrastructure first:" -ForegroundColor Yellow Write-Host " 1. Vykdyti: Deploy-GPO-SecureBootCollection.ps1 -OUPath OU=...' -OutputPath \\server\SecureBootLogs$" -ForegroundColor Cyan Write-Host " 2. Palaukite, kol įrenginiai praneš (12–24 valandos)" -ForegroundColor Cyan Write-Host " 3. Iš naujo paleiskite šį orkestrą" -ForegroundColor Cyan Write-Host jei (-not $DryRun) { Grįžti } } dar { Write-Log $detectionCheck.Pranešimas "Gerai" }
# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Duomenų naujumas: $($freshness. TotalFiles) failai, $($freshness. FreshFiles) fresh (<24h), $($freshness. StaleFiles) pasenusi (>72h)" "INFO" jei ($freshness. Įspėjimas) { Write-Log $freshness. Įspėjimas "WARN" }
# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() jei ($AllowListPath arba $AllowADGroup) { $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup jei ($allowedHostnames.Count -gt 0) { Write-Log "AllowList: ONLY $($allowedHostnames.Count) įrenginiai bus laikomi diegiamais į "INFO" } dar { Write-Log "AllowList specified but no devices found - tai blokuos visus keitimus!" "WARN" } }
# Load VIP/exclusion list (BlockList) $excludedHostnames = @() jei ($ExclusionListPath arba $ExcludeADGroup) { $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup jei ($excludedHostnames.Count -gt 0) { Write-Log "VIP išimtis: $($excludedHostnames.Count) įrenginiai bus praleisti iš "INFO" } }
# Load state $rolloutState = Get-RolloutState $blockedBuckets = Get-BlockedBuckets $adminApproved = Get-AdminApproved $deviceHistory = Get-DeviceHistory
if ($rolloutState.Status -eq "NotStarted") { $rolloutState.Status = "InProgress" $rolloutState.StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Log "Pradedamas naujas diegimas" WAVE" }
Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Užblokuotos talpyklos: $($blockedBuckets.Count)" "INFO"
# Main loop - runs until all eligible devices are updated $iterationCount = 0 o ($true) { $iterationCount++ Write-Host " Write-Host (=" * 80) - Priekinio planopalva balta Write-Log "=== ITERATION $iterationCount ==" "WAVE" Write-Host ("=" * 80) – priekinio planopalva balta # 1 veiksmas: agregavimas Write-Log "1 veiksmas: vykdomas agregavimas..." "INFO" # Orchestrator visada pakartotinai naudoja vieną aplanką (LargeScaleMode), kad būtų išvengta disko išsipūtimo # Administratoriai, naudojantys rinkiklį, rankiniu būdu gauna laiko žyma pažymėtus aplankus momentinių taškų $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current" # Prieš agreguodami patikrinkite duomenų naujuumą $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Duomenų naujumas: $($freshness. FreshFiles)/$($freshness. TotalFiles) įrenginiai, apie kuriuos pranešta paskutinį 24 val. "INFO" jei ($freshness. Įspėjimas) { Write-Log $freshness. Įspėjimas "WARN" } $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1" $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json" $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json" if (Test-Path $aggregateScript) { jei (-not $DryRun) { # Orchestrator visada naudoja srautinio + papildantysis efektyvumo # Aggregator auto-elevates to PS7 if available for best performance $aggregateParams = @{ InputPath = $AggregationInputPath OutputPath = $aggregationPath StreamingMode = $true IncrementalMode = $true SkipReportIfUnchanged = $true ParallelThreads = 8 } # Pass rollout summary if it exists (for velocity/projection data) if (Test-Path $rolloutSummaryPath) { $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath } & $aggregateScript @aggregateParams # Show command to generate full HTML dashboard with device tables Write-Host Write-Host "Norėdami sugeneruoti visą HTML ataskaitų sritį su gamintojo / modelio lentelėmis, vykdyti:" -Priekinio planopalva Geltona Write-Host $aggregateScript -InputPath $AggregationInputPath -OutputPath '"$aggregationPath'" - Priekinio planopalva geltona Write-Host } dar { Write-Log "[DRYRUN] Vykdytų agregavimą" "INFO" # In DryRun, tiesiogiai naudokite esamus agregavimo duomenis iš ReportBasePath $aggregationPath = $ReportBasePath } } $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # 2 veiksmas: įkelti dabartinę įrenginio būseną Write-Log "2 veiksmas: įkeliamas įrenginio būsena..." "INFO" $notUptodateCsv = Get-ChildItem -Path $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue | Where-Object { $_. Pavadinimas -notlike "*Buckets*" } | Sort-Object LastWriteTime -Descending | Select-Object – pirmas 1 jei (-not $notUptodateCsv -and -not $DryRun) { Write-Log "Agregavimo duomenų nerasta. Laukiama..." "WARN" Start-Sleep –sekundės ($PollIntervalMinutes * 60) Toliau } $notUpdatedDevices = jei ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } dar { @() } Write-Log "Įrenginiai neatnaujinami: $($notUpdatedDevices.Count)" "INFO" $notUpdatedIndexes = Get-NotUpdatedIndexes - Įrenginiai $notUpdatedDevices # 3 veiksmas: atnaujinkite įrenginio istoriją (sekimas pagal pagrindinio kompiuterio vardą) Write-Log "3 veiksmas: įrenginio retrospektyvos naujinimas..." "INFO" Update-DeviceHistory – CurrentDevices $notUpdatedDevices – DeviceHistory $deviceHistory Save-DeviceHistory – retrospektyvos $deviceHistory # 4 veiksmas: patikrinkite, ar nėra užblokuotų talpyklų (nepasiekiami įrenginiai) $existingBlockedCount = $blockedBuckets.Count Write-Log "4 veiksmas: tikrinama, ar yra užblokuotų talpyklų (įrenginių tikrinimas praėjus laukimo laikotarpiui)..." "INFO" jei ($existingBlockedCount -gt 0) { Write-Log "Šiuo metu užblokuotos talpyklos iš ankstesnių paleidimų: $existingBlockedCount" "INFO" } jei ($adminApproved.Count -gt 0) { Write-Log "Administratorius patvirtintos talpyklos (nebus iš naujo užblokuotos): $($adminApproved.Count)" "INFO" } $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState - BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun jei ($newlyBlocked.Count -gt 0) { Save-BlockedBuckets – užblokuotas $blockedBuckets Write-Log "Naujai užblokuotos talpyklos (ši iteracija): $($newlyBlocked.Count)" "UŽBLOKUOTA" } # 4b veiksmas: automatiškai atblokuokite talpyklas, kuriose atnaujinti įrenginiai $autoUnblocked = Update-AutoUnblockedBuckets - BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize jei ($autoUnblocked.Count -gt 0) { Save-BlockedBuckets – užblokuotas $blockedBuckets Write-Log "Auto-unblocked buckets (devices updated): $($autoUnblocked.Count)" "OK" } # 5 veiksmas: apskaičiuokite likusius reikalavimus atitinkančius įrenginius $eligibleCount = 0 foreach ($device in $notUpdatedDevices) { $bucketKey = Get-BucketKey $device jei (-not $blockedBuckets.Contains($bucketKey)) { $eligibleCount++ } } Write-Log "Liko reikalavimus atitinkančių įrenginių: $eligibleCount" "INFO" Write-Log "Užblokuotos talpyklos: $($blockedBuckets.Count)" "INFO" # 6 veiksmas: patikrinimas baigtas jei ($eligibleCount -eq 0) { Write-Log "ROLLOUT COMPLETE – atnaujinti visi reikalavimus atitinkantys įrenginiai!" "GERAI" $rolloutState.Status = "Completed" $rolloutState.CompletedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" Save-RolloutState – būsenos $rolloutState Pertraukos } # 6 veiksmas: sukurkite ir įdiekite kitą bangą Write-Log "Step 6: Generating rollout wave..." "INFO" $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames # Patikrinkite, ar turime diegtinų įrenginių ($waveDevices gali būti $null, tuščias arba su faktiniais įrenginiais) $hasDevices = $waveDevices ir @($waveDevices | Where-Object { $_ }). Skaičiavimas -gt 0 jei ($hasDevices) { # Tik didėjančių bangų skaičius, kai iš tikrųjų turime diegtini įrenginiai $rolloutState.CurrentWave++ Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Count) devices" "WAVE" # Visuotinai diegti GPO naudojant funkciją Inlined $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)" $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)" $hostnames = @($waveDevices | ForEach-Object { jei ($_. Pagrindinio kompiuterio vardas) { $_. Hostname } elseif ($_. HostName) { $_. HostName } else { $null } } | Where-Object { $_ }) # Pagrindinio kompiuterio vardų failo įrašymas nuorodai / auditui $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt" $hostnames | Out-File $hostnamesFile – utf8 kodavimas # Tikrinimas, ar turime pagrindinių kompiuterių, kuriuos reikia diegti jei ($hostnames. Skaičius -eq 0) { Write-Log "Nėra leistinų pagrindinio kompiuterio vardų bangoje $($rolloutState.CurrentWave) – įrenginiuose gali trūkti hostname ypatybės" WARN Write-Log "Šios bangos diegimas praleidžiamas – patikrinkite įrenginio duomenis" WARN # Vis dar palaukite prieš kitą iteraciją jei (-not $DryRun) { Write-Log "Miega $PollIntervalMinutes min. prieš pakartodami..." "INFO" Start-Sleep – sekundės ($PollIntervalMinutes * 60) } Toliau } Write-Log "Diegimas į $($hostnames. Count) hostnames in Wave $($rolloutState.CurrentWave)" "INFO" # Diegimas naudojant "WinCS" arba GPO metodą, pagrįstą parametru "UseWinCS" jei ($UseWinCS) { # WinCS metodas: Sukurti GPO su suplanuota užduotimi paleisti WinCsFlags.exe kaip SYSTEM kiekviename galinį punktą Write-Log "Using WinCS deployment method (Key: $WinCSKey)" "WAVE" $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames ' -WinCSKey $WinCSKey ' -WavePrefix $WavePrefix ' -WaveNumber $rolloutState.CurrentWave ' -TargetOU $TargetOU ' -DryRun:$DryRun jei (-not $wincsResult.Success) { Write-Log "WinCS" įdiegti nepavyko – pritaikyta: $($wincsResult.Applied), nepavyko: $($wincsResult.Failed)" "WARN" } dar { Write-Log "WinCS" diegimas sėkmingas – taikoma: $($wincsResult.Applied), praleista: $($wincsResult.Skipped)" "Gerai" } # Įrašyti "WinCS" rezultatus auditui $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json" $wincsResult | ConvertTo-Json gylis 5 | Out-File $wincsResultFile – utf8 kodavimas } dar { # GPO metodas: sukurkite GPO naudodami "AvailableUpdatesPolicy" registro parametrą $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun jei (-not $gpoResult) { Write-Log "GPO diegimas nepavyko – bandys pakartoti paskesnę iteraciją" "KLAIDA" } } # Įrašų banga būsenoje $waveRecord = @{ WaveNumber = $rolloutState.CurrentWave StartedAt = Get-Date -Formatas "mmmm-MM-dd HH:mm:ss" DeviceCount = @($waveDevices). Skaičius Įrenginiai = @($waveDevices | ForEach-Object { @{ Hostname = if ($_. Pagrindinio kompiuterio vardas) { $_. Hostname } elseif ($_. HostName) { $_. HostName } dar { $null } BucketKey = Get-BucketKey $_ } }) } # Įsitikinkite, kad "WaveHistory" visada yra masyvas prieš pridedant (neleidžia maišos suliejimo problemų) $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord) $rolloutState.TotalDevicesTargeted += @($waveDevices). Skaičius Save-RolloutState būsenos $rolloutState Write-Log "Wave $($rolloutState.CurrentWave) įdiegta. Laukiama $PollIntervalMinutes minučių..." "GERAI" } dar { # Rodyti įdiegtų įrenginių, laukiančių naujinimų, būseną Write-Log "INFO" Write-Log "========== VISUOSE ĮDIEGTUOSE ĮRENGINIUOSE – LAUKIAMA BŪSENOS ==========" "INFO" # Gauti visus įdiegtus įrenginius iš bangų istorijos $allDeployedLookup = @{} foreach ($wave in $rolloutState.WaveHistory) { ($device $wave. Įrenginiai) { jei ($device. Pagrindinio kompiuterio vardas) { $allDeployedLookup[$device. Pagrindinio kompiuterio vardas] = @{ Pagrindinio kompiuterio vardas = $device. Hostname BucketKey = $device. "BucketKey" DeployedAt = $wave. Darbo pradžia WaveNumber = $wave. "WaveNumber" } } } } $allDeployedDevices = @($allDeployedLookup.Values) jei ($allDeployedDevices.Count -gt 0) { # Raskite, kurie įdiegti įrenginiai vis dar laukia (sąraše NotUpdated) $stillPendingCount = 0 $noLongerPendingCount = 0 $pendingSample = @() foreach ($deployed in $allDeployedDevices) { jei ($notUpdatedIndexes.HostSet.Contains($deployed. Pagrindinio kompiuterio vardas)) { $stillPendingCount++ jei ($pendingSample.Count -lt $DeviceLogSampleSize) { $pendingSample += $deployed. Hostname } } dar { $noLongerPendingCount++ } } # Gauti faktinius atnaujintus skaičius iš agregavimo – atskirti įvykį 1808 ir UEFICA2023Status $summaryCsv = Get-ChildItem -Path $aggregationPath -Filter "*Summary*.csv" | Sort-Object LastWriteTime -Descending | Select-Object 1 pirmas $actualUpdated = 0 $totalDevicesFromSummary = 0 $event 1808Count = 0 $uefiStatusUpdated = 0 $needsRebootSample = @() jei ($summaryCsv) { $summary = Import-Csv $summaryCsv.FullName | Select-Object – pirmas 1 jei ($summary. Atnaujinta) { $actualUpdated = [sveikasis skaičius]$summary. Atnaujinta } jei ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices } } # Apskaičiuokite greitį pagal bangų istoriją (įrenginiai atnaujinti per dieną) $devicesPerDay = 0 jei ($rolloutState.StartedAt -and $actualUpdated -gt 0) { $startDate = [datetime]::P arse($rolloutState.StartedAt) $daysElapsed = ((Get-Date) - $startDate). TotalDays jei ($daysElapsed -gt 0) { $devicesPerDay = $actualUpdated / $daysElapsed } } # Įrašyti diegimo suvestinę su savaitgalio projekcijomis # Vientisumui užtikrinti naudokite rinkiklio NotUptodate skaičių (neįtraukia SB OFF įrenginių) $notUpdatedCount = jei ($summary ir $summary. NotUptodate) { [int]$summary. NotUptodate } dar { $totalDevicesFromSummary - $actualUpdated } Save-RolloutSummary – būsenos $rolloutState " -TotalDevices $totalDevicesFromSummary ' -UpdatedDevices $actualUpdated ' -NotUpdatedDevices $notUpdatedCount " -DevicesPerDay $devicesPerDay # Patikrinkite neapdorotus duomenis įrenginiams, kuriuose naudojama UEFICA2023Status=Atnaujinta, bet nėra 1808 įvykio (reikia perkrauti) $dataFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue $totalDataFiles = @($dataFiles). Skaičius $batchSize = [Matematika]::Maks.(500, $ProcessingBatchSize) jei ($LargeScaleMode) { $batchSize = [Matematika]::Maks.(2000, $ProcessingBatchSize) }
if ($totalDataFiles -gt 0) { for ($idx = 0; $idx -lt $totalDataFiles; $idx += $batchSize) { $end = [Matematika]:Min($idx + $batchSize - 1, $totalDataFiles - 1) $batchFiles = $dataFiles[$idx.. $end]
foreach ($file in $batchFiles) { išbandykite { $deviceData = Get-Content $file. FullName – žalias | KonvertuotiFrom-Json $hostname = $deviceData.Hostname jei (-not $hostname) { continue } $has 1808 = [int]$deviceData.Event1808Count -gt 0 $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Atnaujinta" jei ($has 1808) { $event 1808Count++ } elseif ($hasUefiUpdated) { $uefiStatusUpdated++ jei ($needsRebootSample.Count -lt $DeviceLogSampleSize) { $needsRebootSample += $hostname } } } sugauti { } }
Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{ Event1808Count = $event 1808Count UEFIUpdatedAwaitingReboot = $uefiStatusUpdated } } } Write-Log "Total deployed: $($allDeployedDevices.Count)" "INFO" Write-Log "Atnaujinta (1808 įvykis patvirtintas): $event 1808Count" "Gerai" jei ($uefiStatusUpdated -gt 0) { Write-Log "Updated (UEFICA2023Status=Updated, awaiting reboot): $uefiStatusUpdated" "OK" $rebootSuffix = jei ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated – $DeviceLogSampleSize) daugiau)" } dar { "" } Write-Log " Įrenginiai, kuriems reikia paleisti iš naujo 1808 įvykiui (pavyzdys): $($needsRebootSample -join ', ')$rebootSuffix" "INFO" Write-Log " Šie įrenginiai praneš apie įvykį 1808 po kito paleidimo iš naujo" "INFO" } Write-Log "Nebėra laukiama: $noLongerPendingCount (įskaitant "SecureBoot OFF", trūkstamus įrenginius)" "INFO" Write-Log "Laukiama būsenos: $stillPendingCount" "INFO" jei ($stillPendingCount -gt 0) { $pendingSuffix = if ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount – $DeviceLogSampleSize) daugiau)" } dar { "" } Write-Log "Laukiantys įrenginiai (pavyzdys): $($pendingSample -join ', ')$pendingSuffix" "WARN" } } dar { Write-Log "Dar neįdiegta jokių įrenginių" "INFO" } Write-Log "================================================================" "INFO" Write-Log "INFO" } # Palaukite prieš kitą iteraciją jei (-not $DryRun) { Write-Log "Miega $PollIntervalMinutes minutėms..." "INFO" Start-Sleep – sekundės ($PollIntervalMinutes * 60) } dar { Write-Log "[DRYRUN] Palauks $PollIntervalMinutes minučių" "INFO" break # Exit after one iteration in dry run } }
# ============================================================================ # GALUTINĖ SUVESTINĖ # ============================================================================
Write-Host "" Write-Host (=" * 80) - Priekinio planopalva žalia Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -ForegroundColor Green Write-Host (=" * 80) - Priekinio planopalva žalia Write-Host
$finalState = Get-RolloutState $finalBlocked = Get-BlockedBuckets
Write-Host "Status: $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Total Waves: $($finalState.CurrentWave)" Write-Host "Tiksliniai įrenginiai: $($finalState.TotalDevicesTargeted)" Write-Host "Užblokuotos talpyklos: $($finalBlocked.Count)" -ForegroundColor $(if ($finalBlocked.Count -gt 0) { "Red" } dar { "Žalias" }) Write-Host "Įrenginiai susekti: $($deviceHistory.Count)" -Priekinio planopalva pilka Write-Host ""
if ($finalBlocked.Count -gt 0) { Write-Host "BLOCKED BUCKETS (require manual review):" -ForegroundColor Red foreach ($key in $finalBlocked.Keys) { $info = $finalBlocked[$key] Write-Host " – $key" – priekinio plano spalva raudona Write-Host " Priežastis: $($info. Reason)" -Priekinio planopalva pilka } Write-Host Write-Host "Blocked buckets file: $blockedBucketsPath" -ForegroundColor Yellow }
Write-Host "" Write-Host "State files:" -ForegroundColor Cyan Write-Host " Rollout State: $rolloutStatePath" Write-Host " Blocked Buckets: $blockedBucketsPath" Write-Host " Įrenginio retrospektyva: $deviceHistoryPath" Write-Host ""