Kopirajte i nalepite ovu uzorak skripte i izmenite po potrebi za okruženje:
<# . SINOPSIS Kontinuirani orkestar za primenu bezbednog pokretanja koji se pokreće dok se primena ne dovrši.
.DESCRIPTION Ova skripta pruža kompletnu automatizaciju za potpunu automatizaciju za objavljivanje certifikata bezbednog pokretanja: 1. Generiše talase primene na osnovu podataka o agregaciji 2. Kreira AD grupe i GPO za svaki talas 3. Monitori za ispravke uređaja (događaj 1808) 4. Otkriva blokirane kontejnere (uređaji koji nisu dostupni) 5. Automatski prelazi na sledeći talas 6. Pokreće se dok se SVI uređaji koji ispunjavaju uslove ne ažuriraju Kriterijumi dovršavanja: - Nema preostalih uređaja: radnja obavezna, visoki stepen pouzdanosti, posmatranje, privremeno pauzirano - Izvan opsega (po dizajnu): Nije podržano, bezbedno pokretanje je onemogućeno - Trči neprekidno dok se ne završi - bez ograničenja proizvoljnog talasa Strategija primene: - VISOKI STEPEN POUZDANOSTI: Svi uređaji u prvom talasu (bezbedni) - POTREBNA JE RADNJA: Napredni dupli (1→2→4→8...) Blokiranje logike: - Nakon MaxWaitHours, orchestrator uređaja koji nisu ažurirani - Ako uređaj nije dostupan (ping ne uspe) → je blokiran na istragu - Ako je uređaj REACHABLE, ali nije ažuriran → da čeka (možda će biti potrebno ponovno pokretanje sistema) - Blokirane kontejnere se isključuju dok ih administrator ne deblokira Automatsko deblokiranje: - Ako se uređaj u blokiranoj kontejnera kasnije prikaže kao ažuriran (događaj 1808), kontejner se automatski deblokira i objavljuju - Ovo rukuje uređajima koji su privremeno bili van mreže, ali su se vratili Praćenje uređaja: - Prati uređaje po imenu hosta (pretpostavlja da se imena ne menjaju tokom primene) - Napomena: JSON kolekcija ne sadrži jedinstveni ID računara; dodajte jedan za bolje praćenje
.PARAMETER AggregationInputPath Putanja do raw JSON podataka uređaja (iz otkrivanja skripte)
.PARAMETER ReportBasePath Osnovna putanja za izveštaje o agregaciji
.PARAMETER TargetOU Specifično ime OU za povezivanje GPI-ja.Opcionalno – ako nije navedeno, GPO je povezan sa osnovnim domenom za pokrivenost na nivou domena.Filtriranje bezbednosne grupe obezbeđuje da samo ciljani uređaji prime smernice.
.PARAMETER MaxWaitHours Časovi čekanja na ažuriranje uređaja pre provere dostupnosti.Nakon ovog vremena, uređaji koji nisu ažurirani se ažuriraju.Nedostupni uređaji dovode do blokiranja kontejnera.Podrazumevano: 72 (3 dana)
.PARAMETER PollIntervalMinutes Minuti između provera statusa. Podrazumevano: 1440 (1 dan)
.PARAMETER AllowListPath Putanja do datoteke koja sadrži imena hosta do DOZVOLI objavljivanje (ciljana primena).Podržava .txt (ime hosta po redu) ili .csv (sa kolonom "Ime hosta/ime_računara").Kada se navede, samo ovi uređaji će biti uključeni u primenu.Lista blokova se i dalje primenjuje nakon funkcije AllowList.
.PARAMETER AllowADGroup Ime AD bezbednosne grupe koja sadrži računarske naloge za DOZVOLJAVANJE.Primer: "SecureBoot-Pilot-Computers" ili "Wave1-devices" Kada se navede, samo uređaji u ovoj grupi će biti uključeni u primenu.Kombinujte sa allowListPath za ciljanje datoteka i AD.
.PARAMETER ExclusionListPath Putanja do datoteke koja sadrži imena hosta za IZUZMI IZ OBJAVLJIVANJA (VIP/izvršni uređaji).Podržava .txt (ime hosta po redu) ili .csv (sa kolonom "Ime hosta/ime_računara").Ovi uređaji nikada neće biti uključeni u bilo koji talas primene.Lista blokova se primenjuje AFTER AllowList filtriranje. . PARAMETER ExcludeADGroup Ime AD bezbednosne grupe koja sadrži računarske naloge koje treba izuzeti.Primer: "VIP-Computers" ili "Executive-devices" Kombinujte sa izrazom ExclusionListPath za i datoteku i izuzetke zasnovane na AD-u.
.PARAMETER UseWinCS Koristite WinCS (Windows Configuration System) umesto GPO/AvailableUpdatesPolicy.WinCS primenjuje omogućavanje bezbednog pokretanja tako što WinCsFlags.exe direktno na svakoj krajnjim tačkama.WinCsFlags.exe se u okviru konteksta SISTEMA putem planiranog zadatka.Ovaj metod je koristan za: - Brže objavljivanje (neposredni efekat naspram čekanja na GPO obradu) - uređaji koji nisu pridruženi domenu - Okruženja bez AD/GPO infrastrukture Referenca: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe
.PARAMETER WinCSKey WinCS ključ koji treba koristiti za omogućavanje bezbednog pokretanja.Podrazumevano: F33E0C8E002 Ovaj ključ odgovara konfiguraciji primene bezbednog pokretanja. . PARAMETER DryRun Prikažite šta bi bilo urađeno bez unošenja promena
.PARAMETER ListBlockedBuckets Prikaži sve trenutno blokirane kontejnere i izađi
.PARAMETER UnblockBucket Deblokiranje određenog kontejnera pomoću tastera i izlaza
.PARAMETER UnblockAll Deblokiranje svih kontejnera i izlaz
.PARAMETER EnableTaskOnDisabled Primenite Enable-SecureBootUpdateTask.ps1 na sve uređaje sa onemogućenim planiranim zadatkom.Kreira GPO sa jednokratnim planiranim zadatkom koji pokreće opciju Omogući skriptu sa opcijom -Quiet.Ovo je korisno za popravljanje uređaja na koje je onemogućen zadatak bezbednog pokretanja sistema.
.EXAMPLE .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath "E:\SecureBootReports" ' -TargetOU "OU=Workstations,DC=contoso,DC=com"
.EXAMPLE # Nabrajanje blokiranih kontejnera .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets
.EXAMPLE # Deblokiranje određene kontejnera .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"
.EXAMPLE # Isključite VIP uređaje iz primene pomoću tekstualne datoteke .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath "E:\SecureBootReports" ' -ExclusionListPath "C:\Admin\VIP-Devices.txt"
.EXAMPLE # Isključite uređaje u AD bezbednosnoj grupi (npr. izvršni laptopovi) .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath "E:\SecureBootReports" ' -ExcludeADGroup "VIP-Computers"
.EXAMPLE # Koristite WinCS (Windows Configuration System) umesto GPO/AvailableUpdatesPolicy # WinCsFlags.exe se pokreće u okviru konteksta SISTEMA za svaku krajnje tačke putem planiranog zadatka .\Start-SecureBootRolloutOrchestrator.ps1 ' -AggregationInputPath "\\server\SecureBootLogs$\Json" ' -ReportBasePath "E:\SecureBootReports" ' - KoristiteWinCS ' -WinCSKey "F33E0C8E002" #>
[CmdletBinding()] param( [Parameter(Obavezno = $false)] [niska]$AggregationInputPath, [Parameter(Obavezno = $false)] [niska]$ReportBasePath, [Parameter(Obavezno = $false)] [niska]$TargetOU, [Parameter(Obavezno = $false)] [string]$WavePrefix = "SecureBoot-Rollout", [Parameter(Obavezno = $false)] [int]$MaxWaitHours = 72, [Parameter(Obavezno = $false)] [int]$PollIntervalMinutes = 1440,
[Parameter(Mandatory = $false)] [int]$ProcessingBatchSize = 5000,
[Parameter(Mandatory = $false)] [int]$DeviceLogSampleSize = 25,
[Parameter(Mandatory = $false)] [switch]$LargeScaleMode, # ============================================================================ # AllowList / BlockList parameters # ============================================================================ # AllowList = Samo uključi ove uređaje (ciljana primena) # BlockList = Isključi ove uređaje (oni se nikada neće objavljivati) # Redosled obrade: Prvo allowList (ako je naveden), a zatim i "Lista blokiranja" [Parameter(Obavezno = $false)] [niska]$AllowListPath, [Parameter(Obavezno = $false)] [niska]$AllowADGroup, [Parameter(Obavezno = $false)] [niska]$ExclusionListPath, [Parameter(Obavezno = $false)] [niska]$ExcludeADGroup, # ============================================================================ # WinCS (Windows Configuration System) parametri # ============================================================================ # WinCS je alternativa za AvailableUpdatesPolicy GPO primenu. # Ona koristi WinCsFlags.exe na svakoj krajnjim tačkama za omogućavanje primene bezbednog pokretanja.# WinCsFlags.exe se pokreće u kontekstu SISTEMA na krajnjim tačkama.# Referenca: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe [Parameter(Obavezno = $false)] [switch]$UseWinCS, [Parameter(Obavezno = $false)] [niska]$WinCSKey = "F33E0C8E002", [Parameter(Obavezno = $false)] [switch]$DryRun, [Parameter(Obavezno = $false)] [switch]$ListBlockedBuckets, [Parameter(Obavezno = $false)] [niska]$UnblockBucket, [Parameter(Obavezno = $false)] [switch]$UnblockAll, [Parameter(Obavezno = $false)] [switch]$EnableTaskOnDisabled )
$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Uzorci primene i nadgledanja"
# ============================================================================ # PROVERA VALJANOSTI ZAVISNOSTI # ============================================================================
function Test-ScriptDependencies { param( [Parameter(Obavezno = $true)] [niska]$ScriptDirectory, [Parameter(Obavezno = $true)] [niska[]]$RequiredScripts ) $missingScripts = @() foreach ($script in $RequiredScripts) { $scriptPath = Join-Path $ScriptDirectory $script ako (-ne (probna putanja $scriptPath)) { $missingScripts += $script } } if ($missingScripts.Count -gt 0) { Write-Host "" Write-Host ("=" * 70) -Boja prednjeg plana Crvena boja Write-Host " ZAVISNI ELEMENTI KOJI NEDOSTAJU" -Boja prednjeg plana Crvena boja Write-Host ("=" * 70) -Boja prednjeg plana Crvena boja Write-Host "" Write-Host "Sledeće zahtevane skripte nisu pronađene:" - Boja prednjeg plana žuta foreach ($script in $missingScripts) { Write-Host " - $script" - Boja prednjeg plana Bela } Write-Host "" Write-Host "Preuzmite najnovije skripte od:" - Prednji planColor Cyan Write-Host " URL: $DownloadUrl" - Boja prednjeg plana bela Write-Host " Idi na: '$DownloadSubPage'" -Boja prednjeg plana belo Write-Host "" Write-Host "Izdvoj sve skripte u isti direktorijum i ponovo pokreni". -Boja prednjeg plana žuta Write-Host "" povratna $false } povratna $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)) { izlaz 1 }
# ============================================================================ # VALIDACIJA PARAMETRA # ============================================================================
# Admin commands only need ReportBasePath $isAdminCommand = $ListBlockedBuckets - ili $UnblockBucket - ili $UnblockAll - ili $EnableTaskOnDisabled
if (-not $ReportBasePath) { Write-Host "GREŠKA: -ReportBasePath je obavezan". -Crvena boja prednjeg plana izlaz 1 }
if (-not $isAdminCommand -and -not $AggregationInputPath) { Write-Host "ERROR: -AggregationInputPath je potreban za primenu (nije potreban za -ListBlockedBuckets, -UnblockBucket, -UnblockAll)" -Prednji planColor Crvena izlaz 1 }
# ============================================================================ OTKRIVANJE # GPO - PROVERI DA LI POSTOJE GPO ZA OTKRIVANJE # ============================================================================
if (-not $isAdminCommand -and -not $DryRun) { $CollectionGPOName = "SecureBoot-EventCollection" # Proverite da li je modul "GroupPolicy" dostupan if (Get-Module -ListAvailable -Name GroupPolicy) { Import-Module GroupPolicy -ErrorAction SilentlyContinue Write-Host "Provera da li postoji GPO za otkrivanje..." -Boja prednjeg plana žuta pokušajte { # Proverite da li GPO postoji $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue ako ($existingGpo) { Write-Host " Pronađen je GPO za otkrivanje: $CollectionGPOName" - Zelena boja prednjeg plana } još { Write-Host "" Write-Host ("=" * 70) -Boja prednjeg plana žuta Write-Host " UPOZORENJE: GPO NIJE PRONAĐEN" - Boja prednjeg plana žuta Write-Host ("=" * 70) -Boja prednjeg plana žuta Write-Host "" Write-Host "GPO za otkrivanje '$CollectionGPOName' nije pronađen". -Boja prednjeg plana žuta Write-Host "Bez ovog GPO-a, neće biti prikupljeni nikakvi podaci uređaja". -Boja prednjeg plana – žuta boja Write-Host "" Write-Host "Da biste primenili GPO za otkrivanje, pokrenite:" - Boja prednjeg plana cijan Write-Host " .\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domain> -AutoDetectOU" -ForegroundColor White Write-Host "" Write-Host "Želite li ipak da nastavite? (Y/N)" -Boja prednjeg plana žuta $response = Host za čitanje ako ($response -nije podudaranje '^[Yy]') { Write-Host "Obustavlja se. Prvo primeni GPO za otkrivanje." - Boja prednjeg plana crvene boje izlaz 1 } } } hvatanje { Write-Host " Nije moguće proveriti GPO: $($_. Exception.Message)" -Boja prednjeg plana žuta } } još { Write-Host " GroupPolicy modul nije dostupan – preskače se GPO provera" - Boja prednjeg plana siva } Write-Host "" }
# ============================================================================ # PUTANJE DATOTEKE STANJA # ============================================================================
$stateDir = Join-Path $ReportBasePath "RolloutState" if (-not (Test-Path $stateDir)) { New-Item -ItemType direktorijum -putanja $stateDir -Force | Bez vrednosti }
$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 KOMPATIBILNOST: ConvertTo-Hashtable # ============================================================================ # ConvertFrom-Json -AsHashtable je samo PS7+ . To obezbeđuje kompatibilnost.
function ConvertTo-Hashtable { param( [Parameter(ValueFromPipeline = $true)] $InputObject ) obrada { if ($null -eq $InputObject) { return @{} } if ($InputObject -is [System.Collections.IDictionary]) { vraća $InputObject } if ($InputObject -is [PSCustomObject]) { # Koristite [uređeno] za dosledno naručivanje ključeva i bezbedno dupliranje rukovanja $hash = [poručeno]@{} foreach ($prop in $InputObject.PSObject.Properties) { # Indeksirani dodeljivanje bezbedno rukuje duplikatima tako što zamenjuje $hash[$prop. Ime] = ConvertTo-Hashtable $prop. Vrednost } povratna $hash } if ($InputObject -is [System.Collections.IEnumerable] -$InputObject -isot [string]) { return @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ }) } return $InputObject } }
# ============================================================================ # ADMINISTRATORSKE KOMANDE: Lista/deblokiranje kontejnera # ============================================================================
if ($ListBlockedBuckets) { Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana žuta Write-Host " BLOKIRANE KONTEJNERA" - Boja prednjeg plana Žuta Write-Host ("=" * 80) -Boja prednjeg plana žuta Write-Host "" if (probna putanja $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuj u heštable ako ($blocked. Broj -eq 0) { Write-Host "Nema blokiranih kontejnera". -Boja prednjeg plana – zelena } još { Write-Host "Ukupno blokirano: $($blocked. Broj)" - Boja prednjeg plana crvena Write-Host "" ($key u $blocked. Tasteri) { $info = $blocked[$key] Write-Host "Kontejner: $key" - Boja prednjeg plana Crvena Write-Host " Blokirano u: $($info. BlockedAt)" -Boja prednjeg plana siva Write-Host" Razlog: $($info. Razlog)" - Boja prednjeg plana siva Write-Host " Neuspeli uređaj: $($info. FailedDevice)" -Boja prednjeg plana siva Write-Host " Poslednji put prijavljen: $($info. LastReported)" -Boja prednjeg plana siva Write-Host " Talas: $($info. WaveNumber)" -Boja prednjeg plana siva Write-Host " Uređaji u kontejnera: $($info. DevicesInBucket)" - Boja prednjeg plana siva Write-Host "" } Write-Host "Deblokiranje kontejnera:" Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockBucket 'BUCKET_KEY'" -ForegroundColor Cyan Write-Host "" Write-Host "Deblokiranje svega:" Write-Host " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '$ReportBasePath' -UnblockAll" -ForegroundColor Cyan } } još { Write-Host "Nije pronađena nijedna datoteka blokiranih kontejnera". -Zelena boja prednjeg plana } Write-Host "" izađi 0 }
if ($UnblockBucket) { Write-Host "" if (probna putanja $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuj u heštable ako ($blocked. Contains($UnblockBucket)) { $blocked. Ukloni($UnblockBucket) $blocked | ConvertTo-Json - Dubina 10 | Out-File $blockedBucketsPath - šifrovanje UTF8 - force # Dodajte na listu koju je odobrio administrator da biste sprečili ponovno blokiranje $adminApproved = @{} if (probna putanja $adminApprovedPath) { $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuj u heštable } $adminApproved[$UnblockBucket] = @{ ApprovedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Odobrio = $env:USERNAME } $adminApproved | ConvertTo-Json - Dubina 10 | Out-File $adminApprovedPath - Šifrovanje UTF8 - force Write-Host "Deblokirana kontejner: $UnblockBucket" - Zelena boja prednjeg plana Write-Host "Dodato na listu koju je odobrio administrator (neće biti automatski blokirano)" -Prednji planColor Cyan } još { Write-Host "Kontejner nije pronađen: $UnblockBucket" - Boja prednjeg plana žuta Write-Host "Dostupne kontejnere:" -Boja prednjeg plana siva $blocked. Tasteri | ForEach-Object { Write-Host " $_" - Boja prednjeg plana Siva } } } još { Write-Host "Nije pronađena nijedna datoteka blokiranih kontejnera". -Boja prednjeg plana žuta } Write-Host "" izađi 0 }
if ($UnblockAll) { Write-Host "" if (probna putanja $blockedBucketsPath) { $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuj u heštable $count = $blocked. Raиuna @{} | ConvertTo-Json | Out-File $blockedBucketsPath - šifrovanje UTF8 - force Write-Host "Deblokirane $count kontejnera". - Boja prednjeg plana – zelena } još { Write-Host "Nije pronađena nijedna blokirana datoteka kontejnera". -Boja prednjeg plana žuta } Write-Host "" izađi 0 }
# ============================================================================ # HELPER FUNKCIJE # ============================================================================
function Get-RolloutState { if (probna putanja $rolloutStatePath) { pokušajte { $loaded = Get-Content $rolloutStatePath -Raw | ConvertFrom-Json | Konvertuj u heštable # Proveri valjanost potrebnih svojstava postoje if ($null -eq $loaded. CurrentWave) { throw "Invalid state file - missing CurrentWave" } # Uverite se da je WaveHistory uvek niz (popravlja PS5.1 JSON deserijalizaciju) if ($null -eq $loaded. WaveHistory) { $loaded. WaveHistory = @() } elseif ($loaded. WaveHistory - nije [niz]) { $loaded. WaveHistory = @($loaded. Talasasta istorija) } povratni $loaded } hvatanje { Write-Log "Otkrivena RolloutState.json: $($_. Exception.Message)" "WARN" Write-Log "Pravljenje rezervne okviru oštećene datoteke i započinjanje iz početka" "WARN" $backupPath = "$rolloutStatePath.oštećen.$(Get-Date -Format 'yyyyMMdd-HHmmss')" Move-Item $rolloutStatePath $backupPath -Force -ErrorAction SilentlyContinue } } vrati @{ CurrentWave = 0 StartedAt = $null LastAggregation = $null TotalDevicesTargeted = 0 TotalDevicesUpdated = 0 Status = "NotStarted" WaveHistory = @() } }
function Save-RolloutState { param($State) $State | ConvertTo-Json - Dubina 10 | Out-File $rolloutStatePath - Šifrovanje UTF8 - force }
function Get-WeekdayProjection { <# . SINOPSIS Izračunavanje datuma dovršavanja za vikende (nema napretka na satu/suncu) #> param( [int]$RemainingDevices, [double]$DevicesPerDay, [datetime]$StartDate = (Get-Date) ) ako ($DevicesPerDay -le 0 -ili $RemainingDevices -le 0) { vrati @{ ProjectedDate = $null WorkingDaysNeeded = 0 CalendarDaysNeeded = 0 } } # Izračunaj potrebne radne dane (ne računajući vikende) $workingDaysNeeded = [matematika]::Ceiling($RemainingDevices / $DevicesPerDay) # Konvertovanje radnih dana u kalendarske dane (dodavanje vikenda) $currentDate = $StartDate.Date $daysAdded = 0 $workingDaysAdded = 0 while ($workingDaysAdded -lt $workingDaysNeeded) { $currentDate = $currentDate.AddDays(1) $daysAdded++ # Broji samo radnim danima if ($currentDate.DayOfWeek -ne [DayOfWeek]::Saturday -and $currentDate.DayOfWeek -ne [DayOfWeek]::Sunday) { $workingDaysAdded++ } } vrati @{ ProjectedDate = $currentDate.ToString("yyyy-MM-dd") WorkingDaysNeeded = $workingDaysNeeded CalendarDaysNeeded = $daysAdded } }
function Save-RolloutSummary { <# . SINOPSIS Čuvanje rezimea primene sa informacijama o projekciji za prikaz kontrolne table #> param( [hashtable]$State, [int]$TotalDevices, [int]$UpdatedDevices, [int]$NotUpdatedDevices, [double]$DevicesPerDay ) $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json" # Izračunaj projekciju svesti o vikendu $projection = Get-WeekdayProjection -$NotUpdatedDevices -DevicesPerDay $DevicesPerDay $summary = @{ GeneratedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss") RolloutStartDate = $State.StartedAt LastAggregation = $State.LastAggregation CurrentWave = $State.CurrentWave Status = $State.Status # Broj uređaja TotalDevices = $TotalDevices UpdatedDevices = $UpdatedDevices NotUpdatedDevices = $NotUpdatedDevices PercentUpdated = if ($TotalDevices -gt 0) { [matematika]::Round(($UpdatedDevices / $TotalDevices) * 100, 1) } else { 0 } # metrike velocite DevicesPerDay = [matematika]::Round($DevicesPerDay, 1) TotalDevicesTargeted = $State.TotalDevicesTargeted TotalWaves = $State.CurrentWave # Projekcija svesti o vikendu ProjectedCompletionDate = $projection. ProjectedDate WorkingDaysRemaining = $projection. WorkingDaysNeededed CalendarDaysRemaining = $projection. CalendarDaysNeeded # Napomena o izuzetku iz vikenda ProjectionNote = "Projektovano dovršavanje izuzima vikende (sat/sunce)" } $summary | ConvertTo-Json -Dubina 5 | Out-File $summaryPath - šifrovanje UTF8 -Force Write-Log "Sačuvan je rezime primene: $summaryPath" "INFORMACIJE" povratna $summary }
function Get-BlockedBuckets { if (probna putanja $blockedBucketsPath) { return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | Konvertuj u heštable } vrati @{} }
function Save-BlockedBuckets { param($Blocked) $Blocked | ConvertTo-Json - Dubina 10 | Out-File $blockedBucketsPath - Šifrovanje UTF8 - force }
function Get-AdminApproved { if (probna putanja $adminApprovedPath) { return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | Konvertuj u heštable } vrati @{} }
function Get-DeviceHistory { if (probna putanja $deviceHistoryPath) { return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | Konvertuj u heštable } vrati @{} }
function Save-DeviceHistory { param($History) $History | ConvertTo-Json - Dubina 10 | Out-File $deviceHistoryPath - Šifrovanje UTF8 - force }
function Save-ProcessingCheckpoint { param( [niska]$Stage, [int]$Processed, [int]$Total, [hashtable]$Metrics = @{} )
$checkpoint = @{ Faza = $Stage UpdatedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Obrađeno = $Processed Ukupno = $Total Procenat = ako ($Total -gt 0) { [matematika]::Round(($Processed / $Total) * 100, 2) } else { 0 } Metrika = $Metrics }
$checkpoint | ConvertTo-Json -Depth 6 | Out-File $processingCheckpointPath -Encoding UTF8 -Force }
function Get-NotUpdatedIndexes { param([niz]$Devices)
$hostSet = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $bucketCounts = @{}
foreach ($device in $Devices) { $hostname = if ($device. Ime hosta) { $device. Ime hosta } elseif ($device. HostName) { $device. HostName } još { $null } ako ($hostname) { [void]$hostSet.Add($hostname) }
$bucketKey = Get-BucketKey $device ako ($bucketKey) { if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey]++ } još { $bucketCounts[$bucketKey] = 1 } } }
return @{ Skup hostova = $hostSet BucketCounts = $bucketCounts } }
function Write-Log { param([niska]$Message, [niska]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $color = prekidač ($Level) { "U redu" { "Zeleno" } "WARN" { "Yellow" } "GREŠKA" { "Crvena" } "BLOKIRANO" { "Tamnocrveno" } "WAVE" { "Cyan" } podrazumevano { "Belo" } } Write-Host "[$timestamp] [$Level] $Message" -Boja prednjeg plana $color # Takođe se prijavi u datoteku $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMMdd').log" "[$timestamp] [$Level] $Message" | Out-File $logFile - Dodavanje – Šifrovanje UTF8 }
function Get-BucketKey { param($Device) # Koristite BucketId iz JSON uređaja ako je dostupan (SHA256 heš iz skripte za otkrivanje) if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { vraća "$($Device.BucketId)" } # Osnova: konstruisanje od proizvođača|model|bios $mfr = if ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } više { $Device.Proizvođač } $model = if ($Device.WMI_Model) { $Device.WMI_Model } još { $Device.Model } $bios = if ($Device.BIOSDescription) { $Device.BIOSDescription } else { $Device.BIOS } vrati "$mfr|$model|$bios" }
# ============================================================================ # VIP/EXCLUSION LIST LOADING # ============================================================================
function Get-ExcludedHostnames { param( [niska]$ExclusionFilePath, [niska]$ADGroupName ) $excluded = [System.Collections.Generic.HashSet[string]]:new([StringComparer]::OrdinalIgnoreCase) # Učitaj iz datoteke (.txt ili .csv) if ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) { $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower() if ($extension -eq ".csv") { # CSV format: očekuje kolonu "Ime Hosta" ili "Ime Računara" $csvData = Import-Csv $ExclusionFilePath $hostCol = if ($csvData[0]. PSObject.Properties.Name -sadrži "Ime hosta") { "Ime Hosta" } elseif ($csvData[0]. PSObject.Properties.Name -sadrži 'ComputerName') { 'ComputerName' } elseif ($csvData[0]. PSObject.Properties.Name -sadrži 'Name') { 'Name' } else { $null } ako ($hostCol) { foreach ($row in $csvData) { ako (![ niska]::IsNullOrWhiteSpace($row.$hostCol)) { Ne, $excluded. Add($row.$hostCol.Trim()) } } } } još { # Čisti tekst: jedno ime hosta po redu Get-Content $ExclusionFilePath | ForEach-Object { $line = $_. Skrati() ako ($line -a -ne $line. StartsWith('#')) { Ne, $excluded. Dodaj($line) } } } Write-Log "Učitano $($excluded. Count) imena hostova iz datoteke izuzetaka: $ExclusionFilePath" "INFORMACIJE" } # Učitaj iz AD bezbednosne grupe ako ($ADGroupName) { pokušajte { $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop | Where-Object { $_.objectClass -eq 'computer' } foreach ($member in $groupMembers) { Ne, $excluded. Dodaj($member. Ime) } Write-Log "Učitani $($groupMembers.Count) računari iz AD grupe: $ADGroupName" "INFORMACIJE" } hvatanje { Write-Log "Nije moguće učitati AD grupu '$ADGroupName': $_" "WARN" } } return @($excluded) }
# ============================================================================ # ALLOW LIST LOADING (Targeted Rollout) # ============================================================================
function Get-AllowedHostnames { <# . SINOPSIS Učitava imena hosta iz AllowList datoteke i/ili AD grupe za ciljnu primenu.Kada se navede allowList, SAMO ovi uređaji će biti uključeni u primenu.#> param( [niska]$AllowFilePath, [niska]$ADGroupName ) $allowed = [System.Collections.Generic.HashSet[string]]:new([StringComparer]::OrdinalIgnoreCase) # Učitaj iz datoteke (.txt ili .csv) if ($AllowFilePath -and (Test-Path $AllowFilePath)) { $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower() if ($extension -eq ".csv") { # CSV format: očekuje kolonu "Ime Hosta" ili "Ime Računara" $csvData = Import-Csv $AllowFilePath if ($csvData.Count -gt 0) { $hostCol = if ($csvData[0]. PSObject.Properties.Name -sadrži "Ime Hostname") { "Ime Hostname" } elseif ($csvData[0]. PSObject.Properties.Name -sadrži 'ComputerName') { 'ComputerName' } elseif ($csvData[0]. PSObject.Properties.Name -sadrži 'Name') { 'Name' } else { $null } ako ($hostCol) { foreach ($row in $csvData) { ako (![ niska]::IsNullOrWhiteSpace($row.$hostCol)) { [void]$allowed. Add($row.$hostCol.Trim()) } } } } } još { # Čisti tekst: jedno ime hosta po redu Get-Content $AllowFilePath | ForEach-Object { $line = $_. Skrati() ako ($line -a -ne $line. StartsWith('#')) { [void]$allowed. Dodaj($line) } } } Write-Log "Učitano $($allowed. Count) imena hostova iz datoteke liste dozvoljenih stavki: $AllowFilePath" "INFORMACIJE" } # Učitaj iz AD bezbednosne grupe ako ($ADGroupName) { pokušajte { $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Recursive -ErrorAction Stop | Where-Object { $_.objectClass -eq 'computer' } foreach ($member in $groupMembers) { [void]$allowed. Dodaj($member. Ime) } Write-Log "Učitani $($groupMembers.Count) računari iz grupe za dozvoljavanje usluge AD: $ADGroupName" "INFORMACIJE" } hvatanje { Write-Log "Nije moguće učitati AD grupu '$ADGroupName': $_" "WARN" } } return @($allowed) }
# ============================================================================ # AŽURIRANOST I NADGLEDANJE PODATAKA # ============================================================================
function Get-DataFreshness { <# . SINOPSIS Proverava koliko su podaci o otkrivanju sveži tako što pregleda vremenske oznake JSON datoteka.Vraća statistiku za vreme poslednjeg prijavljenog krajnjih tačaka.#> param([niska]$JsonPath) $jsonFiles = Get-ChildItem -$JsonPath -Filter "*.json" -ErrorAction SilentlyContinue if ($jsonFiles.Count -eq 0) { vrati @{ Ukupne datoteke = 0 FreshFiles = 0 StaleFiles = 0 NoDataFiles = 0 Najstarija datoteka = $null NewestFile = $null AvgAgeHours = 0 Upozorenje = "Nije pronađena nijedna JSON datoteka – otkrivanje možda nije primenjeno" } } $now = Get-Date $freshThresholdHours = 24 # Files su ažurirane u poslednja 24 časa su "sveže" $staleThresholdHours = 72 # Files stariji od 72 časa su "zaostalo" $fresh = 0 $stale = 0 $ages = @() foreach ($file in $jsonFiles) { $ageHours = ($now – $file. Vreme poslednjeg pisca). UkupnoHours $ages += $ageHours if ($ageHours -le $freshThresholdHours) { $fresh++ } elseif ($ageHours -ge $staleThresholdHours) { $stale++ } } $oldestFile = $jsonFiles | Sort-Object vreme poslednjeg upisivanja | Select-Object - Prvih 1 $newestFile = $jsonFiles | Sort-Object vreme poslednjeg upisivanja -Opadajući | Select-Object - Prvih 1 $warning = $null if ($stale -gt ($jsonFiles.Count * 0,5)) { $warning = "Više od 50% uređaja ima zaostale podatke (>72 časa) – proveri GPO za otkrivanje" } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) { $warning = "Manje od 30% uređaja koji su nedavno prijavljeni – otkrivanje možda nije pokrenuto" } vrati @{ Ukupne datoteke = $jsonFiles.Broj FreshFiles = $fresh StaleFiles = $stale MediumFiles = $jsonFiles.Count - $fresh - $stale OldestFile = $oldestFile.LastWriteTime NewestFile = $newestFile.LastWriteTime AvgAgeHours = [matematika]::Round(($ages | Measure-Object -Average). Prosek, 1) Upozorenje = $warning } }
function Test-DetectionGPODeployed { <# . SINOPSIS Potvrđuje da je infrastruktura otkrivanja/nadgledanja na licu mesta.#> param([niska]$JsonPath) # Proveri 1: JSON putanja postoji ako (-ne (probna putanja $JsonPath)) { vrati @{ IsDeployed = $false Poruka = "JSON putanja unosa ne postoji: $JsonPath" } } # Proverite 2: Najmanje neke JSON datoteke postoje $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Raиuna if ($jsonCount -eq 0) { vrati @{ IsDeployed = $false Poruka = "Nema JSON datoteka u $JsonPath – GPO za otkrivanje možda nije primenjen ili uređaji još uvek nisu prijavljeni" } } # Provera 3: Files su razumno nedavne (barem neke od prošle sedmice) $recentFiles = Get-ChildItem -Putanja $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue | Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) } if ($recentFiles.Count -eq 0) { vrati @{ IsDeployed = $false Message = "No JSON files updated in last 7 days - Detection GPO may be broken or devices offline" } } vrati @{ IsDeployed = $true Poruka = "Otkrivanje je aktivno: $jsonCount datoteke, $($recentFiles.Count) nedavno ažurirano" FileCount = $jsonCount RecentCount = $recentFiles.Count } }
# ============================================================================ # PRAĆENJE UREĐAJA (PO IMENIMA HOSTA) # ============================================================================
function Update-DeviceHistory { <# . SINOPSIS Prati uređaje po imenima hosta jer nemamo jedinstveni identifikator računara.Napomena: BucketId je jedan-prema-više (ista konfiguracija hardvera = isti kontejner).Ako se u JSON kolekciju doda jedinstveni identifikator, ažurirajte ovu funkciju.#> param( [niz]$CurrentDevices, [hashtable]$DeviceHistory ) foreach ($device in $CurrentDevices) { $hostname = $device. Ime hosta if (-not $hostname) { continue } # Prati uređaj po imenima hosta $DeviceHistory[$hostname] = @{ Ime hosta = $hostname BucketId = $device. ID kontejnera Proizvođač = $device. WMI_Manufacturer Model = $device. WMI_Model LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Status = $device. UpdateStatus } } }
# ============================================================================ # OTKRIVANJE BLOKIRANIH KONTEJNERA (na osnovu dostupnosti uređaja) # ============================================================================
<# . OPIS Blokiranje logike: - Kontejner je blokiran samo ako: 1. Uređaj je ciljan u talasu 2. MaxWaitHours je prošao od kada je talas počeo 3. Uređaj NIJE DOSTUPAN (ping ne uspeva) - Ako je uređaj dostupan, ali još uvek nije ažuriran, čekamo (ispravka je možda na čekanju na ponovno pokretanje – događaj 1808 pokreće se samo nakon ponovnog pokretanja sistema) - Uređaj koji nije dostupan ukazuje na to da nešto nije u redu i da treba da se istraži Deblokiranje: - Koristite -ListBlockedBuckets da biste videli blokirane kontejnere - Za deblokiranje određenog kontejnera deblokirajte određeni kontejner - Deblokiraj sve za deblokiranje svih kontejnera #>
function Test-DeviceReachable { param( [niska]$Hostname, [string]$DataPath # Putanja do JSON datoteka uređaja ) # Metod 1: Proverite JSON vremensku oznaku datoteke (najbrže – nije potrebno raščlanjivanje datoteka) # Ako je skripta za otkrivanje nedavno pokrenuta, datoteka je napisana/ažurirana, dokazujući da je uređaj živ ako ($DataPath) { $deviceFile = Get-ChildItem -Putanja $DataPath -Filter "${Hostname}*" -Datoteka -ErrorAction SilentlyContinue | Select-Object - Prvih 1 ako ($deviceFile) { $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). UkupnoHours if ($hoursSinceWrite -lt 72) { return $true } } } # Metod 2: Povratni odgovor na ping (samo ako je JSON zastao ili nedostaje) pokušajte { $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue povratne $ping } hvatanje { vrati $false } }
function Update-BlockedBuckets { param( $RolloutState, $BlockedBuckets, $AdminApproved, [niz]$NotUpdatedDevices, [hashtable]$NotUpdatedIndexes, [int]$MaxWaitHours, [bulov]$DryRun = $false ) $now = Get-Date $newlyBlocked = @() $stillWaiting = @() $devicesToCheck = @() $hostSet = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). Skup hostova } $bucketCounts = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). Broj kontejnera } # Prikupljanje uređaja koji su prošli period čekanja i koji i dalje nisu ažurirani foreach ($wave in $RolloutState.WaveHistory) { ako (-ne $wave. StartedAt) { continue } $waveStart = [DateTime]::P arse($wave. StartedAt) $hoursSinceWave = ($now – $waveStart). UkupnoHours ako ($hoursSinceWave -lt $MaxWaitHours) { # I dalje u toku perioda čekanja – još ne proveravaj Nastavite } # Proverite svaki uređaj na ovom talasu ($deviceInfo u $wave. Uređaji) { $hostname = $deviceInfo.Ime hosta $bucketKey = $deviceInfo.BucketKey # Preskoči ako je kontejner već blokiran if ($BlockedBuckets.Contains($bucketKey)) { continue } # Preskočite ako je kontejner odobrio administrator I talas je započeo PRE odobrenja # (samo proverite uređaje na koje je ciljano NAKON odobrenja administratora radi ponovnog blokiranja) if ($AdminApproved -and $AdminApproved.Contains($bucketKey)) { $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. Odobreno pri datumu ako ($waveStart -lt $approvalTime) { # Ovaj uređaj je ciljan pre odobrenja administratora - preskoči Nastavite } # Wave started after approval - this is fresh targeting, can check } # Da li se ovaj uređaj još uvek nalazi na listi NotUpdated? if ($hostSet.Contains($hostname)) { $devicesToCheck += @{ Ime hosta = $hostname BucketKey = $bucketKey WaveNumber = $wave. WaveNumber HoursSinceWave = [matematika]::Round($hoursSinceWave, 1) } } } } if ($devicesToCheck.Count -eq 0) { povratna $newlyBlocked } Write-Log "Provera dostupnosti $($devicesToCheck.Count) uređaja koji su prošli period čekanja..." "INFORMACIJE" # Praćenje neuspeha po kontejneru za donošenje odluka $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Živo=@() } # Proveri dostupnost svakog uređaja foreach ($device in $devicesToCheck) { $hostname = $device. Ime hosta $bucketKey = $device. Ključ kontejnera ako ($DryRun) { Write-Log "[SUVARUN] Proverava pristupačnost $hostname" "INFORMACIJE" Nastavite } if (-not $bucketFailures.ContainsKey($bucketKey)) { $bucketFailures[$bucketKey] = @{ Nedostupno = @(); AliveButFailed = @(); WaveNumber = $device. WaveNumber; HoursSinceWave = $device. HoursSinceWave } } $isReachable = Test-DeviceReachable -Ime hosta $hostname -DataPath $AggregationInputPath ako (-ne $isReachable) { $bucketFailures[$bucketKey]. Nedostupno += $hostname } još { # Uređaj JE dostupan, ali još uvek nije ažuriran – može biti privremeni neuspeh ili čekanje na ponovno pokretanje sistema $bucketFailures[$bucketKey]. AliveButFailed += $hostname $stillWaiting += $hostname } } # Odluka po kontejneru: blokiraj samo ako su uređaji zaista nedostupni # Živi uređaji sa neuspesima = privremeni, nastavite sa primenom foreach ($bucketKey in $bucketFailures.Keys) { $bf = $bucketFailures[$bucketKey] $unreachableCount = $bf. Nedostupno.Prebrojavanje $aliveFailedCount = $bf. AliveButFailed.Count # Proverite da li ova grupa ima bilo kakve uspehe (iz ažuriranih podataka o uređajima) $bucketHasSuccesses = $stSuccessBuckets -i $stSuccessBuckets.Contains($bucketKey) if ($unreachableCount -gt 0 -i $aliveFailedCount -eq 0) { # ALL uređaji koji otkazivanje nisu dostupni - blokirajte kontejner if ($newlyBlocked -notcontains $bucketKey) { $BlockedBuckets[$bucketKey] = @{ BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Razlog = "$unreachableCount dostupni uređaji posle $($bf. HoursSinceWave) časovi" FailedDevices = ($bf. Unreachable -join ", ") WaveNumber = $bf. WaveNumber DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } else { 0 } } $newlyBlocked += $bucketKey Write-Log "GRUPA JE BLOKIRANA: $bucketKey ($unreachableCount uređaja) nije moguće pristupiti: $($bf. Unreachable -join ', '))" "BLOCKED" } } elseif ($aliveFailedCount -gt 0) { # Uređaji su živi, ali nisu ažurirani - privremeni neuspeh, DO NOT block Write-Log "Grupa $($bucketKey.Substring(0, [Matematika]::Min(16, $bucketKey.Length)))...: $aliveFailedCount uređaji živi, ali na čekanju, $unreachableCount nedostupan – NOT blocking (privremeno)" "INFO" if ($unreachableCount -gt 0) { Write-Log " Nedostupno: $($bf. Unreachable -join ', ')" "WARN" } Write-Log " Živ, ali na čekanju: $($bf. AliveButFailed -join ', ')" "INFO" # Praćenje broja grešaka u stanju objavljivanja za nadgledanje if (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} } $RolloutState.TemporaryFailures[$bucketKey] = @{ AliveButFailed = $bf. Živo, ali nije uspelo Nedostupno = $bf. Nedostupan Poslednji put proverena = Get-Date -Format "yyyy-MM-dd HH:mm:ss" } } } if ($stillWaiting.Count -gt 0) { Write-Log "Uređaji do kojih može da dođe, ali ispravka na čekanju (možda će biti potrebno ponovno pokretanje sistema): $($stillWaiting.Count)" "INFORMACIJE" } povratna $newlyBlocked }
# ============================================================================ # AUTO-DEBLOKIRAJ: Deblokiraj kontejnere kada se uređaji uspešno ažuriraju # ============================================================================
function Update-AutoUnblockedBuckets { <# . OPIS Proverava da li su se uređaji u blokiranim kontejnerima ažurirali (događaj 1808). Automatski se deblokira ako su svi ciljani uređaji u kontejneru ažurirani.Ako su ažurirani samo neki uređaji, obaveštavate administratora koji može ručno da deblokirate. Administrator možete ručno da deblokirate pomoću: .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "path" -UnblockBucket "BucketKey" #> param( $BlockedBuckets, $RolloutState, [niz]$NotUpdatedDevices, [niska]$ReportBasePath, [hashtable]$NotUpdatedIndexes, [int]$LogSampleSize = 25 ) $autoUnblocked = @() $bucketsToCheck = @($BlockedBuckets.Ključevi) $hostSet = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). Skup hostova } foreach ($bucketKey in $bucketsToCheck) { $bucketInfo = $BlockedBuckets[$bucketKey] # Preuzmi sve uređaje koje smo ciljali iz ove kontejnera istorije $targetedDevicesInBucket = @() foreach ($wave in $RolloutState.WaveHistory) { $targetedDevicesInBucket += @($wave. Uređaji | Where-Object { $_. BucketKey -eq $bucketKey }) } if ($targetedDevicesInBucket.Count -eq 0) { continue } # Proverite koliko ciljanih uređaja je i dalje u funkciji NotUpdated naspram ažuriranog $updatedDevices = @() $stillPendingDevices = @() foreach ($targetedDevice in $targetedDevicesInBucket) { if ($hostSet.Contains($targetedDevice.Hostname)) { $stillPendingDevices += $targetedDevice.Ime hosta } još { $updatedDevices += $targetedDevice.Ime hosta } } if ($updatedDevices.Count -gt 0 -i $stillPendingDevices.Count -eq 0) { # SVI ciljani uređaji su ažurirani – automatsko deblokiranje! $BlockedBuckets.Remove($bucketKey) $autoUnblocked += @{ BucketKey = $bucketKey UpdatedDevices = $updatedDevices PreviouslyBlockedAt = $bucketInfo.BlockedAt Reason = "Svi $($updatedDevices.Count) ciljani uređaji su uspešno ažurirani" } Write-Log "AUTOMATSKI DEBLOKIRANA: $bucketKey (Svi $($updatedDevices.Count) ciljani uređaji su uspešno ažurirani)" "U redu" # Broj inkrementnih OEM talasa za OEM kontejner ovog kontejnera (praćenje po OEM-u) $bucketOEM = if ($bucketKey -match '\|') { ($bucketKey -razdeljivanje '\|')[0] } else { 'Nepoznato' } # Izdvajanje OEM-a iz ključa razgraničenog prolazom ili podrazumevanog if (-not $RolloutState.OEMWaveCounts) { $RolloutState.OEMWaveCounts = @{} } $currentWave = if ($RolloutState.OEMWaveCounts[$bucketOEM]) { $RolloutState.OEMWaveCounts[$bucketOEM] } else { 0 } $RolloutState.OEMWaveCounts[$bucketOEM] = $currentWave + 1 Write-Log " OEM '$bucketOEM' broj talasa povećan na $($currentWave + 1) (sledeća dodela: $([int][Matematika]::P ow(2, $currentWave + 1)) uređaji)" "INFORMACIJE" } elseif ($updatedDevices.Count -gt 0 -i $stillPendingDevices.Count -gt 0) { # NEKI uređaji su ažurirani, ali drugi i dalje čekaju – obavestite administratora (samo jednom) if (-not $bucketInfo.UnblockCandidate) { $bucketInfo.UnblockCandidate = $true $bucketInfo.UpdatedDevices = $updatedDevices $bucketInfo.PendingDevices = $stillPendingDevices $bucketInfo.NotifiedAt = (Get-Date). ToString("yyyy-MM-dd HH:mm:ss") Write-Log "" "INFORMACIJE" Write-Log "========== DELIMIČNO AŽURIRANJE U BLOKIRANOJ GRUPI ==========" "INFORMACIJE" Write-Log "Grupa: $bucketKey" "INFORMACIJE" $updatedSample = @($updatedDevices | Select-Object - Prvi $LogSampleSize) $pendingSample = @($stillPendingDevices | Select-Object -Prvi $LogSampleSize) $updatedSuffix = if ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count - $LogSampleSize) više)" } još { "" } $pendingSuffix = if ($stillPendingDevices.Count -gt $LogSampleSize) { " ... (+$($stillPendingDevices.Count - $LogSampleSize) više)" } još { "" } Write-Log "Ažurirani uređaji ($($updatedDevices.Count)): $($updatedSample -join ', ')$updatedSuffix" "OK" Write-Log "Još uvek je na čekanju ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN" Write-Log "" "INFO" Write-Log "Da biste ručno deblokirali ovaj kontejner nakon verifikacije, pokrenite:" "INFORMACIJE" Write-Log " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'"" "INFO" Write-Log "=======================================================" "INFORMACIJE" Write-Log "" "INFO" } } } povratna $autoUnblocked }
# ============================================================================ # WAVE GENERATION (INLINED - isključuje blokirane kontejnere) # ============================================================================
function New-RolloutWave { param( [niska]$AggregationPath, $BlockedBuckets, $RolloutState, [int]$MaxDevicesPerWave = 50, [string[]]$AllowedHostnames = @(), [niska[]]$ExcludedHostnames = @() ) # Učitavanje agregatnih podataka $notUptodateCsv = Get-ChildItem -$AggregationPath -Filter "*NotUptodate*.csv" | Where-Object { $_. Name -notlike "*Buckets*" } | Sort-Object Vreme poslednjeg pisca -Opadajući | Select-Object - Prvih 1 ako (-ne $notUptodateCsv) { Write-Log "Nije pronađena nijedna CSV datoteka notUptodate" "GREŠKA" povratni $null } $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName) # Normalizuj Ime HostName - > ime hosta radi doslednosti (CSV koristi Ime HostName, kôd koristi ime hosta) foreach ($device in $allNotUpdated) { ako ($device. PSObject.Properties['HostName'] -a -not $device. PSObject.Properties['Ime Hostname']) { $device | Add-Member -NotePropertyName "Ime Hostname" -NotePropertyValue $device. Ime hosta - sila } } # Filtriranje blokiranih kontejnera $eligibleDevices = @($allNotUpdated | Where-Object { $bucketKey = Get-BucketKey $_ -ne $BlockedBuckets.Contains($bucketKey) }) # Filtriranje samo na dozvoljenim uređajima (ako je navedena lista allowList) # AllowList = ciljana primena – samo će ovi uređaji biti smatrani if ($AllowedHostnames.Count -gt 0) { $beforeCount = $eligibleDevices.Count $eligibleDevices = @($eligibleDevices | Where-Object { $_. Ime hosta – u $AllowedHostnames }) $allowedCount = $eligibleDevices.Count Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO" } # Filtriranje VIP/isključenih uređaja (Lista blokiranja) # BlockList is applied AFTER AllowList if ($ExcludedHostnames.Count -gt 0) { $beforeCount = $eligibleDevices.Count $eligibleDevices = @($eligibleDevices | Where-Object { $_. Ime hosta - nije $ExcludedHostnames }) $excludedCount = $beforeCount - $eligibleDevices.Count if ($excludedCount -gt 0) { Write-Log "Isključeni $excludedCount VIP/zaštićeni uređaji iz primene" "INFORMACIJE" } } if ($eligibleDevices.Count -eq 0) { Write-Log "Nema preostalih uređaja koji ispunjavaju uslove (svi ažurirani ili blokirani)" "U redu" povratna $null } # Nabavite uređaje koji su već u primeni (iz prethodnih talasa) $devicesAlreadyInRollout = @() if ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) { $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object { $_. Uređaji | ForEach-Object { $_. Ime hosta } } | Where-Object { $_ }) } Write-Log "Uređaji koji su već u primeni: $($devicesAlreadyInRollout.Count)" "INFO" # Razdvojite nivoom pouzdanosti $highConfidenceDevices = @($eligibleDevices | Where-Object { $_. ConfidenceLevel -eq "Visoki stepen pouzdanosti" -i $_. Ime hosta - nije $devicesAlreadyInRollout }) # Potrebna radnja obuhvata: # - Eksplicitna "Potrebna je radnja" # - Nivo pouzdanosti prazan/bez vrednosti # - ANY nepoznata/neprepoznata vrednost ConfidenceLevel (tretira se kao potrebna radnja) $knownSafeCategories = @( "Visokog stepena pouzdanosti", "Privremeno pauzirano", "Pod posmatranje", "U okviru posmatranja - više podataka je potrebno", "Nije podržano", "Nije podržano - poznato ograničenje" ) $actionRequiredDevices = @($eligibleDevices | Where-Object { $_. ConfidenceLevel – $knownSafeCategories -i $_. Ime hosta - nije $devicesAlreadyInRollout }) Write-Log "Visok stepen pouzdanosti (nije u primeni): $($highConfidenceDevices.Count)" "INFORMACIJE" Write-Log "Potrebna je radnja (ne u primeni): $($actionRequiredDevices.Count)" "INFO" # Izrada talasnih uređaja $waveDevices = @() # HIGH CONFIDENCE: Include ALL (safe for rollout) if ($highConfidenceDevices.Count -gt 0) { Write-Log "Dodavanje svih $($highConfidenceDevices.Count) uređaja sa visokim stepenom pouzdanosti" "WAVE" $waveDevices += $highConfidenceDevices } # RADNJA OBAVEZNO: Napredna primena (kontejner zasnovan na OEM-u za kontejnere bez uspeha) # Strategija: # - kontejneri sa 0 uspeha: Raširite po OEM-ovima (1 za OEM -> 2 po OEM -> 4 po OEM-u) # - kontejneri sa ≥1 uspehom: Dvostruko slobodno bez OEM ograničenja if ($actionRequiredDevices.Count -gt 0) { # Učitaj broj uspeha kontejnera sa ažuriranih uređaja CSV (uređaji koji su uspešno ažurirani) $updatedCsv = Get-ChildItem -$AggregationPath -Filter "*updated_devices*.csv" | Sort-Object Vreme poslednjeg upisivanja -Opadajući | Select-Object -Prvih 1 $bucketStats = @{} ako ($updatedCsv) { $updatedDevices = Import-Csv $updatedCsv.FullName # Broj uspeha po ID-u kontejnera $updatedDevices | ForEach-Object { $key = Get-BucketKey $_ ako ($key) { if (-not $bucketStats.ContainsKey($key)) { $bucketStats[$key] = @{ Uspešnosti = 0; Na čekanju = 0; Ukupno = 0 } } $bucketStats[$key]. Uspešni++ $bucketStats[$key]. Ukupno++ } } Write-Log "Učitani $($updatedDevices.Count) ažurirani uređaji u $($bucketStats.Count) kontejnerima" "INFORMACIJE" } još { # Osnovni podaci: isprobajte ActionRequired_Buckets CSV $bucketsCsv = Get-ChildItem -$AggregationPath -Filter "*ActionRequired_Buckets*.csv" | Sort-Object vreme poslednjeg upisivanja -Opadajući | Select-Object - Prvih 1 ako ($bucketsCsv) { Import-Csv $bucketsCsv.FullName | ForEach-Object { $key = if ($_. BucketId) { $_. BucketId } else { "$($_. Proizvođač)|$($_. Model)|$($_. BIOS)" } $bucketStats[$key] = @{ Uspešni = [int]$_. Uspeha Na čekanju = [int]$_. Neobrađeni Ukupno = [int]$_. TotalDevices } } } } # Grupiši notUpdated uređaje po kontejnera (proizvođač|Model|BIOS) $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ } # Razdvojene grupe: nula uspeha naspram uspeha $zeroSuccessBuckets = @() $hasSuccessBuckets = @() foreach ($bucket in $buckets) { $bucketKey = $bucket. Ime $bucketDevices = @($bucket. Grupa) $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Ime hosta }) # Brojanje uspeha u ovoj kontejnera $stats = $bucketStats[$bucketKey] $successes = if ($stats) { $stats. Uspesi } još { 0 } # Pronađi uređaje primenjene na ovu kontejner iz istorije talasa $deployedToBucket = @() foreach ($wave in $RolloutState.WaveHistory) { ($device u $wave. Uređaji) { ako ($device. BucketKey -eq $bucketKey -i $device. Ime hosta) { $deployedToBucket += $device. Ime hosta } } } $deployedToBucket = @($deployedToBucket | Sort-Object -Jedinstveno) # Proverite da li su SVI primenjeni uređaji prijavili uspeh $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames }) $confirmedSuccess = $deployedToBucket.Count - $stillPending.Count # Ako je na čekanju, preskočite ovaj kontejner dok sve ne potvrdite if ($stillPending.Count -gt 0) { $parts = $bucketKey -razdeljivanje '\|' $displayName = "$($parts[0]) - $($parts[1])" Write-Log " Grupa: $displayName - raspoređeno=$($deployedToBucket.Count), Potvrđeno=$confirmedSuccess, Pending=$($stillPending.Count) (čeka se)" "INFORMACIJE" Nastavite } # Preostalo ispunjava uslove = uređaji koji još nisu primenjeni $devicesNotYetTargeted = @($bucketDevices | Where-Object { $_. Ime hosta - nije $deployedToBucket }) if ($devicesNotYetTargeted.Count -eq 0) { continue } # Kategorizuj po broju uspeha $bucketInfo = @{ BucketKey = $bucketKey Uređaji = $devicesNotYetTargeted ConfirmedSuccess = $confirmedSuccess Uspešni = $successes OEM = if ($bucket. Grupa[0]. WMI_Manufacturer) { $bucket. Grupa[0]. WMI_Manufacturer } elseif ($bucketKey -match "\|") { ($bucketKey -razdeljivanje '\|')[0] } još { 'Nepoznato' } } if ($successes -eq 0) { $zeroSuccessBuckets += $bucketInfo } još { $hasSuccessBuckets += $bucketInfo } } # === PROCESS HAS-SUCCESS BUCKETS (≥1 success) === # Dupli broj uspeha – ako je 14 uspelo, primenite 28 dalje foreach ($bucketInfo in $hasSuccessBuckets) { $nextBatchSize = $bucketInfo.Uspehi * 2 $nextBatchSize = [Matematika]::Min($nextBatchSize, $MaxDevicesPerWave) $nextBatchSize = [Matematika]::Min($nextBatchSize, $bucketInfo.Devices.Count) if ($nextBatchSize -gt 0) { $selectedDevices = @($bucketInfo.Devices | Select-Object -First $nextBatchSize) $waveDevices += $selectedDevices $parts = if ($bucketInfo.BucketKey -match "\|") { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Math]::Min(12, $bucketInfo.BucketKey.Length))) } $displayName = "$($parts[0]) - $($parts[1])" Write-Log " [HAS-SUCCESS] $displayName - uspešni=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x confirmed)" "INFO" } } # === OBRADA KONTEJNERA SA NULTE USPEHOM (raširite se po OEM mašinama pomoću per-OEM praćenja) === # Cilj: Raširite rizik po različitim OEM-ovima, pratite napredak po OEM-u nezavisno # Svaki OEM napreduje na osnovu sopstvene istorije uspeha: # - OEM sa uspehom: Dobija više uređaja sledeći talas (2^waveCount) # - OEM bez uspeha: Ostaje na trenutnom nivou dok se ne potvrdi uspeh if ($zeroSuccessBuckets.Count -gt 0) { # Pokretanje broja po OEM talasu ako ne postoji if (-not $RolloutState.OEMWaveCounts) { $RolloutState.OEMWaveCounts = @{} } # Grupiši kontejnere bez uspeha po OEM-u $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OEM } $totalZeroSuccessAdded = 0 $oemsDeployedTo = @() foreach ($oemGroup in $oemBuckets) { $oemName = $oemGroup.Name # Preuzmi broj talasa ovog OEM-a (počinje od 0) $oemWaveCount = if ($RolloutState.OEMWaveCounts[$oemName]) { $RolloutState.OEMWaveCounts[$oemName] } još { 0 } # Izračunaj uređaje za OVAJ OEM: 2^waveCount (1, 2, 4, 8...) $devicesForThisOEM = [int][Matematika]::P ow(2, $oemWaveCount) $devicesForThisOEM = [Matematika]::Max(1, $devicesForThisOEM) $oemDevicesAdded = 0 # Izaberite iz svakog kontejnera ispod ovog OEM-a foreach ($bucketInfo in $oemGroup.Group) { $remaining = $devicesForThisOEM - $oemDevicesAdded if ($remaining -le 0) { break } $toTake = [Matematika]::Min($remaining, $bucketInfo.Devices.Count) if ($toTake -gt 0) { $selectedDevices = @($bucketInfo.Devices | Select-Object -First $toTake) $waveDevices += $selectedDevices $oemDevicesAdded += $toTake $totalZeroSuccessAdded += $toTake $parts = if ($bucketInfo.BucketKey -match "\|") { $bucketInfo.BucketKey -split '\|' } else { @($bucketInfo.OEM, $bucketInfo.BucketKey.Substring(0, [Matematika]::Min(12, $bucketInfo.BucketKey.Length))) } $displayName = "$($parts[0]) - $($parts[1])" Write-Log " [ZERO-SUCCESS] $displayName – Primena=$toTake (OEM talas $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN" } } if ($oemDevicesAdded -gt 0) { Write-Log " OEM: $oemName - talasni $oemWaveCount, dodat $oemDevicesAdded uređaji" "INFORMACIJE" $oemsDeployedTo += $oemName } } # Prati koje OEM-e smo primenili (radi povećavanja sledeće provere uspeha) if ($oemsDeployedTo.Count -gt 0) { $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo Write-Log "Primena bez uspeha: $totalZeroSuccessAdded uređaja na $($oemsDeployedTo.Count) OEM-ovima" "INFORMACIJE" } } } if (@($waveDevices). Broj -eq 0) { povratna $null } povratni $waveDevices }
# ============================================================================ # GPO DEPLOYMENT (INLINED – kreira GPO, bezbednosnu grupu, veze) # ============================================================================
function Deploy-GPOForWave { param( [niska]$GPOName, [niska]$TargetOU, [niska]$SecurityGroupName, [niz]$WaveHostnames, [bulov]$DryRun = $false ) # ADMX smernice: SecureBoot.admx - SecureBoot_AvailableUpdatesPolicy # Putanja registratora: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot # Ime vrednosti: AvailableUpdatesPolicy # Omogućena vrednost: 22852 (0x5944) - Ažuriraj sve tastere za bezbedno pokretanje + bootmgr # Onemogućena vrednost: 0 # # Using Smernice grupe Preferences (GPP) for reliable HKLM\SYSTEM path deployment # GPP kreira postavke u okviru: Konfiguracija računara > Željene postavke > Windows postavke > registratora $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" $RegistryValueName = "AvailableUpdatesPolicy" $RegistryValue = 22852 # 0x5944 - podudara se sa ADMX enabledValue Write-Log "Primena GPO-a: $GPOName" "WAVE" Write-Log "Registrator: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X'))" "INFO" ako ($DryRun) { Write-Log "[DRYRUN] bi kreirao GPO: $GPOName" "INFORMACIJE" Write-Log "[DRYRUN] Kreira bezbednosnu grupu: $SecurityGroupName" "INFORMACIJE" Write-Log "[DRYRUN] bi dodao $(@($WaveHostnames). Broj) računara za grupisanje" "INFORMACIJE" Write-Log "[SUVIŠNO] Povezalo bi GPO sa: $TargetOU" "INFORMACIJE" povratni $true } pokušajte { # Uvoz potrebnih modula Import-Module Smernice grupe - Prekid greške Import-Module ActiveDirectory -Prekid radnje } hvatanje { Write-Log "Nije uspelo uvoz potrebnih modula (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR" vraćanje $false } # 1. korak: Kreiranje ili preuzimanje GPO-a $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue ako ($existingGPO) { Write-Log "GPO već postoji: $GPOName" "INFORMACIJE" $gpo = $existingGPO } još { pokušajte { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Certificate Rollout - AvailableUpdatesPolicy=0x5944 - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Kreirani GPO: $GPOName" "U redu" } hvatanje { Write-Log "Kreiranje GPO-a nije uspelo: $($_. Exception.Message)" "ERROR" povratni $false } } # 2. korak: Postavljanje vrednosti registratora Smernice grupe željenim postavkama (GPP) # GPP je pouzdaniji za HKLM\SYSTEM putanje nego Set-GPRegistryValue pokušajte { # Prvo pokušajte da uklonite sve postojeće željene opcije za ovu vrednost (da biste izbegli duplikate) Remove-GPPrefRegistryValue -Ime $GPOName -Kontekst računar -ključ -$RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue # Create GPP registry preference with "Replace" action # Zameni = Kreiraj ako ne postoji, ažuriraj ako postoji (najpopularnije) # Ažuriraj = Ažuriraj samo ako postoji (ne uspeva ako vrednost ne postoji) Set-GPPrefRegistryValue - Ime $GPOName ' -Kontekstualni računar ' -Zamena radnje ' - Ključ $RegistryKey ' -ValueName $RegistryValueName ' -Otkucajte reč "DWord" -Vrednost $RegistryValue Write-Log "Konfigurisana željena postavka GPP registratora: $RegistryValueName = 0x5944 (Action=Replace)" "U redu" } hvatanje { Write-Log "GPP nije uspeo, pokušaj set-GPRegistryValue: $($_. Exception.Message)" "WARN" # Vraća se na Set-GPRegistryValue (funkcioniše ako je primenjena ADMX) pokušajte { Set-GPRegistryValue - ime $GPOName ' - Ključ $RegistryKey ' -ValueName $RegistryValueName ' -Otkucajte reč "DWord" -Vrednost $RegistryValue Write-Log "Konfigurisani registrator putem usluge Set-GPRegistryValue: $RegistryValueName = 0x5944" "U redu" } hvatanje { Write-Log "Postavljanje vrednosti registratora nije uspelo: $($_. Exception.Message)" "ERROR" povratne $false } } # 3. korak: Kreiranje ili preuzimanje bezbednosne grupe $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue ako (-ne $existingGroup) { pokušajte { $group = New-ADGroup - Ime $SecurityGroupName ' -Bezbednost Grupne kategorije ' -GroupScope DomainLocal ' -Opis "Računari koji su ciljani za primenu bezbednog pokretanja - $GPOName" ' -Prolaz Write-Log "Kreirana bezbednosna grupa: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Kreiranje bezbednosne grupe nije uspelo: $($_. Exception.Message)" "ERROR" vraćanje $false } } još { Write-Log "Bezbednosna grupa postoji: $SecurityGroupName" "INFORMACIJE" $group = $existingGroup } # 4. korak: Dodavanje računara u bezbednosnu grupu $added = 0 $failed = 0 foreach ($hostname in $WaveHostnames) { pokušajte { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue $added++ } hvatanje { $failed++ } } Write-Log "Dodato $added računarima u bezbednosnu grupu ($failed nije pronađen u AD)" "U redu" # 5. korak: Konfigurisanje bezbednosnog filtriranja u GPO pokušajte { # Ukloni podrazumevanu dozvolu "Autorizovani korisnici" Primeni dozvolu (zadrži čitanje) Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType grupa -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue # Add Apply permission for our security group Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType grupa -PermissionLevel GpoApply -ErrorAction Stop Write-Log "Konfigurisano bezbednosno filtriranje za: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Konfigurisanje bezbednosnog filtriranja nije uspelo: $($_. Exception.Message)" "WARN" Write-Log "GPO može da se primeni na sve računare u povezanom OU – verifikuj ručno" "UPOZORI" } # 6. korak: Povezivanje GPO sa OU (CRITICAL za primenu smernica) ako ($TargetOU) { pokušajte { $existingLink = Get-GPInheritance -Ciljna $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName - eq $GPOName } ako (-ne $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Da -ErrorAction Stop Write-Log "Povezan GPO sa: $TargetOU" "U redu" Write-Log "GPO će se primeniti u sledećem gpupdate na ciljnim računarima" "INFORMACIJE" } još { Write-Log "GPO je već povezan sa ciljnim OU" "INFORMACIJE" } } hvatanje { Write-Log "KRITIČNO: Povezivanje GPO sa OU nije uspelo: $($_. Exception.Message)" "ERROR" Write-Log "GPO je kreiran, ali NIJE POVEZAN – neće se primeniti ni na koji računar!" "GREŠKA" Write-Log "Potrebno je ručno ispravljanje: New-GPLink -Ime "$GPOName" -Cilj '$TargetOU' -LinkEnabled Da" "GREŠKA" povratna $false } } još { Write-Log "UPOZORENJE: Nije naveden ciljniOU – GPO je kreiran, ali NIJE POVEZANO!" "GREŠKA" Write-Log "Ručno povezivanje potrebno da bi GPO aktivan" "GREŠKA" Write-Log "Pokreni: New-GPLink -ime "$GPOName" -cilj "<Your-Domain-DN>" -LinkEnabled Yes" "ERROR" } # 7. korak: Provera GPO konfiguracije Write-Log "Provera GPO konfiguracije..." "INFORMACIJE" pokušajte { $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop Write-Log "Status GPO: $($gpoReport.GpoStatus)" "INFO" # Proverite da li je konfigurisana postavka registratora $regSettings = Get-GPRegistryValue -Name $GPOName -Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue ako (-ne $regSettings) { # Isprobajte proveru GPP registratora (različitu putanju u GPO) Write-Log "Provera željenih postavki GPP registratora..." "INFORMACIJE" } } hvatanje { Write-Log "Nije moguće verifikovali GPO: $($_. Exception.Message)" "WARN" } povratna $true }
# ============================================================================ # WINCS DEPLOYMENT (Alternative to AvailableUpdatesPolicy GPO) # ============================================================================ # Referenca: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe # # WinCS komande (pokreni na krajnjim tačkama u kontekstu SISTEMA): # Upit: WinCsFlags.exe /query -- ključni F33E0C8E002 # Primeni: WinCsFlags.exe /apply -- taster "F33E0C8E002" # Uspostavi početne vrednosti: WinCsFlags.exe /reset -- ključ "F33E0C8E002" # # Ovaj metod primenjuje GPO sa planiranim zadatkom koji se WinCsFlags.exe /apply # kao SYSTEM na ciljnim krajnjim tačkama. Slično načinu primene skripte za otkrivanje, # ali se pokreće jednom (pri pokretanju) umesto dnevno.
function Deploy-WinCSGPOForWave { <# . SINOPSIS Primeni omogućavanje bezbednog pokretanja usluge WinCS putem GPO planiranog zadatka.. OPIS Kreira GPO koji primenjuje planirani zadatak za pokretanje WinCsFlags.exe /apply u okviru SYSTEM kontekst pri pokretanju mašinskog pokretanja. Ciljanje kontrola bezbednosne grupe.. PARAMETAR GPOName Ime za GPO.. PARAMETER TargetOU OU sa povezivanjem GPO-a.. PARAMETER SecurityGroupName Bezbednosna grupa za GPO filtriranje.. PARAMETER WaveHostnames Imena hostova koja treba dodati bezbednosnoj grupi.. PARAMETER WinCSKey WinCS ključ koji treba primeniti (podrazumevano: F33E0C8E002).. PARAMETER DryRun Ako je tačno, evidentirajte samo ono što bi bilo urađeno.#> param( [Parameter(Obavezno = $true)] [niska]$GPOName, [Parameter(Obavezno = $false)] [niska]$TargetOU, [Parameter(Obavezno = $true)] [niska]$SecurityGroupName, [Parameter(Obavezno = $true)] [niz]$WaveHostnames, [Parameter(Obavezno = $false)] [niska]$WinCSKey = "F33E0C8E002", [Parameter(Obavezno = $false)] [bulov]$DryRun = $false ) # Konfiguracija planiranog zadatka za WinCsFlags.exe $TaskName = "SecureBoot-WinCS-Apply" $TaskPath = "\Microsoft\Windows\SecureBoot\" $TaskDescription = "Primenjuje konfiguraciju bezbednog pokretanja putem usluge WinCS – ključ: $WinCSKey" Write-Log "Primena WinCS GPO: $GPOName" "WAVE" Write-Log "Zadatak će biti pokrenut: WinCsFlags.exe /apply --ključ '"$WinCSKey'"" "INFORMACIJE" Write-Log "Okidač: Pri pokretanju sistema (pokreće se jednom kao SISTEM)" "INFORMACIJE" ako ($DryRun) { Write-Log "[DRYRUN] Kreira GPO: $GPOName" "INFORMACIJE" Write-Log "[DRYRUN] Kreira bezbednosnu grupu: $SecurityGroupName" "INFORMACIJE" Write-Log "[DRYRUN] bi dodao $(@($WaveHostnames). Broj) računara za grupisanje" "INFORMACIJE" Write-Log "[DRYRUN] Primenjuje planirani zadatak: $TaskName" "INFORMACIJE" Write-Log "[DRYRUN] Povezalo bi GPO sa: $TargetOU" "INFORMACIJE" vrati @{ Uspeh = $true GPOCreated = $false GroupCreated = $false ComputersAdded = 0 } } pokušajte { # Uvoz potrebnih modula Import-Module Smernice grupe - Tačka greške Import-Module ActiveDirectory -Prekid radnje } hvatanje { Write-Log "Nije uspelo uvoz potrebnih modula (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } # 1. korak: Kreiranje ili preuzimanje GPO-a $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue ako ($gpo) { Write-Log "GPO već postoji: $GPOName" "INFORMACIJE" } još { pokušajte { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot WinCS Deployment - $WinCSKey - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Kreirani GPO: $GPOName" "U redu" } hvatanje { Write-Log "Kreiranje GPO-a nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } } # 2. korak: Kreiranje XML-a planiranog zadatka za GPO primenu # Ovo kreira zadatak koji se pokreće WinCsFlags.exe /primeni pri pokretanju $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <za registraciju> <opis>$TaskDescription</Opis> WinCsFlags.exe1 Author>SYSTEM</Author> WinCsFlags.exe5 /RegistrationInfo> WinCsFlags.exe7 okidače> WinCsFlags.exe9 BootTrigger> <je>tačno</Omogućeno> <odlaganje>PT5M</Odlaganje> </BootTrigger> </Triggers> <principala> <principala="Author"> <UserId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </principal> </Principals> <postavke> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <StartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <StartOnDemand>true</AllowStartOnDemand> <je>true</Enabled> <skriveno>netačno</Skriveno> <RunOnlyIfIdle>false</RunOnlyIfIdle> WinCsFlags.exe03 OnemogućistartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession> WinCsFlags.exe07 UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine> WinCsFlags.exe11 WakeToRun>false</WakeToRun> WinCsFlags.exe15 ExecutionTimeLimit>PT1H</ExecutionTimeLimit> WinCsFlags.exe19 DeleteExpiredTaskAfter>P30D</DeleteExpiredTaskAfter> WinCsFlags.exe23 prioritet>7</Prioritet> WinCsFlags.exe27 /Settings> WinCsFlags.exe29 Kontekst radnji="Author"WinCsFlags.exe30 WinCsFlags.exe31 exec> WinCsFlags.exe33 komanda>WinCsFlags.exe</Command> WinCsFlags.exe37 argumenata>/apply -- taster "$WinCSKey"WinCsFlags.exe39 /Argumenti> WinCsFlags.exe41 /Exec> WinCsFlags.exe43 /Actions> WinCsFlags.exe45 /Fascikla> " @
# Step 3: Deploy scheduled task via GPO Preferences # Uskladišti XML zadatka u SYSVOL za GPO planirane zadatke neposredni zadatak pokušajte { $gpoId = $gpo. Id.ToString() $sysvolPath = "\\$((Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Smernice\{$gpoId}\Machine\Preferences\ScheduledTasks" if (-not (test-path $sysvolPath)) { New-Item -ItemType direktorijum -Putanja $sysvolPath -Force | Bez vrednosti } # Kreirajte ScheduledTasks.xml za GPP $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"> <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="{$([guid]::NewGuid(). ToString(). ToUpper())}"> <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\System" logonType="S4U"> <Task version="1.3"> <za registraciju informacija> <Opis>$TaskDescription</Opis> </RegistrationInfo> <principala> <ID="Author"> <UserId>NT AUTHORITY\System</UserId> <LogonType>S4U</LogonType> <RunLevel>HighestAvailable</RunLevel> </glavni> </Principals> <postavke> <IdleSettings> <trajanje>PT5M</Trajanje> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <startIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <StartOnDemand>true</AllowStartOnDemand> <je>true</Enabled> <skriveno>netačno</Skriveno> <ExecutionTimeLimit>PT1H</ExecutionTimeLimit> <prioritet>7</Prioritet> <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter> </Settings> <okidača> <tajmTrigger> <početna>$(Get-Date -Format "yyyy-MM-dd')T00:00:00</StartBoundary> <je>true</Enabled> </TimeTrigger> </Triggers> <radnji> <Exec> <Komanda>WinCsFlags.exe</Command> <argumenata>/apply -- taster "$WinCSKey"</Argumenti> </Exec> </Actions> </Task> </Svojstva> </ImmediateTaskV2> </ScheduledTasks> "@ $gppTaskXml | Out-File -FilePath (join-path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force Write-Log "Primenjeni planirani zadatak u GPO: $TaskName" "U redu" } hvatanje { Write-Log "Primena XML-a planiranog zadatka nije uspela: $($_. Exception.Message)" "WARN" Write-Log "Povratak na WinCS primenu zasnovanu na registratoru" "INFORMACIJE" # Povratna informacija: koristite pristup WinCS registratora ako zadatak planiranog GPP-a ne uspe # WinCS može da se pokrene i putem ključa registratora # (Primena zavisi od API-ja WinCS registratora ako je dostupan) } # 4. korak: Kreiranje ili preuzimanje bezbednosne grupe $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue ako (-ne $group) { pokušajte { $group = New-ADGroup - Ime $SecurityGroupName ' -Bezbednost Grupne kategorije ' -GroupScope DomainLocal ' -Opis "Računari koji su ciljani za primenu winCS bezbednog pokretanja - $GPOName" ' ' -Prolaz Write-Log "Kreirana bezbednosna grupa: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Kreiranje bezbednosne grupe nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } } još { Write-Log "Bezbednosna grupa postoji: $SecurityGroupName" "INFORMACIJE" } # 5. korak: Dodavanje računara u bezbednosnu grupu $added = 0 $failed = 0 foreach ($hostname in $WaveHostnames) { pokušajte { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue $added++ } hvatanje { $failed++ } } Write-Log "Dodato $added u bezbednosnu grupu ($failed nije pronađeno u AD)" "U redu" # 6. korak: Konfigurisanje bezbednosnog filtriranja u GPO pokušajte { Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType grupa -PermissionLevel GpoApply -ErrorAction Stop Write-Log "Konfigurisano bezbednosno filtriranje za: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Konfigurisanje bezbednosnog filtriranja nije uspelo: $($_. Exception.Message)" "WARN" } # 7. korak: Povezivanje GPO sa OU ako ($TargetOU) { pokušajte { $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName - eq $GPOName } ako (-ne $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Da -ErrorAction Stop Write-Log "Povezani GPO sa: $TargetOU" "U redu" } još { Write-Log "GPO je već povezan sa ciljnim OU" "INFORMACIJE" } } hvatanje { Write-Log "KRITIČNO: Povezivanje GPO sa OU nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = "GPO veza nije uspela: $($_. Exception.Message)" } } } Write-Log "WinCS GPO primena je dovršena" "U redu" Write-Log "Mašine će se pokrenuti WinCsFlags.exe pri sledećem osvežavanju GPO- a + ponovno pokretanje/pokretanje" "INFORMACIJE" vrati @{ Uspeh = $true GPOCreated = $true GroupCreated = $true ComputersAdded = $added ComputersFailed = $failed } }
# Wrapper function to maintain compatibility with main loop funkcije Deploy-WinCSForWave { param( [Parameter(Obavezno = $true)] [niz]$WaveHostnames, [Parameter(Obavezno = $false)] [niska]$WinCSKey = "F33E0C8E002", [Parameter(Obavezno = $false)] [string]$WavePrefix = "SecureBoot-Rollout", [Parameter(Obavezno = $false)] [int]$WaveNumber = 1, [Parameter(Obavezno = $false)] [niska]$TargetOU, [Parameter(Obavezno = $false)] [bulov]$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 ' - Suva $DryRun # Konvertuj u očekivani povratni format vrati @{ Uspeh = $result. Uspeh Primenjeno = $result. Ažurirani računari Preskočeno = 0 Nije uspelo = if ($result. Računari nisu uspeli) { $result. Računara nije uspela } još { 0 } Rezultati = @() } }
# ============================================================================ # OMOGUĆI PRIMENU ZADATKA # ============================================================================ # Primenite Enable-SecureBootUpdateTask.ps1 na uređaje sa onemogućenim planiranim zadatkom.# Koristi GPO sa neposredno planiranim zadatkom koji se pokreće jednom.
function Deploy-EnableTaskGPO { <# . SINOPSIS Primenite Enable-SecureBootUpdateTask.ps1 putem GPO planiranog zadatka.. OPIS Kreira GPO koji primenjuje jednokratni planirani zadatak kako bi omogućio Secure-Boot-Update scheduled task on target devices.. PARAMETER TargetOU OU sa povezivanjem GPO-a.. PARAMETER TargetHostnames Imena hostova uređaja sa onemogućenim zadatkom (iz izveštaja o agregaciji).. PARAMETER DryRun Ako je tačno, evidentirajte samo ono što bi bilo urađeno.#> param( [Parameter(Obavezno = $false)] [niska]$TargetOU, [Parameter(Obavezno = $true)] [niz]$TargetHostnames, [Parameter(Obavezno = $false)] [bulov]$DryRun = $false ) $GPOName = "SecureBoot-EnableTask-Remediation" $SecurityGroupName = "SecureBoot-EnableTask-devices" $TaskName = "SecureBoot-EnableTask-OneTime" $TaskDescription = "Jednokratni zadatak za omogućavanje planiranog zadatka bezbednog pokretanja i ažuriranja" Write-Log "=" * 70 "INFO" Write-Log "PRIMENA ENABLE TASK REMEDIATION" "INFO" Write-Log "=" * 70 "INFO" Write-Log "Ciljni uređaji: $($TargetHostnames.Count)" "INFORMACIJE" Write-Log "GPO: $GPOName" "INFORMACIJE" Write-Log "Bezbednosna grupa: $SecurityGroupName" "INFORMACIJE" ako ($DryRun) { Write-Log "[DRYRUN] bi kreirao GPO: $GPOName" "INFORMACIJE" Write-Log "[DRYRUN] Kreira bezbednosnu grupu: $SecurityGroupName" "INFORMACIJE" Write-Log "[DRYRUN] Dodao bi $($TargetHostnames.Count) računare u grupu" "INFORMACIJE" Write-Log "[DRYRUN] Primenjuje jednokratni planirani zadatak kako bi se omogućilo bezbedno pokretanje-ažuriranje" "INFORMACIJE" Write-Log "[DRYRUN] Povezalo bi GPO sa: $TargetOU" "INFORMACIJE" vrati @{ Uspeh = $true ComputersAdded = 0 DryRun = $true } } pokušajte { # Uvoz potrebnih modula Import-Module GroupPolicy - Tačka greške Import-Module ActiveDirectory -Prekid radnje } hvatanje { Write-Log "Uvoz potrebnih modula nije uspeo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } # 1. korak: Kreiranje ili preuzimanje GPO-a $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue ako ($gpo) { Write-Log "GPO već postoji: $GPOName" "INFORMACIJE" } još { pokušajte { $gpo = New-GPO -Name $GPOName -Comment "Secure Boot Task Enable Remediation - Created $(Get-Date -Format 'yyyy-MM-dd HH:mm')" Write-Log "Napravljeni GPO: $GPOName" "U redu" } hvatanje { Write-Log "Kreiranje GPO-a nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } } # 2. korak: Primena xml-a planiranog zadatka u GPO SYSVOL # Zadatak pokreće PowerShell komandu radi omogućavanja zadatka bezbednog pokretanja pokušajte { $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Smernice\{$($gpo. Id)}\Machine\Preferences\ScheduledTasks" ako (-ne (probna putanja $sysvolPath)) { New-Item -ItemType direktorijum -Putanja $sysvolPath -Force | Bez vrednosti } # PowerShell komanda za omogućavanje zadatka bezbednog pokretanja i ažuriranja $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 }' # Komanda "Kodiraj" za ugrađivanje bezbednog XML-a $encodedCommand = [Konvertuj]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableCommand)) $taskGuid = [guid]::NewGuid(). ToString("B"). ToUpper() # GPP Scheduled Task XML - Trenutni zadatak koji se pokreće jednom $gppTaskXml = @" <?xml version="1.0" encoding="utf-8"?> <ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"> <ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="$TaskName" image="0" changed="$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" uid="$taskGuid" removePolicy="1" userContext="0"> <Properties action="C" name="$TaskName" runAs="NT AUTHORITY\SYSTEM" logonType="S4U"> <Task version="1.3"> <za registraciju informacija> <Opis>$TaskDescription</Opis> </RegistrationInfo> <principala> <ID="Author"> <userId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </principal> </Principals> <postavke> <IdleSettings> <trajanje>PT5M</Trajanje> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>false</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <StartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <AllowStartOnDemand>true</AllowStartOnDemand> <je>true</Enabled> <skriveno>pogrešno</Skriveno> <ExecutionTimeLimit>PT1H</ExecutionTimeLimit> <prioritet>7</Prioritet> <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter> </Settings> <radnje> <exec> <komanda>powershell.exe</Command> <argumenata>-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</Argumenti> </Exec> </Actions> </Task> </Svojstva> </ImmediateTaskV2> </ScheduledTasks> "@ $gppTaskXml | Out-File -FilePath (join-path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force Write-Log "Primenjeni jednokratni planirani zadatak u GPO: $TaskName" "U redu" } hvatanje { Write-Log "Primena XML-a planiranog zadatka nije uspela: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } # 3. korak: Kreiranje ili preuzimanje bezbednosne grupe $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue ako (-ne $group) { pokušajte { $group = New-ADGroup - Ime $SecurityGroupName ' -Bezbednost Grupne kategorije ' -GroupScope DomainLocal ' -Description "Computers with disabled Secure-Boot-Update task - targeted for remediation" ' ' -Prolaz Write-Log "Kreirana bezbednosna grupa: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Kreiranje bezbednosne grupe nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = $_. Exception.Message } } } još { Write-Log "Bezbednosna grupa postoji: $SecurityGroupName" "INFORMACIJE" } # 4. korak: Dodavanje računara u bezbednosnu grupu $added = 0 $failed = 0 foreach ($hostname in $TargetHostnames) { pokušajte { $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue $added++ } hvatanje { $failed++ Write-Log "Računar nije pronađen u OD-u: $hostname" "WARN" } } Write-Log "Dodat je $added u bezbednosnu grupu ($failed nije pronađen u AD)" "U redu" # 5. korak: Konfigurisanje bezbednosnog filtriranja u GPO pokušajte { Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType grupa -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType grupa -PermissionLevel GpoApply -ErrorAction Stop Write-Log "Konfigurisano bezbednosno filtriranje za: $SecurityGroupName" "U redu" } hvatanje { Write-Log "Konfigurisanje bezbednosnog filtriranja nije uspelo: $($_. Exception.Message)" "WARN" } # 6. korak: Povezivanje GPO-a sa OU ako ($TargetOU) { pokušajte { $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue | Select-Object -ExpandProperty GpoLinks | Where-Object { $_. DisplayName - eq $GPOName } ako (-ne $existingLink) { New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop Write-Log "Povezan GPO sa: $TargetOU" "U redu" } još { Write-Log "GPO je već povezan sa ciljnim OU" "INFORMACIJE" } } hvatanje { Write-Log "Povezivanje GPO-a sa OU nije uspelo: $($_. Exception.Message)" "ERROR" vrati @{ Uspeh = $false; Greška = "GPO veza nije uspela: $($_. Exception.Message)" } } } još { Write-Log "Nije naveden nijedan CiljOU – GPO će morati ručno da se poveže" "WARN" } Write-Log "" "INFO" Write-Log "OMOGUĆI PRIMENU ZADATKA DOVRŠENO" "U redu" Write-Log "Uređaji će pokrenuti zadatak omogućavanja pri sledećem GPO osvežavanju (gpupdate)" "INFORMACIJE" Write-Log "Zadatak se pokreće jednom kao SISTEM i omogućava bezbedno pokretanje-ažuriranje" "INFORMACIJE" Write-Log "" "INFO" vrati @{ Uspeh = $true ComputersAdded = $added ComputersFailed = $failed GPOName = $GPOName SecurityGroup = $SecurityGroupName } }
# ============================================================================ # OMOGUĆI ZADATAK NA ONEMOGUĆENIM UREĐAJIMA # ============================================================================ ako ($EnableTaskOnDisabled) { Write-Host "" Write-Host ("=" * 70) -Boja prednjeg plana žuta Write-Host " ENABLE TASK REMEDIATION - Ispravljanje onemogućenih planiranih zadataka" -Boja prednjeg plana Žuta Write-Host ("=" * 70) -Boja prednjeg plana žuta Write-Host "" # Pronalaženje uređaja sa onemogućenim zadatkom iz agregatnih podataka ako (-ne $AggregationInputPath) { Write-Host "GREŠKA: -AgregationInputPath je potreban da bi se identifikovali uređaji sa onemogućenim zadatkom" -Boja prednjeg plana Crvena boja Write-Host "Upotreba: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <putanja> -ReportBasePath <putanja>" -Boja prednjeg plana Siva izlaz 1 } Write-Host "Skeniranje uređaja sa onemogućenim zadatkom bezbednog pokretanja-ažuriranja..." -Prednji planColor Cyan # Učitavanje JSON datoteka i pronalaženje uređaja sa onemogućenim zadatkom $jsonFiles = Get-ChildItem -$AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_. Ime –notmatch "ScanHistory|Status primene|RolloutPlan" } $disabledTaskDevices = @() foreach ($file in $jsonFiles) { pokušajte { $device = Get-Content $file. FullName - Raw | ConvertFrom-Json ako ($device. SecureBootTaskEnabled -eq $false -or $device. SecureBootTaskStatus -eq "Disabled" -or $device. SecureBootTaskStatus -eq "NotFound") { # Uključite samo uređaje koji već nisu ažurirani (nema događaja 1808) if ([int]$device. Event1808Count -eq 0) { $disabledTaskDevices += $device. Ime hosta } } } hvatanje { # Preskoči nevažeće datoteke } } $disabledTaskDevices = $disabledTaskDevices | Select-Object -jedinstveno if ($disabledTaskDevices.Count -eq 0) { Write-Host "" Write-Host "Nije pronađen nijedan uređaj sa onemogućenim zadatkom bezbednog pokretanja i ažuriranja". -Zelena boja prednjeg plana Write-Host "Svi uređaji imaju zadatak omogućen ili su već ažurirani". -Boja prednjeg plana siva izađi 0 } Write-Host "" Write-Host "Pronađeni $($disabledTaskDevices.Count) uređaji sa onemogućenim zadatkom:" -Boja prednjeg plana žuta $disabledTaskDevices | Select-Object -Prvih 20 | ForEach-Object { Write-Host " - $_" - Boja prednjeg plana Siva } if ($disabledTaskDevices.Count -gt 20) { Write-Host " ... i $($disabledTaskDevices.Count - 20) više" -Boja prednjeg plana siva } Write-Host "" # Deploy the Enable Task GPO $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun ako ($result. Uspeh) { Write-Host "" Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green Write-Host " Računari dodati u bezbednosnu grupu: $($result. ComputersAdded)" -Prednji planKolor cijan ako ($result. Računara nije uspela -gt 0) { Write-Host " Računari nisu pronađeni u AD-u: $($result. ComputersFailed)" -Boja prednjeg plana Žuta } Write-Host "" Write-Host "SLEDEĆI KORACI:" - Boja prednjeg plana bela Write-Host " 1. Uređaji će primiti GPO pri sledećem osvežavanju (gpupdate /force)" - Boja prednjeg plana sivog Write-Host " 2. Jednokratni zadatak će omogućiti bezbedno pokretanje-ažuriranje" – boja prednjeg plana siva Write-Host " 3. Ponovno pokretanje agregatne funkcije radi verifikacije zadatka je sada omogućeno" - Boja prednjeg plana siva } još { Write-Host "" Write-Host "NEUSPEŠNO: nije bilo moguće primeniti enable Task GPO" -ForegroundColor Red Write-Host "Greška: $($result. Greška)" -Boja prednjeg plana Crvena boja } izađi 0 }
# ============================================================================ # GLAVNA PETLJA ORKESTRACIJE # ============================================================================
Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana– cijan Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - CONTINUOUS DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 80) -Boja prednjeg plana– cijan Write-Host ""
if ($DryRun) { Write-Host "[REŽIM SUVOG IZVRŠAVANJA]" -Magenta boja prednjeg plana }
if ($UseWinCS) { Write-Host "[WinCS MODE]" -Boja prednjeg plana žuta Write-Host "Korišćenje WinCsFlags.exe GPO/AvailableUpdatesPolicy" -Boja prednjeg plana žuta Write-Host "WinCS ključ: $WinCSKey" - Boja prednjeg plana siva Write-Host "" }
Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Putanja unosa: $AggregationInputPath" "INFORMACIJE" Write-Log "Putanja izveštaja: $ReportBasePath" "INFORMACIJE" ako ($UseWinCS) { Write-Log "Metod primene: WinCS (WinCsFlags.exe /apply --key '"$WinCSKey'")" "INFO" } još { Write-Log "Metod primene: GPO (AvailableUpdatesPolicy)" "INFO" }
# Resolve TargetOU - default to domain root for domain-wide coverage # Potrebno samo za metod primene GPO (WinCS ne zahteva AD/GPO) ako (-ne $UseWinCS -a -not $TargetOU) { pokušajte { # Isprobajte više metoda da biste dobili DN domena $domainDN = $null # Metod 1: Get-ADDomain (zahteva RSAT-AD-PowerShell) pokušajte { Import-Module ActiveDirectory -Prekid radnje $domainDN = (Get-ADDomain -ErrorAction Stop). DistinguishedName } hvatanje { Write-Log "Get-ADDomain nije uspeo: $($_. Exception.Message)" "WARN" } 2. metod: Koristite rootDSE putem ADSI ako (-ne $domainDN) { pokušajte { $rootDSE = [ADSI]"LDAP://RootDSE" $domainDN = $rootDSE.defaultNamingContext.ToString() } hvatanje { Write-Log "ADSI RootDSE nije uspelo: $($_. Exception.Message)" "WARN" } } # Metod 3: Raščlanjivanje iz članstva u domenu računara ako (-ne $domainDN) { pokušajte { $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain() $domainDN = "DC=" + ($domain. Ime - zamena '\.', ',DC=') } hvatanje { Write-Log "GetComputerDomain nije uspeo: $($_. Exception.Message)" "WARN" } } ako ($domainDN) { $TargetOU = $domainDN Write-Log "Cilj: osnovni direktorijum domena ($domainDN) – GPO će primeniti širok domen putem filtriranja bezbednosne grupe" "INFORMACIJE" } još { Write-Log "Nije moguće utvrditi DN domena – GPO će biti kreiran, ali NIJE POVEZANO!" "GREŠKA" Write-Log "Navedite -TargetOU parametar ili povežite GPO ručno nakon kreiranja" "GREŠKA" $TargetOU = $null } } hvatanje { Write-Log "Nije moguće dobiti DN domena – GPO će biti kreiran, ali ne i povezan. Povežite ručno ako je potrebno." "UPOZORI" Write-Log "Greška: $($_. Exception.Message)" "WARN" $TargetOU = $null } } još { Write-Log "Ciljni OU: $TargetOU" "INFO" }
Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Interval ankete: $PollIntervalMinutes minuta" "INFORMACIJE" ako ($LargeScaleMode) { Write-Log "LargeScaleMode omogućen (grupna veličina: $ProcessingBatchSize, uzorak evidencije: $DeviceLogSampleSize)" "INFORMACIJE" }
# ============================================================================ # PREDUSLOV ZA PROVERU: Proverite da li je otkrivanje primenjeno i da li radi # ============================================================================
Write-Host "" Write-Log "Provera preduslova..." "INFORMACIJE"
$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath ako (-$detectionCheck.IsDeployed) { Write-Log $detectionCheck.Poruka "GREŠKA" Write-Host "" Write-Host "OBAVEZNO: Prvo primeni infrastrukturu otkrivanja:" -Boja prednjeg plana žuta Write-Host " 1. Pokreni: Deploy-GPO-SecureBootCollection.ps1 -OUPath 'OU=...' -OutputPath '\server\SecureBootLogs$'" -ForegroundColor Cyan Write-Host " 2. Sačekajte da uređaji prijavljaju (12-24 časa)" -Prednji planColor Cyan Write-Host " 3. Ponovo pokrenite ovaj orkestrator " - Prednji planColor Cyan Write-Host "" ako (-ne $DryRun) { Vratiti } } još { Write-Log $detectionCheck.Poruka "U redu" }
# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Ažuriranost podataka: $($freshness. TotalFiles) datoteke, $($freshness. FreshFiles) sveže (<24h), $($freshness. StaleFiles) zaoštravanje (>72h)" "INFORMACIJE" ako ($freshness. Upozorenje) { Write-Log $freshness. Upozorenje "UPOZORENJE" }
# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() ako ($AllowListPath -ili $AllowADGroup) { $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup if ($allowedHostnames.Count -gt 0) { Write-Log "AllowList: ONLY $($allowedHostnames.Count) uređaji će biti u razmatranju za primenu" "INFORMACIJE" } još { Write-Log "AllowList je naveden, ali nije pronađen nijedan uređaj – ovo će blokirati sve primene!" "UPOZORI" } }
# Load VIP/exclusion list (BlockList) $excludedHostnames = @() ako ($ExclusionListPath - ili $ExcludeADGroup) { $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup if ($excludedHostnames.Count -gt 0) { Write-Log "VIP izuzimanje: $($excludedHostnames.Count) uređaji će biti preskočeni iz primene" "INFORMACIJE" } }
# Load state $rolloutState = Get-RolloutState $blockedBuckets = Get-BlockedBuckets $adminApproved = Get-AdminProprovedeno $deviceHistory = Get-DeviceHistory
if ($rolloutState.Status -eq "NotStarted") { $rolloutState.Status = "InProgress" $rolloutState.StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Log "Započinjanje nove primene" "WAVE" }
Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Blokirane kontejnere: $($blockedBuckets.Count)" "INFO"
# Main loop - runs until all eligible devices are updated $iterationCount = 0 dok ($true) { $iterationCount++ Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana Bela Write-Log "=== ITERATION $iterationCount ===" "WAVE" Write-Host ("=" * 80) -Boja prednjeg plana Bela # 1. korak: pokretanje agregacije Write-Log "1. korak: pokretanje agregacije..." "INFORMACIJE" # Orchestrator uvek ponovo koristi jednu fasciklu (LargeScaleMode) da bi se izbeglo bloat na disku # Administratori koji koriste agregator ručno dobijaju fascikle sa vremenskom oznakom za snimke tačaka u vremenu $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current" # Proverite svežinu podataka pre agregiranja $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Ažuriranost podataka: $($freshness. FreshFiles)/$($freshness. TotalFiles) uređaji prijavljeni u poslednjih 24h" "INFO" ako ($freshness. Upozorenje) { Write-Log $freshness. Upozorenje "UPOZORENJE" } $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1" $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json" $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json" if (probna putanja $aggregateScript) { ako (-ne $DryRun) { # Orchestrator uvek koristi striming + inkrementalno za efikasnost # Agregator automatski podiže na PS7 ako je dostupan za najbolje performanse $aggregateParams = @{ InputPath = $AggregationInputPath OutputPath = $aggregationPath StreamingMode = $true InkrementalMode = $true SkipReportIfUnchanged = $true ParallelThreads = 8 } # Dodaj rezime objavljene stavke ako postoji (za podatke o štićenosti/projekciji) if (probna putanja $rolloutSummaryPath) { $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath } & $aggregateScript @aggregateParams # Prikaži komandu za generisanje cele HTML kontrolne table sa tabelama uređaja Write-Host "" Write-Host "Da biste generisala kompletnu HTML kontrolnu tablu sa tabelama proizvođača/modela, pokrenite:" -Boja prednjeg plana Žuta Write-Host " $aggregateScript -InputPath '"$AggregationInputPath'" -OutputPath '"$aggregationPath'"" -Boja prednjeg plana Žuta Write-Host "" } još { Write-Log "[DRYRUN] Će pokrenuti agregaciju" "INFORMACIJE" # In DryRun, use existing agregation data from ReportBasePath directly $aggregationPath = $ReportBasePath } } $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # 2. korak: Učitavanje trenutnog statusa uređaja Write-Log "2. korak: učitavanje statusa uređaja..." "INFORMACIJE" $notUptodateCsv = Get-ChildItem -Putanja $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue | Where-Object { $_. Name -notlike "*Buckets*" } | Sort-Object Vreme poslednjeg upisivanja -Opadajući | Select-Object - Prvih 1 ako (-ne $notUptodateCsv -a -not $DryRun) { Write-Log "Nisu pronađeni podaci o agregaciji. Čeka se..." "UPOZORI" Start-Sleep - sekunde ($PollIntervalMinutes * 60) Nastavite } $notUpdatedDevices = if ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } else { @() } Write-Log "Uređaji nisu ažurirani: $($notUpdatedDevices.Count)" "INFORMACIJE" $notUpdatedIndexes = Get-NotUpdatedIndexes -Uređaji $notUpdatedDevices # 3. korak: ažuriranje istorije uređaja (praćenje po imenima hosta) Write-Log "3. korak: ažuriranje istorije uređaja..." "INFORMACIJE" Update-DeviceHistory -CurrentDevices $notUpdatedDevices -DeviceHistory $deviceHistory Save-DeviceHistory - istorija $deviceHistory # 4. korak: Provera blokiranih kontejnera (nedostupni uređaji) $existingBlockedCount = $blockedBuckets.Count Write-Log "4. korak: Provera blokiranih kontejnera (provera da li postoje blokirani uređaji tokom čekanja)..." "INFORMACIJE" if ($existingBlockedCount -gt 0) { Write-Log "Trenutno blokirani kontejneri iz prethodnih pokretanje: $existingBlockedCount" "INFORMACIJE" } if ($adminApproved.Count -gt 0) { Write-Log "Administrator odobrene kontejnere (neće biti ponovo blokirane): $($adminApproved.Count)" "INFORMACIJE" } $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState -BlockedBuckets $blockedBuckets -AdminApproveden $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun if ($newlyBlocked.Count -gt 0) { Save-BlockedBuckets - Blokirano $blockedBuckets Write-Log "Novo blokirane kontejnere (ova iteracija): $($newlyBlocked.Count)" "BLOKIRANO" } # 4. korak: Automatsko deblokiranje kontejnera u kojima su uređaji ažurirani $autoUnblocked = Update-AutoUnblockedBuckets -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -NotUpdatedDevices $notUpdatedDevices -ReportBasePath $ReportBasePath -NotUpdatedIndexes $notUpdatedIndexes -LogSampleSize $DeviceLogSampleSize if ($autoUnblocked.Count -gt 0) { Save-BlockedBuckets - Blokirano $blockedBuckets Write-Log "Automatski deblokirana kontejnera (uređaji ažurirani): $($autoUnblocked.Count)" "U redu" } # 5. korak: Izračunavanje preostalih uređaja koji ispunjavaju uslove $eligibleCount = 0 foreach ($device in $notUpdatedDevices) { $bucketKey = Get-BucketKey $device if (-not $blockedBuckets.Contains($bucketKey)) { $eligibleCount++ } } Write-Log "Preostali uređaji koji ispunjavaju uslove: $eligibleCount" "INFORMACIJE" Write-Log "Blokirane kontejnere: $($blockedBuckets.Count)" "INFO" # 6. korak: Provera dovršenja if ($eligibleCount -eq 0) { Write-Log "ROLLOUT COMPLETE - Ažurirani su svi uređaji koji ispunjavaju uslove!" "U redu" $rolloutState.Status = "Dovršeno" $rolloutState.CompletedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Save-RolloutState - Status $rolloutState Slomiti } # 6. korak: Generiši i primeni sledeći talas Write-Log "6. korak: Generisanje talasa primene..." "INFORMACIJE" $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames # Proverite da li imamo uređaje za primenu ($waveDevices mogu biti $null, prazni ili sa stvarnim uređajima) $hasDevices = $waveDevices -i @($waveDevices | Where-Object { $_ }). Count -gt 0 ako ($hasDevices) { # Broj talasa u inkrementu samo kada imamo uređaje za primenu $rolloutState.CurrentWave++ Write-Log "Wave $($rolloutState.CurrentWave): $(@($waveDevices). Count) uređaji" "WAVE" # Primeni GPO pomoću ugrađene funkcije $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)" $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)" $hostnames = @($waveDevices | ForEach-Object { ako ($_. Ime hosta) { $_. Ime hosta } elseif ($_. HostName) { $_. HostName } još { $null } } | Where-Object { $_ }) # Sačuvaj datoteku sa imenima hosta za referencu/nadzor $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt" $hostnames | Out-File $hostnamesFile – šifrovanje UTF8 # Proverite da li imamo imena hostova na koje treba da se primene ako ($hostnames. Broj -eq 0) { Write-Log "U talasu $($rolloutState.CurrentWave) nisu pronađena važeća imena hosta - uređajima možda nedostaje svojstvo "Ime Hostname" "WARN" Write-Log "Preskakanje primene za ovaj talas – provera podataka o uređaju" "WARN" # I dalje čekaj pre sledećeg iteracije ako (-ne $DryRun) { Write-Log "Spavanje na $PollIntervalMinutes minuta pre nego što pokušate ponovo..." "INFORMACIJE" Start-Sleep - Sekunde ($PollIntervalMinutes * 60) } Nastavite } Write-Log "Primena na $($hostnames. Count) imena hostova u Wave $($rolloutState.CurrentWave)" "INFO" # Primeni pomoću WinCS ili GPO metoda zasnovanog na -UseWinCS parametru ako ($UseWinCS) { # WinCS metod: Kreiranje GPO sa planiranim zadatkom za pokretanje WinCsFlags.exe kao SYSTEM za svaku krajnju tačku Write-Log "Korišćenje WinCS metoda primene (ključ: $WinCSKey)" "WAVE" $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames ' -WinCSKey $WinCSKey ' -WavePrefix $WavePrefix ' -WaveNumber $rolloutState.CurrentWave ' - TargetOU $TargetOU ' - Suva $DryRun ako (-ne $wincsResult.Uspeh) { Write-Log "WinCS primena je imala neuspeha – primenjeno: $($wincsResult.Applied), Nije uspelo: $($wincsResult.Failed)" "WARN" } još { Write-Log "WinCS primena je uspela – primenjeno: $($wincsResult.Applied), Preskočeno: $($wincsResult.Preskočeno)" "U redu" } # Sačuvaj WinCS rezultate za nadzor $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json" $wincsResult | ConvertTo-Json -Dubina 5 | Out-File $wincsResultFile – šifrovanje UTF8 } još { # GPO metod: Kreiranje GPO sa postavkom registratora AvailableUpdatesPolicy $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun ako (-ne $gpoResult) { Write-Log "GPO primena nije uspela – ponovo će pokušati sledeću iteraciju" "GREŠKA" } } # Talas zapisa u stanju $waveRecord = @{ WaveNumber = $rolloutState.CurrentWave StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss" DeviceCount = @($waveDevices). Raиuna Uređaji = @($waveDevices | ForEach-Object { @{ Ime hosta = ako ($_. Ime hosta) { $_. Ime hosta } elseif ($_. HostName) { $_. HostName } još { $null } BucketKey = Get-BucketKey $_ } }) } # Uverite se da je WaveHistory uvek niz pre dodavanja (sprečava probleme sa heštable objedinjavanjem) $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord) $rolloutState.TotalDevicesTargeted += @($waveDevices). Raиuna Save-RolloutState – status $rolloutState Write-Log "Wave $($rolloutState.CurrentWave) primenjena. Čekanje $PollIntervalMinutes minuta..." "U redu" } još { # Prikaži status primenjenih uređaja koji čekaju na ispravke Write-Log "" "INFO" Write-Log "========== SVI UREĐAJI KOJI SU PRIMENJENI – ČEKA SE NA STATUS ==========" "INFORMACIJE" # Preuzmi sve primenjene uređaje iz istorije talasa $allDeployedLookup = @{} foreach ($wave in $rolloutState.WaveHistory) { ($device u $wave. Uređaji) { ako ($device. Ime hosta) { $allDeployedLookup[$device. Ime hosta] = @{ Ime hosta = $device. Ime hosta BucketKey = $device. Ključ kontejnera DeployedAt = $wave. Započeto WaveNumber = $wave. WaveNumber } } } } $allDeployedDevices = @($allDeployedLookup.Values) if ($allDeployedDevices.Count -gt 0) { # Pronađite koji primenjeni uređaji su i dalje na čekanju (na listi NotUpdated) $stillPendingCount = 0 $noLongerPendingCount = 0 $pendingSample = @() foreach ($deployed in $allDeployedDevices) { if ($notUpdatedIndexes.HostSet.Contains($deployed. Ime hosta)) { $stillPendingCount++ if ($pendingSample.Count -lt $DeviceLogSampleSize) { $pendingSample += $deployed. Ime hosta } } još { $noLongerPendingCount++ } } # Preuzmite stvarne ažurirane brojeve iz agregacije - razlikovanje događaja 1808 naspram UEFICA2023Status $summaryCsv = Get-ChildItem -Putanja $aggregationPath -Filtriraj "*rezime*.csv" | Sort-Object vreme poslednjeg upisivanja -Opadajući | Select-Object - Prvih 1 $actualUpdated = 0 $totalDevicesFromSummary = 0 $event 1808Count = 0 $uefiStatusUpdated = 0 $needsRebootSample = @() ako ($summaryCsv) { $summary = Import-Csv $summaryCsv.FullName | Select-Object - Prvih 1 ako ($summary. Ažurirano) { $actualUpdated = [int]$summary. Ažurirano } ako ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices } } # Izračunaj protok u istoriji talasa (uređaji se ažuriraju po danu) $devicesPerDay = 0 if ($rolloutState.StartedAt -and $actualUpdated -gt 0) { $startDate = [datetime]::P arse($rolloutState.StartedAt) $daysElapsed = ((Get-Date) – $startDate). Ukupni dani if ($daysElapsed -gt 0) { $devicesPerDay = $actualUpdated / $daysElapsed } } # Sačuvajte rezime objavljene stavke sa projekcijama koje su svesne vikenda # Koristite broj notUptodate agregatora (isključuje SB OFF uređaje) radi doslednosti $notUpdatedCount = if ($summary -i $summary. NotUptodate) { [int]$summary. NotUptodate } else { $totalDevicesFromSummary - $actualUpdated } Save-RolloutSummary - Država $rolloutState ' -TotalDevices $totalDevicesFromSummary ' -UpdatedDevices $actualUpdated ' -NotUpdatedDevices $notUpdatedCount ' -DevicesPerDay $devicesPerDay # Proverite neobražene podatke za uređaje sa UEFICA2023Status=Updated, ali nema događaja 1808 (potrebno je ponovo pokrenuti) $dataFiles = Get-ChildItem -Putanja $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue $totalDataFiles = @($dataFiles). Raиuna $batchSize = [Matematika]::Max(500, $ProcessingBatchSize) ako ($LargeScaleMode) { $batchSize = [Matematika]::Max(2000, $ProcessingBatchSize) }
if ($totalDataFiles -gt 0) { za ($idx = 0; $idx -lt $totalDataFiles; $idx += $batchSize) { $end = [Matematika]::Min($idx + $batchSize - 1, $totalDataFiles - 1) $batchFiles = $dataFiles[$idx.. $end]
foreach ($file in $batchFiles) { pokušajte { $deviceData = Get-Content $file. FullName - Raw | ConvertFrom-Json $hostname = $deviceData.Ime hosta if (-not $hostname) { continue } $has 1808 = [int]$deviceData.Event1808Count -gt 0 $hasUefiUpdated = $deviceData.UEFICA2023Status -eq "Ažurirano" if ($has 1808) { $event 1808Count++ } elseif ($hasUefiUpdated) { $uefiStatusUpdated++ if ($needsRebootSample.Count -lt $DeviceLogSampleSize) { $needsRebootSample += $hostname } } } hvatanje { } }
Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{ Event1808Count = $event 1808Count UEFIUpdatedAwaitingReboot = $uefiStatusUpdated } } } Write-Log "Ukupan broj primenjenih: $($allDeployedDevices.Count)" "INFORMACIJE" Write-Log "Ažurirano (potvrđen je događaj 1808): $event 1808Count" "U redu" if ($uefiStatusUpdated -gt 0) { Write-Log "Ažurirano (UEFICA2023Status=Updated, čeka se ponovno pokretanje sistema): $uefiStatusUpdated" "U redu" $rebootSuffix = if ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated - $DeviceLogSampleSize) još)" } još { "" } Write-Log " Uređajima potrebno ponovno pokretanje za događaj 1808 (uzorak): $($needsRebootSample -join ', ')$rebootSuffix" "INFO" Write-Log " Ovi uređaji će prijaviti događaj 1808 nakon sledećeg ponovnog pokretanja aplikacije" "INFORMACIJE" } Write-Log "Više nije na čekanju: $noLongerPendingCount (uključuje SecureBoot ISKLJUČENO, uređaji koji nedostaju)" "INFORMACIJE" Write-Log "Čeka se status: $stillPendingCount" "INFORMACIJE" if ($stillPendingCount -gt 0) { $pendingSuffix = if ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount - $DeviceLogSampleSize) još)" } još { "" } Write-Log "Uređaji na čekanju (uzorak): $($pendingSample -join ', ')$pendingSuffix" "WARN" } } još { Write-Log "Još uvek nije primenjen nijedan uređaj" "INFORMACIJE" } Write-Log "================================================================" "INFORMACIJE" Write-Log "" "INFO" } # Sačekajte pre sledeće iteracije ako (-ne $DryRun) { Write-Log "Spavanje na $PollIntervalMinutes minuta..." "INFORMACIJE" Start-Sleep - Sekunde ($PollIntervalMinutes * 60) } još { Write-Log "[DRYRUN] Čekaće $PollIntervalMinutes minuta" "INFORMACIJE" break # Exit after one iteration in dry run } }
# ============================================================================ # KONAČNI REZIME # ============================================================================
Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana – zelena boja prednjeg plana Write-Host " REZIME ROLLOUT ORCHESTRATOR" -Boja prednjeg plana – zelena Write-Host ("=" * 80) -Boja prednjeg plana – zelena boja prednjeg plana Write-Host ""
$finalState = Get-RolloutState $finalBlocked = Get-BlockedBuckets
Write-Host "Status: $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Ukupni talasi: $($finalState.CurrentWave)" Write-Host "Korisnici koji su ciljani: $($finalState.TotalDevicesTargeted)" Write-Host "Blokirane kontejnere: $($finalBlocked.Count)" -Prednji planColor $(if ($finalBlocked.Count -gt 0) { "Crveno" } još { "Zelena" }) Write-Host "Uređaji koji se prate: $($deviceHistory.Count)" -Boja prednjeg plana siva Write-Host ""
if ($finalBlocked.Count -gt 0) { Write-Host "BLOKIRANE GRUPE (potrebno je ručno redigovanje):" - Boja prednjeg plana crvena foreach ($key in $finalBlocked.Keys) { $info = $finalBlocked[$key] Write-Host " - $key" - Boja prednjeg plana Crvena boja Write-Host " Razlog: $($info. Razlog)" - Boja prednjeg plana siva } Write-Host "" Write-Host "Blokirana datoteka kontejnera: $blockedBucketsPath" -Boja prednjeg plana žuta }
Write-Host "" Write-Host "Datoteke stanja:" - Boja prednjeg plana– cijan Write-Host " Stanje primene: $rolloutStatePath" Write-Host " Blokirane kontejnere: $blockedBucketsPath" Write-Host " Istorija uređaja: $deviceHistoryPath" Write-Host ""