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 ""  

​​​​​​​

Da li vam je potrebna dodatna pomoć?

Želite još opcija?

Istražite pogodnosti pretplate, pregledajte kurseve za obuku, saznajte kako da obezbedite uređaj i još mnogo toga.