Kopirajte i zalijepite tu oglednu skriptu i po potrebi izmijenite za svoje okruženje:

<# . SINOPSIS     Orkestrator kontinuiranog uvođenja sigurnog pokretanja koji se pokreće dok se implementacija ne dovrši.

.DESCRIPTION     Ova skripta pruža potpunu automatizaciju za izdavanje certifikata sigurnog pokretanja:     1.      Generira valove za početak rada na temelju podataka o agregaciji     2. Stvara AD grupe i GPO za svaki val     3. Monitori za ažuriranja uređaja (događaj 1808)     4. Otkriva blokirane grupe (nedostupne uređaje)     5. Automatski se prebacuje na sljedeći val     6. Pokreće se dok se ne ažuriraju svi uređaji koji ispunjavaju uvjete          Kriteriji dovršetka:     - Nema uređaja preostalih u: Akcija Obavezno, Visoka pouzdanost, Promatranje, Privremeno pauzirano     - Izvan dosega (po dizajnu): nije podržano, sigurno pokretanje onemogućeno     - Neprekidno se pokreće do dovršetka – nema proizvoljnog ograničenja vala          Strategija razvoja:     - VISOKA POUZDANOST: svi uređaji u prvom valu (sigurni)     - POTREBNA RADNJA: progresivne dvostruke akcije (1→2→4→8...)          Logika blokiranja:     - Nakon MaxWaitHours, orchestrator pings uređaje koji nisu ažurirani     - Ako je uređaj UNREACHABLE (ping ne uspije) → grupa blokirana za istragu     - Ako je uređaj REACHABLE, ali nije ažuriran → čekanja (možda je potrebno ponovno pokretanje)     - Blokirane grupe su isključene dok ih administrator ne deblokira          Automatsko deblokiranje:     - Ako se uređaj u blokiranoj grupi kasnije prikazuje kao ažuriran (događaj 1808),       grupa se automatski deblokira i nastavlja se     - Time se rukuje uređajima koji su privremeno izvan mreže, ali su se vratili          Praćenje uređaja:     - prati uređaje prema nazivu glavnog računala (pretpostavlja da se nazivi ne mijenjaju tijekom postavljanja)     - Napomena: JSON zbirka ne sadrži jedinstveni ID računala; dodajte ga radi boljeg praćenja

.PARAMETER AggregationInputPath     Put do neobrađenih JSON podataka uređaja (iz skripte Otkrivanje)

.PARAMETER ReportBasePath     Osnovni put za izvješća o agregaciji

.PARAMETER TargetOU     Razlikovni naziv OU-a za povezivanje GPO-ova.Neobavezno – ako nije navedeno, GPO je povezan s korijenom domene radi pokrivenosti na razini domene.Filtriranje sigurnosnih grupa osigurava da samo ciljani uređaji primaju pravilnik.

.PARAMETER MaxWaitHours     Sati čekanja ažuriranja uređaja prije provjere dostupnosti.Nakon tog vremena, uređajima koji nisu ažurirani ping se.Nedostupni uređaji uzrokuju blokiranje grupe.Zadano: 72 (3 dana)

.PARAMETER PollIntervalMinutes     Minute između provjera stanja. Zadano: 1440 (1 dan)

.PARAMETER AllowListPath     Put do datoteke koja sadrži nazive glavnog računala do mogućnosti ALLOW za rollout (ciljano rollout).Podržava .txt (jedan naziv glavnog računala po retku) ili .csv (s stupcem Naziv Računala/Naziv Računala).Kada je to navedeno, samo će ti uređaji biti uključeni u početak.BlockList je i dalje primijenjen nakon AllowList.

.PARAMETER AllowADGroup     Naziv ad-sigurnosne grupe koja sadrži računalne račune za ALLOW.Primjer: "SecureBoot-Pilot-Computers" ili "Wave1-Devices"     Kada je navedeno, samo uređaji u ovoj grupi bit će uključeni u rollout.Kombiniranje s allowListPathom za ciljanje na temelju datoteka i za AD.

.PARAMETER ExclusionListPath     Put do datoteke koja sadrži nazive glavnog računala za ISKLJUČIVANJE iz primjene (VIP/izvršni uređaji).Podržava .txt (jedan naziv glavnog računala po retku) ili .csv (s stupcem Naziv Računala/Naziv Računala).Ti uređaji nikad neće biti obuhvaćeni valom za uvršćitenje.BlockList se primjenjuje NAKON allowList filtriranja.     . PARAMETER ExcludeADGroup     Naziv sigurnosne grupe ad-a koja sadrži račune računala koje treba izuzeti.Primjer: "VIP-Computers" ili "Executive-Devices"     Kombinirajte s ExclusionListPath za izuzimanja na temelju datoteka i za AD.

.PARAMETER UseWinCS     Koristite WinCS (Windows Configuration System) umjesto GPO/AvailableUpdatesPolicy.WinCS implementira omogućivanje sigurnog pokretanja pokretanjem WinCsFlags.exe izravno na svakoj krajnjoj točki.WinCsFlags.exe se u odjeljku KONTEKST SUSTAVA putem zakazanog zadatka.Ta je metoda korisna za:     - brža uvodi (neposredni učinak u odnosu na čekanje na obradu GPO-a)     - uređaji koji nisu pridruženi domeni     - 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     Tipka WinCS koja se koristi za omogućivanje sigurnog pokretanja.Zadano: F33E0C8E002     Taj ključ odgovara konfiguraciji implementacije sigurnog pokretanja.     . PARAMETAR DryRun     Pokaži što će se učiniti bez promjene

.PARAMETER ListBlockedBuckets     Prikaži sve trenutno blokirane grupe i izađi

.PARAMETER UnblockBucket     Deblokiranje određene grupe prema ključu i izlaz

.PARAMETER UnblockAll     Deblokiranje svih grupa i izlaz

.PARAMETER EnableTaskOnDisabled     Implementacija Enable-SecureBootUpdateTask.ps1 svim uređajima s onemogućenim zakazanim zadatkom.Stvara GPO s jednokratno zakazanim zadatkom koji pokreće skriptu Omogući s mogućnošću -Quiet.To je korisno za popravak uređaja s onemogućenim zadatkom sigurnog pokretanja.

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

.EXAMPLE     # Popis blokiranih grupa     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -ListBlockedBuckets

.EXAMPLE     # Deblokiranje određene grupe     .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "E:\SecureBootReports" -UnblockBucket "Dell_Latitude5520_BIOS1.2.3"

.EXAMPLE     # Izuzimanje VIP uređaja iz primjene pomoću tekstne datoteke     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExclusionListPath "C:\Admin\VIP-Devices.txt"

.EXAMPLE     # Izuzimanje uređaja u sigurnosnoj grupi servisa AD (npr. izvršna prijenosna računala)     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -ExcludeADGroup "VIP-Računala"

.EXAMPLE     # Koristite WinCS (Windows Configuration System) umjesto GPO/AvailableUpdatesPolicy     # WinCsFlags.exe se u kontekstu sustava na svakoj krajnjoj točki putem zakazanog zadatka     .\Start-SecureBootRolloutOrchestrator.ps1 '         -AggregationInputPath "\\server\SecureBootLogs$\Json" '         -ReportBasePath "E:\SecureBootReports" '         -UseWinCS '         -WinCSKey "F33E0C8E002" #>

[CmdletBinding()] param(     [Parameter(Mandatory = $false)]     [niz]$AggregationInputPath,     [Parameter(Mandatory = $false)]     [niz]$ReportBasePath,     [Parameter(Mandatory = $false)]     [niz]$TargetOU,     [Parameter(Mandatory = $false)]     [string]$WavePrefix = "SecureBoot-Rollout",     [Parameter(Mandatory = $false)]     [int]$MaxWaitHours = 72,     [Parameter(Mandatory = $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 = Uvrstite samo ove uređaje (ciljano pokretanje)     # BlockList = Isključi ove uređaje (nikad se neće uvoditi)     # Redoslijed obrade: AllowList prvi (ako je naveden), a zatim BlockList     [Parameter(Mandatory = $false)]     [niz]$AllowListPath,     [Parameter(Mandatory = $false)]     [niz]$AllowADGroup,     [Parameter(Mandatory = $false)]     [niz]$ExclusionListPath,     [Parameter(Mandatory = $false)]     [niz]$ExcludeADGroup,     # ============================================================================     Parametri sustava # WinCS (Windows Configuration System)     # ============================================================================     # WinCS je alternativa implementaciji AvailableUpdatesPolicy GPO.                              # Koristi se WinCsFlags.exe na svakoj krajnjoj točki da bi omogućio rollout sigurnog pokretanja.# WinCsFlags.exe se u kontekstu sustava na krajnjoj točki.# Referenca: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe          [Parameter(Mandatory = $false)]     [switch]$UseWinCS,          [Parameter(Mandatory = $false)]     [string]$WinCSKey = "F33E0C8E002",          [Parameter(Mandatory = $false)]     [switch]$DryRun,          [Parameter(Mandatory = $false)]     [switch]$ListBlockedBuckets,          [Parameter(Mandatory = $false)]     [niz]$UnblockBucket,          [Parameter(Mandatory = $false)]     [switch]$UnblockAll,          [Parameter(Mandatory = $false)]     [switch]$EnableTaskOnDisabled )

$ErrorActionPreference = "Stop" $ScriptRoot = $PSScriptRoot $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Uzorci implementacije i nadzora"

# ============================================================================ # PROVJERA OVISNOSTI # ============================================================================

function Test-ScriptDependencies {     param(         [Parameter(Mandatory = $true)]         [niz]$ScriptDirectory,         [Parameter(Mandatory = $true)]         [string[]]$RequiredScripts     )     $missingScripts = @()     foreach ($script in $RequiredScripts) {         $scriptPath = Join-Path $ScriptDirectory $script         if (-not (Test-Path $scriptPath)) {             $missingScripts += $script         }     }     if ($missingScripts.Count -gt 0) {         Write-Host ""         Write-Host ("=" * 70) -Boja prednjeg plana Crvena         Write-Host " NEDOSTAJE ZAVISNOSTI" -Prednji planBoja Crvena         Write-Host ("=" * 70) -Crveni prednji planBoja         Write-Host ""         Write-Host "Nisu pronađene sljedeće potrebne skripte:" -Boja prednjeg plana Žuta         foreach ($script u $missingScripts) {             Write-Host " - $script" -ForegroundColor White         }         Write-Host ""         Write-Host "Preuzmite najnovije skripte iz:" -ForegroundColor Cyan         Write-Host " URL: $DownloadUrl" -ForegroundColor White         Write-Host " Idite na: '$DownloadSubPage'" -ForegroundColor White         Write-Host ""         Write-Host "Izdvoji sve skripte u isti direktorij i ponovno pokreni." -Boja prednjeg plana Žuta         Write-Host ""         povratna $false     }     vraćanje $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 }

# ============================================================================ # PROVJERA VALJANOSTI PARAMETARA # ============================================================================

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

if (-not $ReportBasePath) {     Write-Host "ERROR: -ReportBasePath is required". -ForegroundColor Red     izlaz 1 }

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

# ============================================================================ # GPO DETECTION - CHECK FOR DETECTION GPO # ============================================================================

if (-not $isAdminCommand -and -not $DryRun) {     $CollectionGPOName = "SecureBoot-EventCollection"     # Provjerite je li dostupan modul GroupPolicy     if (Get-Module -ListAvailable -Name GroupPolicy) {         Import-Module GroupPolicy –ErrorAction SilentlyContinue         Write-Host "Provjera otkrivanja GPO..." - Boja prednjeg plana Žuta         pokušajte {             # Provjerite postoji li GPO             $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue             if ($existingGpo) {                 Write-Host pronađen je " Detection GPO: $CollectionGPOName" -ForegroundColor Green             } još {                 Write-Host ""                 Write-Host ("=" * 70) -Boja prednjeg plana Žuta                 Write-Host " UPOZORENJE: OTKRIVANJE GPO NIJE PRONAĐENO" -Boja prednjeg plana Žuta                 Write-Host ("=" * 70) -Boja prednjeg plana Žuta                 Write-Host ""                 Write-Host "Otkrivanje GPO '$CollectionGPOName' nije pronađeno." -Boja prednjeg plana Žuta                 Write-Host "Bez ovog GPO-a neće se prikupljati podaci o uređaju". -Boja prednjeg plana Žuta                 Write-Host ""                 Write-Host "Za implementaciju GPO otkrivanja, pokreni:" -ForegroundColor Cyan                 Write-Host " .\Deploy-GPO-SecureBootCollection.ps1 -DomainName <domena> -AutoDetectOU" -ForegroundColor White                 Write-Host ""                 Write-Host "Želite li ipak nastaviti?                                     (Y/N)" - Boja prednjeg plana Žuta                 $response = Glavno računalo za čitanje                 if ($response -notmatch '^[Yy]') {                     Write-Host "Prekid. Najprije implementirajte GPO otkrivanja." -ForegroundColor Red                     izlaz 1                 }             }         } uhvatiti {             Write-Host " Nije moguće provjeriti GPO: $($_. Exception.Message)" -ForegroundColor Yellow         }     } još {         Write-Host " GroupPolicy modul nije dostupan - preskače GPO provjeru" -ForegroundColor Gray     }     Write-Host "" }

# ============================================================================ # PUTOVI DATOTEKA STANJA # ============================================================================

$stateDir = Join-Path $ReportBasePath "RolloutState" if (-not (Test-Path $stateDir)) {     New-Item -ItemType Direktorij -Put $stateDir -Force | Izlazna vrijednost -Null }

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

# ============================================================================ # PS 5.1 KOMPATIBILNOST: ConvertTo-Hashtable Ne, ne============================================================================ # ConvertFrom-Json -AsHashtable je samo PS7+. To omogućuje kompatibilnost.

function ConvertTo-Hashtable {     param(         [Parameter(ValueFromPipeline = $true)]         $InputObject     )     postupak {         if ($null -eq $InputObject) { return @{} }         if ($InputObject -is [System.Collections.IDictionary]) { return $InputObject }         if ($InputObject -is [PSCustomObject]) {             # Koristi [naručeno] za dosljedno naručivanje ključeva i sigurno rukovanje duplikatima             $hash = [naručeno]@{}             foreach ($prop u $InputObject.PSObject.Properties) {                 # Indeksirani zadatak sigurno rukuje duplikatima pisanjem preko                 $hash[$prop. Naziv] = ConvertTo-Hashtable $prop. Vrijednost             }             povratna $hash         }         if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {             return @($InputObject | ForEach-Object { ConvertTo-Hashtable $_ })         }         povratna $InputObject     } }

# ============================================================================ # ADMINISTRATORSKE NAREDBE: Popis/deblokiranje grupa # ============================================================================

if ($ListBlockedBuckets) {     Write-Host ""     Write-Host ("=" * 80) -Boja prednjeg plana Žuta     Write-Host " BLOKIRANE KANTE" - Boja prednjeg plana Žuta     Write-Host ("=" * 80) -Boja prednjeg plana Žuta     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         ako ($blocked. Count -eq 0) {             Write-Host "Nema blokiranih grupa". -Prednji planBoja Zelena         } još {             Write-Host "Ukupno blokirano: $($blocked. Count)" -ForegroundColor Red             Write-Host ""             ($key $blocked. tipke) {                 $info = $blocked[$key]                 Write-Host "Bucket: $key" -ForegroundColor Red                 Write-Host " blokirano na: $($info. BlockedAt)" - Boja prednjeg planaSiva                 Write-Host " Razlog: $($info. Reason)" -ForegroundColor Gray                 Write-Host " Neuspjeli uređaj: $($info. FailedDevice)" -Boja prednjeg planaSiva                 Write-Host " Zadnje izvješće: $($info. LastReported)" -ForegroundColor Gray                 Write-Host " Val: $($info. WaveNumber)" - Boja prednjeg planaSiva                 Write-Host " Uređaji u grupi: $($info. DevicesInBucket)" -Boja prednjeg plana Siva                 Write-Host ""             }             Write-Host "Deblokiranje grupe:"             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 datoteka blokiranih grupa". -ForegroundColor Green     }     Write-Host ""     izlaz 0 }     

if ($UnblockBucket) {     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         ako ($blocked. Contains($UnblockBucket)) {             $blocked je. Ukloni($UnblockBucket)             $blocked | ConvertTo-Json -Dubina 10 | Out-File $blockedBucketsPath -Encoding UTF8 -Force             # Dodaj na popis koji je odobrio administrator da biste spriječili ponovno blokiranje             $adminApproved = @{}             if (Test-Path $adminApprovedPath) {                 $adminApproved = Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable             }             $adminApproved[$UnblockBucket] = @{                 ApprovedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                 ApprovedBy = $env:USERNAME             }             $adminApproved | ConvertTo-Json -Dubina 10 | Out-File $adminApprovedPath -Encoding UTF8 -Force             Write-Host "Unblocked bucket: $UnblockBucket" -ForegroundColor Green             Write-Host "Dodano na popis odobren od strane administratora (neće se automatski ponovno blokirati)" -ForegroundColor Cyan         } još {             Write-Host "Bucket not found: $UnblockBucket" -ForegroundColor Yellow             Write-Host "Dostupne grupe:" - Boja prednjeg planaSiva             $blocked je. Tipke | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }         }     } još {         Write-Host "Nije pronađena datoteka blokiranih grupa". -Boja prednjeg plana Žuta     }     Write-Host ""     izlaz 0 }                          

if ($UnblockAll) {     Write-Host ""     if (Test-Path $blockedBucketsPath) {         $blocked = Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable         $count = $blocked. Računati         @{} | ConvertTo-Json | Out-File $blockedBucketsPath -Encoding UTF8 -Force         Write-Host "Unblocked all $count buckets". -ForegroundColor Green     } još {         Write-Host "Nije pronađena datoteka blokiranih grupa". -Boja prednjeg plana Žuta     }     Write-Host ""     izlaz 0 }

# ============================================================================ # FUNKCIJE POMOĆNIH PROGRAMA # ============================================================================

function Get-RolloutState {     if (Test-Path $rolloutStatePath) {         pokušajte {             $loaded = Get-Content $rolloutStatePath -Raw | ConvertFrom-Json | ConvertTo-Hashtable             # Provjerite postoje li obavezna svojstva             ako ($null -eq $loaded. CurrentWave) {                 throw "Invalid state file - missing CurrentWave"             }             # Provjerite je li WaveHistory uvijek polje (popravci PS5.1 JSON deserijalizacija)             ako ($null -eq $loaded. WaveHistory) (                 $loaded je. WaveHistory = @()             } elseif ($loaded. WaveHistory -isnot [polje]) {                 $loaded je. WaveHistory = @($loaded. WaveHistory)             }             povratna $loaded         } uhvatiti {             Write-Log "Otkriveno RolloutState.json oštećeno: $($_. Exception.Message)" "WARN"             Write-Log "Sigurnosno kopiranje oštećene datoteke i pokretanje nove" "UPOZORENJE"             $backupPath = "$rolloutStatePath.corrupted.$(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 -Encoding UTF8 -Force }

function Get-WeekdayProjection {     < broj     . SINOPSIS         Izračun projicirani datum završetka koji se računa za vikende (nema napretka na sub/ned)     #>     param(         [int]$RemainingDevices,         [double]$DevicesPerDay,         [datetime]$StartDate = (Get-Date)     )     if ($DevicesPerDay -le 0 -or $RemainingDevices -le 0) {         vrati @{             ProjectedDate = $null             WorkingDaysNeeded = 0             CalendarDaysNeeded = 0         }     }     # Izračun potrebnih radnih dana (osim vikenda)     $workingDaysNeeded = [matematika]::Ceiling($RemainingDevices / $DevicesPerDay)     # Pretvaranje radnih dana u kalendarske dane (dodavanje vikenda)     $currentDate = $StartDate.Date     $daysAdded = 0     $workingDaysAdded = 0     while ($workingDaysAdded -lt $workingDaysNeeded) {         $currentDate = $currentDate.AddDays(1)         $daysAdded++         Brojanje samo radnih dana         if ($currentDate.DayOfWeek -ne [DayOfWeek]::saturday -and             $currentDate.DayOfWeek -ne [DayOfWeek]::nedjelja) {             $workingDaysAdded++         }     }     vrati @{         ProjectedDate = $currentDate.ToString("yyyy-MM-dd")         WorkingDaysNeeded = $workingDaysNeeded         CalendarDaysNeeded = $daysAdded     } }                                  

function Save-RolloutSummary {     < broj     . SINOPSIS         Spremanje sažetka rollouta s informacijama o projekciji za prikaz nadzorne ploče     #>     param(         [raspršivanje]$State,         [int]$TotalDevices,         [int]$UpdatedDevices,         [int]$NotUpdatedDevices,         [double]$DevicesPerDay     )     $summaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     # Izračun projekcije u obliku vikenda     $projection = Get-WeekdayProjection -RemainingDevices $NotUpdatedDevices -DevicesPerDay $DevicesPerDay     $summary = @{         GeneratedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")         RolloutStartDate = $State.StartedAt         LastAggregation = $State.LastAggregation         CurrentWave = $State.CurrentWave         Status = $State.Status         # Broj uređaja         TotalDevices = $TotalDevices         UpdatedDevices = $UpdatedDevices         NotUpdatedDevices = $NotUpdatedDevices         PercentUpdated = ako ($TotalDevices -gt 0) { [matematika]:Round(($UpdatedDevices / $TotalDevices) * 100, 1) } else { 0 }         # Velocity metrics         DevicesPerDay = [matematika]::Round($DevicesPerDay, 1)         TotalDevicesTargeted = $State.TotalDevicesTargeted         TotalWaves = $State.CurrentWave         # Projekcija u obliku vikenda         ProjectedCompletionDate = $projection. ProjectedDate         WorkingDaysRemaining = $projection. WorkingDaysNeeded         CalendarDaysRemaining = $projection. CalendarDaysNeeded         # Napomena o isključenju vikenda         ProjectionNote = "Projektirani dovršetak isključuje vikende (sub/ned)"     }     $summary | ConvertTo-Json -Dubina 5 | Out-File $summaryPath -Encoding UTF8 -Force     Write-Log " Rollout summary saved: $summaryPath" "INFO"     vraćanje $summary }                                                             

function Get-BlockedBuckets {     if (test-path $blockedBucketsPath) {         return Get-Content $blockedBucketsPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     vrati @{} }

function Save-BlockedBuckets {     param($Blocked)     $Blocked | ConvertTo-Json -Dubina 10 | Out-File $blockedBucketsPath -Encoding UTF8 -Force }

function Get-AdminApproved {     if (Test-Path $adminApprovedPath) {         return Get-Content $adminApprovedPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     vrati @{} }

function Get-DeviceHistory {     if (Test-Path $deviceHistoryPath) {         return Get-Content $deviceHistoryPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable     }     vrati @{} }

function Save-DeviceHistory {     param($History)     $History | ConvertTo-Json -Dubina 10 | Out-File $deviceHistoryPath -Encoding UTF8 -Force }

function Save-ProcessingCheckpoint {     param(         [niz]$Stage,         [int]$Processed,         [int]$Total,         [raspršivanje]$Metrics = @{}     )

    $checkpoint = @{         Stage = $Stage         UpdatedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         Obrađeno = $Processed         Ukupno = $Total         Postotak = ako ($Total -gt 0) { [matematika]::Round(($Processed / $Total) * 100, 2) } još { 0 }         Metrika = $Metrics     }

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

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

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

    foreach ($device in $Devices) {         $hostname = ako ($device. Naziv glavnog računala) { $device. Hostname } elseif ($device. HostName) { $device. HostName } još { $null }         if ($hostname) {             [void]$hostSet.Add($hostname)         }

        $bucketKey = Get-BucketKey $device         if ($bucketKey) {             if ($bucketCounts.ContainsKey($bucketKey)) {                 $bucketCounts[$bucketKey]++             } još {                 $bucketCounts[$bucketKey] = 1             }         }     }

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

function Write-Log {     param([niz]$Message; [niz]$Level = "INFO")     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     $color = prekidač ($Level) {         "OK" { "Green" }         "WARN" { "Yellow" }         "ERROR" { "Red" }         "BLOCKED" { "DarkRed" }         "WAVE" { "Cyan" }         zadano { "Bijelo" }     }     Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color     # Prijavite se i u datoteku     $logFile = Join-Path $stateDir "Orchestrator_$(Get-Date -Format 'yyyyMMdd').log"     "[$timestamp] [$Level] $Message" | Out-File $logFile -Append -Encoding UTF8 }               

function Get-BucketKey {     param($Device)     # Upotrijebite BucketId s JSON uređaja ako je dostupan (SHA256 raspršivanje iz skripte otkrivanja)     if ($Device.BucketId -and "$($Device.BucketId)" -ne "") { return "$($Device.BucketId)" }     # Rezervni: konstrukt proizvođača|model|bios     $mfr = ako ($Device.WMI_Manufacturer) { $Device.WMI_Manufacturer } još { $Device.Manufacturer }     $model = if ($Device.WMI_Model) { $Device.WMI_Model } još { $Device.Model }     $bios = if ($Device.BIOSDescription) { $Device.BIOSDescription } else { $Device.BIOS }     return "$mfr|$model|$bios" }

# ============================================================================ # UČITAVANJE POPISA VIP/IZUZETAKA # ============================================================================

function Get-ExcludedHostnames {     param(         [niz]$ExclusionFilePath,         [niz]$ADGroupName     )     $excluded = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)     # Učitaj iz datoteke (podržava .txt ili .csv)     if ($ExclusionFilePath -and (Test-Path $ExclusionFilePath)) {         $extension = [System.IO.Path]::GetExtension($ExclusionFilePath). ToLower()         if ($extension -eq ".csv") {             # CSV oblik: očekuje stupac "Naziv Glavnog računala" ili "Naziv Računala"             $csvData = Import-Csv $ExclusionFilePath             $hostCol = if ($csvData[0]. PSObject.Properties.Name -contains 'Hostname') { 'Hostname' }                        elseif ($csvData[0]. PSObject.Properties.Name -contains 'ComputerName') { 'ComputerName' }                        elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                        else { $null }             if ($hostCol) {                 foreach ($row u $csvData) {                     ako je (![ niz]::IsNullOrWhiteSpace($row.$hostCol)) {                         [void]$excluded. Add($row.$hostCol.Trim())                     }                 }             }         } još {             # Običan tekst: jedan naziv glavnog računala po retku             Get-Content $ExclusionFilePath | ForEach-Object {                 $line = $_. Skraćivanje()                 ako ($line -and -not $line. StartsWith('#')) {                     [void]$excluded. Add($line)                 }             }         }         Write-Log "Učitan $($excluded. Count) hostnames from exclusion file: $ExclusionFilePath" "INFO"     }     # Učitaj iz ad-sigurnosne grupe     if ($ADGroupName) {         pokušajte {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Rekursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'računalo' }             foreach ($member u $groupMembers) {                 [void]$excluded. Add($member. Naziv)             }             Write-Log "Učitana računala $($groupMembers.Count) iz grupe AD: $ADGroupName" "INFO"         } uhvatiti {             Write-Log "Ad grupu '$ADGroupName' nije moguće učitati: $_" "UPOZORENJE"         }     }     return @($excluded) }                                                                             

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

function Get-AllowedHostnames {     < broj     . SINOPSIS         Učitava nazive glavnog računala iz datoteke AllowList i/ili grupe AD za ciljano pokretanje.Kada je naveden AllowList, samo će ti uređaji biti uključeni u početak rada.#>     param(         [niz]$AllowFilePath,         [niz]$ADGroupName     )          $allowed = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)          # Učitaj iz datoteke (podržava .txt ili .csv)     if ($AllowFilePath -and (Test-Path $AllowFilePath)) {         $extension = [System.IO.Path]::GetExtension($AllowFilePath). ToLower()                  if ($extension -eq ".csv") {             # CSV oblik: očekuje stupac "Naziv Glavnog računala" ili "Naziv Računala"             $csvData = Import-Csv $AllowFilePath             if ($csvData.Count -gt 0) {                 $hostCol = if ($csvData[0]. PSObject.Properties.Name -contains 'Hostname') { 'Hostname' }                            elseif ($csvData[0]. PSObject.Properties.Name -contains 'ComputerName') { 'ComputerName' }                            elseif ($csvData[0]. PSObject.Properties.Name -contains 'Name') { 'Name' }                            else { $null }                                  if ($hostCol) {                     foreach ($row u $csvData) {                         ako je (![ niz]::IsNullOrWhiteSpace($row.$hostCol)) {                             [void]$allowed. Add($row.$hostCol.Trim())                         }                     }                 }             }         } još {             # Običan tekst: jedan naziv glavnog računala po retku             Get-Content $AllowFilePath | ForEach-Object{                 $line = $_. Skraćivanje()                 ako ($line -and -not $line. StartsWith('#')) {                     [void]$allowed. Add($line)                 }             }         }                  Write-Log "Učitan $($allowed. Count) hostnames from allow list file: $AllowFilePath" "INFO"     }          # Učitaj iz ad-sigurnosne grupe     if ($ADGroupName) {         pokušajte {             $groupMembers = Get-ADGroupMember -Identity $ADGroupName -Rekursive -ErrorAction Stop |                  Where-Object { $_.objectClass -eq 'računalo' }                          foreach ($member u $groupMembers) {                 [void]$allowed. Add($member. Naziv)             }                          Write-Log "Učitana računala $($groupMembers.Count) iz grupe dopuštenih servisa AD: $ADGroupName" "INFO"         } uhvatiti {             Write-Log "Ad grupu '$ADGroupName' nije moguće učitati: $_" "UPOZORENJE"         }     }          return @($allowed) }

# ============================================================================ # SVJEŽINA PODATAKA I PRAĆENJE # ============================================================================

function Get-DataFreshness {     < broj     . SINOPSIS         Provjerava koliko su novi podaci otkrivanja pregledom vremenskih oznaka JSON datoteke.Vraća statistiku o zadnjim prijavljenim krajnjim točkama.#>     param([niz]$JsonPath)     $jsonFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue     if ($jsonFiles.Count -eq 0) {         vrati @{             TotalFiles = 0             FreshFiles = 0             StaleFiles = 0             NoDataFiles = 0             OldestFile = $null             NewestFile = $null             AvgAgeHours = 0             Upozorenje = "Nije pronađena nijedna JSON datoteka – otkrivanje nije moguće implementirati"         }     }     $now = Get-Date     $freshThresholdHours = 24 # Files ažurirana u zadnja 24 sata su "svježe"     $staleThresholdHours = 72 # Files stariji od 72 sata su "zastarjeli"     $fresh = 0     $stale = 0     $ages = @()     foreach ($file u $jsonFiles) {         $ageHours = ($now - $file. LastWriteTime). TotalHours         $ages += $ageHours         if ($ageHours -le $freshThresholdHours) {             $fresh++         } elseif ($ageHours -ge $staleThresholdHours) {             $stale++         }     }     $oldestFile = $jsonFiles | Sort-Object LastWriteTime | Select-Object - Prvi 1     $newestFile = $jsonFiles | Sort-Object LastWriteTime -Descending | Select-Object - Prvi 1     $warning = $null     if ($stale -gt ($jsonFiles.Count * 0,5)) {         $warning = "Više od 50% uređaja ima zastarjele podatke (>72 sata) – provjerite otkrivanje GPO"     } elseif ($fresh -lt ($jsonFiles.Count * 0.3)) {         $warning = "Nedavno prijavljeno manje od 30 % uređaja – otkrivanje možda nije pokrenuto"     }     vrati @{         TotalFiles = $jsonFiles.Count         FreshFiles = $fresh         StaleFiles = $stale         MediumFiles = $jsonFiles.Count - $fresh - $stale         OldestFile = $oldestFile.LastWriteTime         NewestFile = $newestFile.LastWriteTime         AvgAgeHours = [matematika]::Round(($ages | Measure-Object -Average). Prosjek, 1)         Upozorenje = $warning     } }                                                 

function Test-DetectionGPODeployed {     < broj     . SINOPSIS         Provjerava je li infrastruktura otkrivanja/nadzora na mjestu.#>     param([niz]$JsonPath)     # Check 1: JSON path exists     if (-not (Test-Path $JsonPath)) {         vrati @{             Uvedeno = $false             Poruka = "Put unosa JSON-a ne postoji: $JsonPath"         }     }     # 2. provjera: postoje barem neke JSON datoteke     $jsonCount = (Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue). Računati     if ($jsonCount -eq 0) {         vrati @{             Uvedeno = $false             Message = "Nema JSON datoteka u $JsonPath – otkrivanje GPO-a možda nije implementirano ili uređaji još nisu prijavili"         }     }     # Check 3: Files su razumno nedavno (barem neki u prošlog tjedna)     $recentFiles = Get-ChildItem -Path $JsonPath -Filter "*.json" -ErrorAction SilentlyContinue |         Where-Object { $_. LastWriteTime -gt (Get-Date). AddDays(-7) }     if ($recentFiles.Count -eq 0) {         vrati @{             Uvedeno = $false             Message = "Nema JSON datoteka ažuriranih u zadnjih 7 dana – otkrivanje GPO-a možda je neispravno ili uređaji izvan mreže"         }     }     vrati @{         Uvedeno = $true         Poruka = "Otkrivanje je aktivno: $jsonCount datoteke, $($recentFiles.Count) nedavno ažurirano"         FileCount = $jsonCount         RecentCount = $recentFiles.Count     } }                         

# ============================================================================ # PRAĆENJE UREĐAJA (PREMA NAZIVU GLAVNOG RAČUNALA) # ============================================================================

function Update-DeviceHistory {     < broj     . SINOPSIS         Prati uređaje prema nazivu glavnog računala jer nemamo jedinstveni identifikator računala.Napomena: BucketId je jedan-prema-više (ista hardverska konfiguracija = ista grupa).Ako se u JSON zbirku doda jedinstveni identifikator, ažurirajte ovu funkciju.#>     param(         [polje]$CurrentDevices,         [raspršivanje]$DeviceHistory     )          foreach ($device u $CurrentDevices) {         $hostname = $device. Hostname         if (-not $hostname) { continue }                  # Praćenje uređaja prema nazivu glavnog računala         $DeviceHistory[$hostname] = @{             Naziv glavnog računala = $hostname             BucketId = $device. Id kante             Proizvođač = $device. WMI_Manufacturer             Model = $device. WMI_Model             LastSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             Status = $device. UpdateStatus         }     } }

# ============================================================================ # BLOKIRANO OTKRIVANJE KANTICE (na temelju doseg uređaja) # ============================================================================

<# . OPIS     Logika blokiranja:     - Grupa je blokirana samo ako:       1. Uređaj je ciljan u valu       2. MaxWaitHours je prošao od pokretanja vala       3. Uređaj NIJE DOSTUPAN (ping ne uspijeva)          - Ako je uređaj dostupan, ali još nije ažuriran, čekamo       (ažuriranje možda čeka ponovno pokretanje – događaj 1808 pokreće se samo nakon ponovnog pokretanja)          - Uređaj koji nije dostupan označava da je došlo do pogreške i da je potrebna istraga          Deblokiranje:     - Koristite -ListBlockedBuckets za prikaz blokiranih grupa     - Koristite -UnblockBucket "BucketKey" za deblokiranje određene grupe     - Koristite -DeblokiranjeSve za deblokiranje svih grupa #>

function Test-DeviceReachable {     param(         [niz]$Hostname,         [string]$DataPath # Put do JSON datoteka uređaja     )     # Method 1: Check JSON file timestamp (fastest — no file parsing needed)     # Ako je skripta otkrivanja nedavno pokrenuta, datoteka je napisana/ažurirana, dokazujući da je uređaj živ     if ($DataPath) {         $deviceFile = Get-ChildItem -Path $DataPath -Filter "${Hostname}*" -File -ErrorAction SilentlyContinue | Select-Object - Prvi 1         if ($deviceFile) {             $hoursSinceWrite = ((Get-Date) - $deviceFile.LastWriteTime). TotalHours             if ($hoursSinceWrite -lt 72) { return $true }         }     }     # Method 2: Fallback to ping (only if JSON is stale or missing)     pokušajte {         $ping = Test-Connection -ComputerName $Hostname -Count 1 -Quiet -ErrorAction SilentlyContinue         povratna $ping     } uhvatiti {         povratna $false     } }          

function Update-BlockedBuckets {     param(         $RolloutState,         $BlockedBuckets,         $AdminApproved,         [polje]$NotUpdatedDevices,         [raspršivanje]$NotUpdatedIndexes,         [int]$MaxWaitHours,         [bool]$DryRun = $false     )     $now = Get-Date     $newlyBlocked = @()     $stillWaiting = @()     $devicesToCheck = @()     $hostSet = ako ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } još { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     $bucketCounts = if ($NotUpdatedIndexes -and $NotUpdatedIndexes.BucketCounts) { $NotUpdatedIndexes.BucketCounts } else { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). BucketCounts }     # Prikupite uređaje koji su prošli razdoblje čekanja i još nisu ažurirani     foreach ($wave in $RolloutState.WaveHistory) {         if (-not $wave. StartedAt) { continue }         $waveStart = [DateTime]::P arse($wave. StartedAt)         $hoursSinceWave = ($now - $waveStart). TotalHours         if ($hoursSinceWave -lt $MaxWaitHours) {             # Još uvijek unutar razdoblja čekanja – još ne provjeravaj             Nastaviti         }         # Provjerite svaki uređaj iz ovog vala         ($deviceInfo $wave. Uređaji) {             $hostname = $deviceInfo.Hostname             $bucketKey = $deviceInfo.BucketKey             # Preskoči ako je grupa već blokirana             if ($BlockedBuckets.Contains($bucketKey)) { continue }             # Preskoči ako je grupa odobrena za administratore AND val je pokrenut PRIJE odobrenja             # (samo provjerite uređaje koji su ciljani nakon administratorskog odobrenja za ponovno blokiranje)             if ($AdminApproved -and $AdminApproved.Contains($bucketKey)) {                 $approvalTime = [DateTime]::P arse($AdminApproved[$bucketKey]. Odobreno)                 if ($waveStart -lt $approvalTime) {                     # Ovaj je uređaj bio ciljan prije odobrenja administratora – preskoči                     Nastaviti                 }                 # Val je započeo nakon odobrenja - ovo je svježe ciljanje, može provjeriti             }             # Je li ovaj uređaj i dalje na popisu NotUpdated?             if ($hostSet.Contains($hostname)) {                 $devicesToCheck += @{                     Naziv glavnog računala = $hostname                     BucketKey = $bucketKey                     WaveNumber = $wave. WaveNumber                     HoursSinceWave = [matematika]::Round($hoursSinceWave; 1)                 }             }         }     }     if ($devicesToCheck.Count -eq 0) {         vraćanje $newlyBlocked     }     Write-Log "Provjera dostupnosti uređaja $($devicesToCheck.Count) nakon razdoblja čekanja..." "INFO"     # Praćenje pogrešaka po grupi za donošenje odluka     $bucketFailures = @{} # BucketKey -> @{ Unreachable=@(); Alive=@() }     # Provjerite pristupačnost svakog uređaja     foreach ($device u $devicesToCheck) {         $hostname = $device. Hostname         $bucketKey = $device. Ključ kante         if ($DryRun) {             Write-Log "[DRYRUN] Provjerava $hostname pristupačnosti" "INFO"             Nastaviti         }         if (-not $bucketFailures.ContainsKey($bucketKey)) {             $bucketFailures[$bucketKey] = @{ Nedostupan = @(); AliveButFailed = @(); WaveNumber = $device. WaveNumber; HoursSinceWave = $device. HoursSinceWave }         }         $isReachable = Test-DeviceReachable -Hostname $hostname -DataPath $AggregationInputPath         if (-not $isReachable) {             $bucketFailures[$bucketKey]. Nedostupan += $hostname         } još {             # Uređaj je dostupan, ali još nije ažuriran – može biti privremena pogreška ili čekanje na ponovno pokretanje             $bucketFailures[$bucketKey]. AliveButFailed += $hostname             $stillWaiting += $hostname         }     }     # Decision per bucket: only block if devices are truly UNREACHABLE     # Alive devices with failures = temporary, continue rollout     foreach ($bucketKey in $bucketFailures.Keys) {         $bf = $bucketFailures[$bucketKey]         $unreachableCount = $bf. Unreachable.Count         $aliveFailedCount = $bf. AliveButFailed.Count         # Provjerite ima li ova grupa uspjeha (iz ažuriranih podataka o uređajima)         $bucketHasSuccesses = $stSuccessBuckets -i $stSuccessBuckets.Contains($bucketKey)         if ($unreachableCount -gt 0 -and $aliveFailedCount -eq 0) {             # SVI neispravni uređaji nisu dostupni – blokirajte grupu             if ($newlyBlocked -notcontains $bucketKey) {                 $BlockedBuckets[$bucketKey] = @{                     BlockedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"                     Reason = "All $unreachableCount device(s) unreachable after $($bf. HoursSinceWave) sati"                     FailedDevices = ($bf. Unreachable -join ", ")                     WaveNumber = $bf. WaveNumber                     DevicesInBucket = if ($bucketCounts.ContainsKey($bucketKey)) { $bucketCounts[$bucketKey] } else { 0 }                 }                 $newlyBlocked += $bucketKey                 Write-Log "BUCKET BLOCKED: $bucketKey ($unreachableCount device(s) unreachable: $($bf. Unreachable -join ', '))" "BLOCKED"             }         } elseif ($aliveFailedCount -gt 0) {             # Uređaji su živi, ali se ne ažuriraju – privremeni kvar, NE blokiraj             Write-Log "Bucket $($bucketKey.Substring(0, [Math]::Min(16, $bucketKey.Length)))...: $aliveFailedCount device(s) alive but pending, $unreachableCount unreachable - NOT blocking (temporary)" "INFO"             if ($unreachableCount -gt 0) {                 Write-Log " Nedostupan: $($bf. Unreachable -join ', ')" "WARN"             }             Write-Log " Živ, ali na čekanju: $($bf. AliveButFailed -join ', ')" "INFO"             # Praćenje broja neuspjeha u stanju pokretanja za praćenje             if (-not $RolloutState.TemporaryFailures) { $RolloutState.TemporaryFailures = @{} }             $RolloutState.TemporaryFailures[$bucketKey] = @{                 AliveButFailed = $bf. AliveButFailed                 Unreachable = $bf. Nedostupan                 LastChecked = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             }         }     }     if ($stillWaiting.Count -gt 0) {         Write-Log "Uređaji dostupni, ali ažuriranje na čekanju (možda će trebati ponovno pokretanje): $($stillWaiting.Count)" "INFO"     }     povratna $newlyBlocked }                                                                                                                                                                                  

# ============================================================================ # AUTOMATSKO DEBLOKIRANJE: Deblokiranje grupa kada se uređaji uspješno ažuriraju # ============================================================================

function Update-AutoUnblockedBuckets {     < broj     . OPIS         Provjerava jesu li uređaji u blokiranim grupama ažurirani (događaj 1808).         Automatsko deblokiranje ako su ažurirani svi ciljani uređaji u grupi.Ako su samo NEKI uređaji ažurirani, obavijestit će administratora koji može ručno deblokirati.                  Administrator možete ručno deblokirati pomoću:           .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath "path" -UnblockBucket "BucketKey"     #>     param(         $BlockedBuckets,         $RolloutState,         [polje]$NotUpdatedDevices,         [niz]$ReportBasePath,         [raspršivanje]$NotUpdatedIndexes,         [int]$LogSampleSize = 25     )     $autoUnblocked = @()     $bucketsToCheck = @($BlockedBuckets.Tipke)     $hostSet = ako ($NotUpdatedIndexes -and $NotUpdatedIndexes.HostSet) { $NotUpdatedIndexes.HostSet } još { (Get-NotUpdatedIndexes -Devices $NotUpdatedDevices). HostSet }     foreach ($bucketKey u $bucketsToCheck) {         $bucketInfo = $BlockedBuckets[$bucketKey]         # Get all devices we targeted from this bucket historically         $targetedDevicesInBucket = @()         foreach ($wave in $RolloutState.WaveHistory) {             $targetedDevicesInBucket += @($wave. Uređaji | Where-Object { $_. BucketKey -eq $bucketKey })         }         if ($targetedDevicesInBucket.Count -eq 0) { continue }         # Provjerite koliko je ciljanih uređaja još uvijek u aplikaciji NotUpdated ili ažurirano         $updatedDevices = @()         $stillPendingDevices = @()         foreach ($targetedDevice u $targetedDevicesInBucket) {             if ($hostSet.Contains($targetedDevice.Hostname)) {                 $stillPendingDevices += $targetedDevice.Naziv glavnog računala             } još {                 $updatedDevices += $targetedDevice.Naziv glavnog računala             }         }         if ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -eq 0) {             # Svi su ciljani uređaji ažurirani – automatsko deblokiranje!             $BlockedBuckets.Remove($bucketKey)             $autoUnblocked += @{                 BucketKey = $bucketKey                 UpdatedDevices = $updatedDevices                 PreviouslyBlockedAt = $bucketInfo.BlockedAt                 Reason = "All $($updatedDevices.Count) targeted device(s) successfully updated"             }             Write-Log "AUTOMATSKO DEBLOKIRANJE: $bucketKey (svi su se ciljani uređaji s vrijednostima $($updatedDevices.Count) uspješno ažurirali)" "U redu"             # Inkrement OEM val brojanje za ovu grupu OEM (per-OEM praćenje)             $bucketOEM = ako ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } else { 'Unknown' } # Extract OEM from pipe-delimited key or default             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 valova povećava se na $($currentWave + 1) (sljedeća dodjela: $([int][Matematika]::P ow(2, $currentWave + 1)) uređaji)" "INFO"         }         elseif ($updatedDevices.Count -gt 0 -and $stillPendingDevices.Count -gt 0) {             # NEKI su uređaji ažurirani, ali su drugi i dalje na čekanju – obavijestite administratora (samo jedanput)             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 "" "INFO"                 Write-Log "========== DJELOMIČNO AŽURIRANJE U BLOKIRANOJ GRUPI ==========" "INFO"                 Write-Log "Grupa: $bucketKey" "INFO"                 $updatedSample = @($updatedDevices | Select-Object -First $LogSampleSize)                 $pendingSample = @($stillPendingDevices | Select-Object -First $LogSampleSize)                 $updatedSuffix = ako ($updatedDevices.Count -gt $LogSampleSize) { " ... (+$($updatedDevices.Count - $LogSampleSize) više)" } još { "" }                 $pendingSuffix = ako ($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š uvijek na čekanju ($($stillPendingDevices.Count)): $($pendingSample -join ', ')$pendingSuffix" "WARN"                 Write-Log "" "INFO"                 Write-Log "Da biste ručno deblokirali ovu grupu nakon provjere, pokrenite:" "INFO"                 Write-Log " .\Start-SecureBootRolloutOrchestrator.ps1 -ReportBasePath '"$ReportBasePath'" -UnblockBucket '"$bucketKey'"" "INFO"                 Write-Log "=======================================================" "INFO"                 Write-Log "" "INFO"             }         }     }     povratna $autoUnblocked }                                                                                          

# ============================================================================ # WAVE GENERATION (INLINED – isključuje blokirane grupe) # ============================================================================

function New-RolloutWave {     param(         [niz]$AggregationPath,         $BlockedBuckets,         $RolloutState,         [int]$MaxDevicesPerWave = 50,         [string[]]$AllowedHostnames = @(),         [string[]]$ExcludedHostnames = @()     )     # Učitavanje podataka o agregaciji     $notUptodateCsv = Get-ChildItem -Path $AggregationPath -Filter "*NotUptodate*.csv" |          Where-Object { $_. Naziv -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object - Prvi 1     if (-not $notUptodateCsv) {         Write-Log "PRONAĐEN JE CSV BEZ AŽURIRANJA" "POGREŠKA"         vraćanje $null     }     $allNotUpdated = @(Import-Csv $notUptodateCsv.FullName)     # Normalizirajte HostName -> hostname radi dosljednosti (CSV koristi Naziv Glavnog računala, kod koristi Naziv Glavnog računala)     foreach ($device u $allNotUpdated) {         ako ($device. PSObject.Properties['HostName'] -and -not $device. PSObject.Properties['Hostname']) {             $device | Add-Member -NotePropertyName 'Hostname' -NotePropertyValue $device. HostName – sila         }     }     # Filtriranje blokiranih grupa     $eligibleDevices = @($allNotUpdated | Where-Object {         $bucketKey = Get-BucketKey $_         -nije $BlockedBuckets.Contains($bucketKey)     Ne, ne, ne, ne.     # Filtriraj samo na dopuštene uređaje (ako je naveden AllowList)     # AllowList = ciljano rollout - samo ti uređaji će se smatrati     if ($AllowedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Hostname -in $AllowedHostnames         Ne, ne, ne, ne.         $allowedCount = $eligibleDevices.Count         Write-Log "AllowList applied: $allowedCount of $beforeCount devices are in allow list" "INFO"     }     # FiltrirajTE VIP/isključene uređaje (BlockList)     # BlockList se primjenjuje NAKON AllowList     if ($ExcludedHostnames.Count -gt 0) {         $beforeCount = $eligibleDevices.Count         $eligibleDevices = @($eligibleDevices | Where-Object {             $_. Hostname -notin $ExcludedHostnames         Ne, ne, ne, ne.         $excludedCount = $beforeCount - $eligibleDevices.Count         if ($excludedCount -gt 0) {             Write-Log "Isključeno $excludedCount VIP/zaštićenih uređaja od primjene" "INFO"         }     }     if ($eligibleDevices.Count -eq 0) {         Write-Log "Nema preostalih uređaja koji ispunjavaju uvjete (svi ažurirani ili blokirani)" "U redu"         povratna $null     }     # Nabavite uređaje koji su već u tijeku (iz prethodnih valova)     $devicesAlreadyInRollout = @()     if ($RolloutState.WaveHistory -and $RolloutState.WaveHistory.Count -gt 0) {         $devicesAlreadyInRollout = @($RolloutState.WaveHistory | ForEach-Object {             $_. Uređaji | ForEach-Object { $_. Naziv glavnog računala }         } | Where-Object { $_ })     }     Write-Log "Uređaji koji su već u implementaciji: $($devicesAlreadyInRollout.Count)" "INFO"     # Odvojite prema razini pouzdanosti     $highConfidenceDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -eq "Visoka pouzdanost" -and         $_. Hostname -notin $devicesAlreadyInRollout     Ne, ne, ne, ne.     # Akcija Obavezno uključuje:     # – eksplicitno "Potrebna je radnja"     # – prazno/null razina pouzdanosti     # – nepoznata/nepoznata vrijednost confidenceLevel (tretira se kao potrebna radnja)     $knownSafeCategories = @(         "Visoko povjerenje",         "Privremeno pauzirano",         "Pod opažanjima",         "Pod promatranjom – potrebno je više podataka",         "Nije podržano",         "Nije podržano – poznato ograničenje"     )     $actionRequiredDevices = @($eligibleDevices | Where-Object {         $_. ConfidenceLevel -notin $knownSafeCategories -and         $_. Hostname -notin $devicesAlreadyInRollout     Ne, ne, ne, ne.     Write-Log "Visoka pouzdanost (nije u implementaciji): $($highConfidenceDevices.Count)" "INFO"     Write-Log "Potrebna je akcija (nije u implementaciji): $($actionRequiredDevices.Count)" "INFO"     # Izradite valne uređaje     $waveDevices = @()     # VISOKA POUZDANOST: Uključi SVE (sigurno za rollout)     if ($highConfidenceDevices.Count -gt 0) {         Write-Log "Adding all $($highConfidenceDevices.Count) High Confidence devices" "WAVE"         $waveDevices += $highConfidenceDevices     } # POTREBNA RADNJA: progresivno uvodinje (utemeljeno na grupi s OEM-širenjem za grupe nultog uspjeha)     # Strategija:     # - Kante s 0 uspjeha: Spread across OEMs (1 per OEM -> 2 per OEM -> 4 per OEM)     # - Kante s ≥1 uspjeha: dvostruko slobodno bez ograničenja OEM-a     if ($actionRequiredDevices.Count -gt 0) {         # Učitaj broj uspjeha grupe s ažuriranih CSV uređaja (uređaji koji su uspješno ažurirani)         $updatedCsv = Get-ChildItem -Path $AggregationPath -Filter "*updated_devices*.csv" |             Sort-Object LastWriteTime -Descending | Select-Object - Prvi 1         $bucketStats = @{}         if ($updatedCsv) {             $updatedDevices = Import-Csv $updatedCsv.FullName             # Brojanje uspjeha po BucketId-u             $updatedDevices | ForEach-Object {                 $key = Get-BucketKey $_                 if ($key) {                     if (-not $bucketStats.ContainsKey($key)) {                         $bucketStats[$key] = @{ Uspjeh = 0; Na čekanju = 0; Ukupno = 0 }                     }                     $bucketStats[$key]. Uspjeh ++                     $bucketStats[$key]. Ukupno + +                 }             }             Write-Log ažuriranim uređajima "Loaded $($updatedDevices.Count) u grupama $($bucketStats.Count) "INFO"         } još {             # Rezervni: pokušajte ActionRequired_Buckets CSV             $bucketsCsv = Get-ChildItem -Path $AggregationPath -Filter "*ActionRequired_Buckets*.csv" |                 Sort-Object LastWriteTime -Descending | Select-Object - Prvi 1             if ($bucketsCsv) {                 Import-Csv $bucketsCsv.FullName | ForEach-Object {                     $key = ako ($_. BucketId) { $_. BucketId } još { "$($_. Proizvođač)|$($_. Model)|$($_. BIOS)" }                     $bucketStats[$key] = @{                         Successes = [int]$_. Uspjehe                         Na čekanju = [int]$_. Neriješene                         Ukupno = [int]$_. TotalDevices                     }                 }             }         }         # Group NotUpdated uređaji po grupi (proizvođač|Model|BIOS)         $buckets = $actionRequiredDevices | Group-Object { Get-BucketKey $_ }         # Odvojene grupe: nula-uspjeh vs has-success         $zeroSuccessBuckets = @()         $hasSuccessBuckets = @()         foreach ($bucket u $buckets) {             $bucketKey = $bucket. Ime             $bucketDevices = @($bucket. Grupa)             $bucketHostnames = @($bucketDevices | ForEach-Object { $_. Naziv glavnog računala })             Brojanje uspjeha u ovoj grupi             $stats = $bucketStats[$bucketKey]             $successes = ako ($stats) { $stats. Successes } else { 0 }             # Pronađi uređaje implementirane u ovu grupu iz povijesti vala             $deployedToBucket = @()             foreach ($wave in $RolloutState.WaveHistory) {                 ($device $wave. Uređaji) {                     ako ($device. BucketKey -eq $bucketKey -and $device. Naziv glavnog računala) {                         $deployedToBucket += $device. Hostname                     }                 }             }             $deployedToBucket = @($deployedToBucket | Sort-Object -Unique)             # Provjerite je li sve implementirane uređaje prijavilo uspjeh             $stillPending = @($deployedToBucket | Where-Object { $_ -in $bucketHostnames })             $confirmedSuccess = $deployedToBucket.Count - $stillPending.Count             # Ako je na čekanju, preskočite ovu grupu dok svi ne potvrde             if ($stillPending.Count -gt 0) {                 $parts = $bucketKey -split '\|'                 $displayName = "$($parts[0]) - $($parts[1])"                 Write-Log " Bucket: $displayName - Deployed=$($deployedToBucket.Count), Confirmed=$confirmedSuccess, Pending=$($stillPending.Count) (waiting)" "INFO"                 Nastaviti             }             # Preostali kvalificirani broj = uređaji koji još nisu implementirali             $devicesNotYetTargeted = @($bucketDevices | Where-Object {                 $_. Naziv glavnog računala -notin $deployedToBucket             Ne, ne, ne, ne.             if ($devicesNotYetTargeted.Count -eq 0) { continue }             # Kategoriziraj prema zbroju uspjeha             $bucketInfo = @{                 BucketKey = $bucketKey                 Uređaji = $devicesNotYetTargeted                 ConfirmedSuccess = $confirmedSuccess                 Successes = $successes                 OEM = ako ($bucket. Grupa[0]. WMI_Manufacturer) { $bucket. Grupa[0]. WMI_Manufacturer } elseif ($bucketKey -match '\|') { ($bucketKey -split '\|')[0] } još { 'Nepoznato' }             }             if ($successes -eq 0) {                 $zeroSuccessBuckets += $bucketInfo             } još {                 $hasSuccessBuckets += $bucketInfo             }         }         # === PROCESS HAS-SUCCESS BUCKETS (≥1 success) ===         # Udvostruči broj uspjeha – ako je 14 uspjelo, implementiraj 28 dalje         foreach ($bucketInfo u $hasSuccessBuckets) {             $nextBatchSize = $bucketInfo.Successes * 2             $nextBatchSize = [Matematika]::Min($nextBatchSize, $MaxDevicesPerWave)             $nextBatchSize = [Matematika]::Min($nextBatchSize, $bucketInfo.Devices.Count)             if ($nextBatchSize -gt 0) {                 $selectedDevices = @($bucketInfo.Uređaji | Select-Object -First $nextBatchSize)                 $waveDevices += $selectedDevices                 $parts = ako ($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 " [HAS-SUCCESS] $displayName - Successes=$($bucketInfo.Successes), Deploying=$nextBatchSize (2x confirmed)" "INFO"             }         }         # === OBRADA GRUPA NULTOG USPJEHA (proširivanje preko OEM-a s praćenjem po OEM-u) ===         # Cilj: širenje rizika na različite OEM-ove, samostalno praćenje napretka po OEM-u         # Svaki OEM napreduje na temelju vlastite povijesti uspjeha:         # – OEM s uspjehom: dobiva više uređaja sljedećeg vala (2^waveCount)         # – OEM bez uspjeha: ostaje na trenutnoj razini do potvrde uspjeha         if ($zeroSuccessBuckets.Count -gt 0) {             # Inicijaliziraj broj per-OEM vala ako ne postoji             if (-not $RolloutState.OEMWaveCounts) {                 $RolloutState.OEMWaveCounts = @{}             }             # Group zero-success buckets by OEM             $oemBuckets = $zeroSuccessBuckets | Group-Object { $_. OEM }             $totalZeroSuccessAdded = 0             $oemsDeployedTo = @()             foreach ($oemGroup u $oemBuckets) {                 $oemName = $oemGroup.Name                 # Get this OEM's wave count (starts at 0)                 $oemWaveCount = if ($RolloutState.OEMWaveCounts[$oemName]) {                     $RolloutState.OEMWaveCounts[$oemName]                 } još { 0 }                 # Izračun uređaja za THIS OEM: 2^waveCount (1, 2, 4, 8...)                 $devicesForThisOEM = [int][Matematika]::P ow(2, $oemWaveCount)                 $devicesForThisOEM = [Matematika]::Max(1, $devicesForThisOEM)                 $oemDevicesAdded = 0                 # Pick from each bucket under this OEM                 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.Uređaji | Select-Object -First $toTake)                         $waveDevices += $selectedDevices                         $oemDevicesAdded += $toTake                         $totalZeroSuccessAdded += $toTake                         $parts = ako ($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 - Deploying=$toTake (OEM wave $oemWaveCount = ${devicesForThisOEM}/OEM)" "WARN"                     }                 }                 if ($oemDevicesAdded -gt 0) {                     Write-Log " OEM: $oemName - Wave $oemWaveCount, Added $oemDevicesAdded devices" "INFO"                     $oemsDeployedTo += $oemName                 }             }             # Pratite u koje smo OEM-ove implementirali (radi povećavanja na sljedeću provjeru uspjeha)             if ($oemsDeployedTo.Count -gt 0) {                 $RolloutState.PendingOEMWaveIncrement = $oemsDeployedTo                 Write-Log "Implementacija bez uspjeha: $totalZeroSuccessAdded na uređajima $($oemsDeployedTo.Count) OEM-ove" "INFO"             }         }     }     if (@($waveDevices). Count -eq 0) {         povratna $null     }     povratna $waveDevices }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

# ============================================================================ # GPO DEPLOYMENT (INLINED – stvara GPO, sigurnosnu grupu, veze) # ============================================================================

function Deploy-GPOForWave {     param(         [niz]$GPOName,         [niz]$TargetOU,         [niz]$SecurityGroupName,         [polje]$WaveHostnames,         [bool]$DryRun = $false     )     # ADMX pravilnik: SecureBoot.admx – SecureBoot_AvailableUpdatesPolicy     # Put registra: HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot     # Naziv vrijednosti: AvailableUpdatesPolicy     # Enabled Value: 22852 (0x5944) - Update all Secure Boot keys + bootmgr     # Onemogućena vrijednost: 0     #     # Using pravilnik grupe Preferences (GPP) for reliable HKLM\SYSTEM path deployment     # GPP stvara postavke u odjeljku: Konfiguracija računala > Preference > postavke sustava Windows > registra     $RegistryKey = "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot"     $RegistryValueName = "AvailableUpdatesPolicy"     $RegistryValue = 22852 # 0x5944 - podudara se s admx enabledValue     Write-Log "Implementacija GPO: $GPOName" "WAVE"     Write-Log "Registar: $RegistryKey\$RegistryValueName = $RegistryValue (0x$($RegistryValue.ToString('X')))" "INFO"     if ($DryRun) {         Write-Log "[DRYRUN] Bi stvorio GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Bi stvorio sigurnosnu grupu: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Bi dodao $(@($WaveHostnames). Brojanje) računala za grupiranje" "INFO"         Write-Log "[DRYRUN] Želi povezati GPO na: $TargetOU" "INFO"         povratna $true     }     pokušajte {         # Uvoz obaveznih modula         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory – zaustavljanje pogreške     } uhvatiti {         Write-Log "Uvoz obaveznih modula nije uspio (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         povratna $false     }     # Prvi korak: stvaranje ili dohvaćanje GPO-a     $existingGPO = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($existingGPO) {         Write-Log "GPO već postoji: $GPOName" "INFO"         $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 "Created GPO: $GPOName" "OK"         } uhvatiti {             Write-Log "Stvaranje GPO-a nije uspjelo: $($_. Exception.Message)" "ERROR"             vraćanje $false         }     }     # Drugi korak: postavljanje vrijednosti registra pomoću pravilnik grupe Preference (GPP)     # GPP je pouzdaniji za HKLM\SYSTEM putove nego Set-GPRegistryValue     pokušajte {         # Najprije pokušajte ukloniti bilo koju postojeću preferencu za tu vrijednost (da biste izbjegli duplikate)         Remove-GPPrefRegistryValue -Name $GPOName -Context Computer -Key $RegistryKey -ValueName $RegistryValueName -ErrorAction SilentlyContinue         # Stvaranje preferenci registra GPP-a pomoću akcije "Zamijeni"         # Zamijeni = Stvori ako ne postoji, ažuriraj ako postoji (najpouzdanije)         # Ažuriranje = ažuriranje samo ako postoji (ne uspijeva ako vrijednost ne postoji)         Set-GPPrefRegistryValue -Name $GPOName '             -Kontekstno računalo '             -Zamjena akcije '             -Tipka $RegistryKey '             -ValueName $RegistryValueName '             -Upišite DWord '             -Vrijednost $RegistryValue         Write-Log "Configured GPP registry preference: $RegistryValueName = 0x5944 (Action=Replace)" "OK"     } uhvatiti {         Write-Log "GPP failed, trying Set-GPRegistryValue: $($_. Exception.Message)" "WARN"         # Fallback to Set-GPRegistryValue (funkcionira ako se implementira ADMX)         pokušajte {             Set-GPRegistryValue -Name $GPOName '                 -Tipka $RegistryKey '                 -ValueName $RegistryValueName '                 -Upišite DWord '                 -Vrijednost $RegistryValue             Write-Log "Configured Registry via Set-GPRegistryValue: $RegistryValueName = 0x5944" "OK"         } uhvatiti {             Write-Log "Postavljanje vrijednosti registra nije uspjelo: $($_. Exception.Message)" "ERROR"             povratna $false         }     }     # Treći korak: stvaranje ili dohvaćanje sigurnosne grupe     $existingGroup = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $existingGroup) {         pokušajte {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers targeted for Secure Boot rollout - $GPOName" '                 -Prolaz             Write-Log "Stvorena sigurnosna grupa: $SecurityGroupName" "U redu"         } uhvatiti {             Write-Log "Stvaranje sigurnosne grupe nije uspjelo: $($_. Exception.Message)" "ERROR"             povratna $false         }     } još {         Write-Log "Sigurnosna grupa postoji: $SecurityGroupName" "INFO"         $group = $existingGroup     }     # Četvrti korak: dodavanje računala u sigurnosnu grupu     $added = 0     $failed = 0     foreach ($hostname u $WaveHostnames) {         pokušajte {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } uhvatiti {             $failed++         }     }     Write-Log "Dodano $added računala u sigurnosnu grupu ($failed nije pronađeno u AD)" "U redu"     # Peti korak: konfiguriranje sigurnosnog filtriranja na servisu GPO     pokušajte {         # Ukloni zadanu dozvolu "Autorizirani korisnici" Primijeni dozvolu (zadrži čitanje)         Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         # Dodaj dozvolu Primijeni za našu sigurnosnu grupu         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Konfigurirano sigurnosno filtriranje za: $SecurityGroupName" "U redu"     } uhvatiti {         Write-Log "Konfiguriranje sigurnosnog filtriranja nije uspjelo: $($_. Exception.Message)" "WARN"         Write-Log "GPO se može primijeniti na sva računala u povezanom OU - provjerite ručno" "UPOZORENJE"     }     # Šesti korak: povezivanje GPO-a s OU-om (kritično za primjenu pravilnika)     if ($TargetOU) {         pokušajte {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Da -ErrorAction Stop                 Write-Log "Linked GPO to: $TargetOU" "OK"                 Write-Log "GPO će se primijeniti na sljedeći gpupdate na ciljnim računalima" "INFO"             } još {                 Write-Log "GPO je već povezan s ciljnim OU" "INFO"             }         } uhvatiti {             Write-Log "KRITIČNO: povezivanje GPO-a s OU- om nije uspjelo: $($_. Exception.Message)" "ERROR"             Write-Log "GPO je stvoren, ali NOT LINKED – neće se primjenjivati ni na koja računala!" "POGREŠKA"             Write-Log "Ručno popravak obavezno: New-GPLink -Name '$GPOName' -Target '$TargetOU' -LinkEnabled Da" "ERROR"             vraćanje $false         }     } još {         Write-Log "UPOZORENJE: Nije naveden TargetOU – stvoren je GPO, ali NOT LINKED!" "POGREŠKA"         Write-Log "Ručno povezivanje potrebno da bi GPO snazi" "ERROR"         Write-Log "Pokreni: New-GPLink -Name '$GPOName' -Target '<Your-Domain-DN>' -LinkEnabled Da" "ERROR"     }     # Sedmi korak: provjera konfiguracije GPO-a     Write-Log "Provjera konfiguracije servisa GPO..." "INFO"     pokušajte {         $gpoReport = Get-GPO -Name $GPOName -ErrorAction Stop         Write-Log "GPO status: $($gpoReport.GpoStatus)" "INFO"         # Provjerite je li postavka registra konfigurirana         $regSettings = Get-GPRegistryValue -Name $GPOName -Key "HKLM\SYSTEM\CurrentControlSet\Control\SecureBoot" -ErrorAction SilentlyContinue         if (-not $regSettings) {             # Isprobajte provjeru registra GPP -a (drugačiji put u GPO-u)             Write-Log "Provjera preferenci registra GPP...". "INFO"         }     } uhvatiti {         Write-Log "Nije moguće provjeriti GPO: $($_. Exception.Message)" "WARN"     }     vraćanje $true }                                                                                                

# ============================================================================ # WINCS DEPLOYMENT (alternative to AvailableUpdatesPolicy GPO) Ne, ne============================================================================ # Referenca: https://support.microsoft.com/en-us/topic/windows-configuration-system-wincs-apis-for-secure-boot-d3e64aa0-6095-4f8a-b8e4-fbfda254a8fe # # WinCS Naredbe (pokretanje na krajnjoj točki u kontekstu sustava): # Upit: WinCsFlags.exe /query --key F33E0C8E002 # Primijeni: WinCsFlags.exe /apply --key "F33E0C8E002" # Ponovno postavi: WinCsFlags.exe /reset --key "F33E0C8E002" # # Ova metoda implementira GPO s zakazanim zadatkom koji se pokreće WinCsFlags.exe /apply # kao SUSTAV na ciljnim krajnjim točkama. Slično načinu na koji se implementira skripta otkrivanja, # ali se pokreće jednom (pri pokretanju) umjesto dnevno.

function Deploy-WinCSGPOForWave {     < broj     . SINOPSIS         Implementirajte WinCS Secure Boot enablement putem zakazanog GPO zadatka.. OPIS         Stvara GPO koji implementira zakazani zadatak za pokretanje WinCsFlags.exe /apply         u odjeljku KONTEKST SUSTAVA pri pokretanju računala. Ciljne kontrole sigurnosne grupe.. PARAMETAR GPOName         Naziv GPO-a.. PARAMETAR TargetOU         OU za povezivanje GPO-a s.. PARAMETER SecurityGroupName         Sigurnosna grupa za GPO filtriranje.. Nazivi parametra WaveHostnames         Nazivi glavnog računala za dodavanje u sigurnosnu grupu.. PARAMETAR WinCSKey         Tipka WinCS koja će se primijeniti (zadano: F33E0C8E002).. PARAMETAR DryRun         Ako je istinito, zapisujte samo što će biti učinjeno.#>     param(         [Parameter(Mandatory = $true)]         [niz]$GPOName,                  [Parameter(Mandatory = $false)]         [niz]$TargetOU,                  [Parameter(Mandatory = $true)]         [niz]$SecurityGroupName,                  [Parameter(Mandatory = $true)]         [polje]$WaveHostnames,                  [Parameter(Mandatory = $false)]         [string]$WinCSKey = "F33E0C8E002",                  [Parameter(Mandatory = $false)]         [bool]$DryRun = $false     )          # Zakazana konfiguracija zadatka za WinCsFlags.exe     $TaskName = "SecureBoot-WinCS-Apply"     $TaskPath = "\Microsoft\Windows\SecureBoot\"     $TaskDescription = "Primjenjuje konfiguraciju sigurnog pokretanja putem WinCS - Ključ: $WinCSKey"          Write-Log "Deploying WinCS GPO: $GPOName" "WAVE"     Write-Log "Zadatak će se pokrenuti: WinCsFlags.exe /apply --key '"$WinCSKey'"" "INFO"     Write-Log "Trigger: At system startup (runs once as SYSTEM)" "INFO"          if ($DryRun) {         Write-Log "[DRYRUN] Bi stvorio GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Bi stvorio sigurnosnu grupu: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] dodao bi $(@($WaveHostnames). Brojanje) računala za grupiranje" "INFO"         Write-Log "[DRYRUN] Implementira zakazani zadatak: $TaskName" "INFO"         Write-Log "[DRYRUN] Želi povezati GPO na: $TargetOU" "INFO"         vrati @{             Success = $true             GPOCreated = $false             GroupCreated = $false             ComputersAdded = 0         }     }          pokušajte {         # Uvoz obaveznih modula         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory – zaustavljanje pogreške     } uhvatiti {         Write-Log "Uvoz obaveznih modula nije uspio (GroupPolicy, ActiveDirectory): $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Pogreška = $_. Exception.Message }     }          # Prvi korak: stvaranje ili dohvaćanje GPO-a     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($gpo) {         Write-Log "GPO već postoji: $GPOName" "INFO"     } 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 "Created GPO: $GPOName" "OK"         } uhvatiti {             Write-Log "Stvaranje GPO-a nije uspjelo: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = $_. Exception.Message }         }     }          # Drugi korak: stvaranje zakazanog XML-a zadatka za GPO implementaciju     # Time se stvara zadatak koji se pokreće WinCsFlags.exe /apply pri pokretanju     $taskXml = @" <?xml version="1.0" encoding="UTF-16"?> <verzija zadatka="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">   <RegistrationInfo>     <opisa>$TaskDescription</Description>     WinCsFlags.exe1 stvaranje>sustava</Author>   WinCsFlags.exe5 /RegistrationInfo>   WinCsFlags.exe7 okidači>     WinCsFlags.exe9 boottrigger>       <omogućeno>true</Enabled>       <kašnjenje>PT5M</Delay>     </BootTrigger>   </Triggers>   <glavnice>     <id="Autor">       <UserId>S-1-5-18</UserId>       <RunLevel>Najvišadostupna</RunLevel>     </Glavni>   </Glavnice>   <postavke>     <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>     <neautotostartIfOnBatteries>false</DisallowStartIfOnBatteries>     <stopIfGoingOnBatteries>false</StopIfGoingOnBatteries>     <AllowHardTerminate>true</AllowHardTerminate>     <startWhenAvailable>true</StartWhenAvailable>     <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>     <idleSettings>       <stopOnIdleEnd>false</StopOnIdleEnd>       <RestartOnIdle>false</RestartOnIdle>     </IdleSettings>     <AllowStartOnDemand>true</AllowStartOnDemand>     <omogućeno>true</Enabled>     <skriveno>false</Hidden>     <RunOnlyIfIdle>false</RunOnlyIfIdle>     WinCsFlags.exe03 DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>     WinCsFlags.exe07 UseUnifiedSchedulingEngine>istinito</UseUnifiedSchedulingEngine>     WinCsFlags.exe11 WakeToRun>false</WakeToRun>     WinCsFlags.exe15 ExecutionTimeLimit>PT1H</ExecutionTimeLimit>     WinCsFlags.exe19 DeleteExpiredTaskAfter>P30D</DeleteExpiredTaskNakon>     WinCsFlags.exe23 prioritet>7</Priority>   WinCsFlags.exe27 /Settings>   WinCsFlags.exe29 akcije Context="Author"WinCsFlags.exe30     WinCsFlags.exe31 exec>       WinCsFlags.exe33 naredbeni>WinCsFlags.exe</Command>       WinCsFlags.exe37 argumenti>/apply --key "$WinCSKey"WinCsFlags.exe39 /Argumenti>     WinCsFlags.exe41 /Exec>   WinCsFlags.exe43 /Akcije> WinCsFlags.exe45 /Task> " @

    # Step 3: Deploy scheduled task via GPO Preferences     # Spremi XML zadatka u SYSVOL za GPO zakazane zadatke Neposredni zadatak     pokušajte {         $gpoId = $gpo. Id.ToString()         $sysvolPath = "\\$((Get-ADDomain). DNSRoot)\SYSVOL\$((Get-ADDomain). DNSRoot)\Policies\{$gpoId}\Machine\Preferences\ScheduledTasks"         if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Direktorij -Put $sysvolPath -Force | Izlazna vrijednost -Null         }         # Stvaranje 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}" naziv="$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">       <verzija zadatka="1.3">         <RegistracijaInfo>           <opisa>$TaskDescription</Description>         </RegistrationInfo>         <glavnice>           <id="Autor">             <UserId>NT AUTHORITY\System</UserId>             <vrsta>S4U</LogonType>             <RunLevel>NajvišaDostupna</RunLevel>           </Glavni>         </Glavnice>         <postavke>           <idleSettings>             <trajanje>PT5M</Duration>             <waitTimeout>PT1H</WaitTimeout>             <stopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle>false</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <stopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <startWhenAvailable>true</StartWhenAvailable>           <AllowStartOnDemand>istinito</AllowStartOnDemand>           <omogućeno>true</Enabled>           <skriveno>false</Hidden>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <prioritet>7</Priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </Settings>         <okidači>           <timetrigger>             <StartBoundary>$(Get-Date -Format 'yyyy-MM-dd')T00:00:00</StartBoundary>             <omogućeno>true</Enabled>           </TimeTrigger>         </Triggers>         <akcije>           <exec>             <naredbeni>WinCsFlags.exe</Command>             <argumenti>/apply --key "$WinCSKey"</Argumenti>           </Exec>         </Akcije>       </Task>     </Properties>   </ImmediateTaskV2> </ScheduledTasks> "@ ("@")         $gppTaskXml | Out-File -FilePath (join-path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force         Write-Log "Uveden zakazani zadatak u GPO: $TaskName" "OK"     } uhvatiti {         Write-Log "Implementacija zakazanog XML-a zadatka nije uspjela: $($_. Exception.Message)" "WARN"         Write-Log "Vraćanje na implementaciju winCS-a utemeljenu na registru" "INFO"         # Fallback: Use WinCS registry approach if GPP scheduled task fails         # WinCS također se može pokrenuti putem ključa registra         # (Implementacija ovisi o API-ju registra WinCS ako je dostupan)     }     Četvrti korak: stvaranje ili dohvaćanje sigurnosne grupe     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $group) {         pokušajte {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers targeted for Secure Boot WinCS rollout - $GPOName" '                 -Prolaz             Write-Log "Stvorena sigurnosna grupa: $SecurityGroupName" "U redu"         } uhvatiti {             Write-Log "Stvaranje sigurnosne grupe nije uspjelo: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = $_. Exception.Message }         }     } još {         Write-Log "Sigurnosna grupa postoji: $SecurityGroupName" "INFO"     }     # Peti korak: dodavanje računala u sigurnosnu grupu     $added = 0     $failed = 0     foreach ($hostname u $WaveHostnames) {         pokušajte {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } uhvatiti {             $failed++         }     }     Write-Log "Dodano $added računala u sigurnosnu grupu ($failed nije pronađeno u AD)" "U redu"     # Šesti korak: konfiguriranje sigurnosnog filtriranja na servisu GPO     pokušajte {         Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Konfigurirano sigurnosno filtriranje za: $SecurityGroupName" "U redu"     } uhvatiti {         Write-Log "Konfiguriranje sigurnosnog filtriranja nije uspjelo: $($_. Exception.Message)" "WARN"     }     # Sedmi korak: povezivanje GPO-a s OU-om     if ($TargetOU) {         pokušajte {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }             if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Yes -ErrorAction Stop                 Write-Log "Linked GPO to: $TargetOU" "OK"             } još {                 Write-Log "GPO je već povezan s ciljnim OU" "INFO"             }         } uhvatiti {             Write-Log "KRITIČNO: povezivanje GPO-a s OU- om nije uspjelo: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = "GPO veza nije uspjela: $($_. Exception.Message)" }         }     }     Write-Log "Dovršena je implementacija GPO-a za WinCS" "U redu"     Write-Log "Računala će se pokrenuti WinCsFlags.exe sljedećem GPO osvježavanju + ponovno pokretanje/pokretanje" "INFO"     vrati @{         Success = $true         GPOCreated = $true         GroupCreated = $true         ComputersAdded = $added         ComputersFailed = $failed     } }                                                                                        

# Wrapper function to maintain compatibility with main loop funkcija Deploy-WinCSForWave {     param(         [Parameter(Mandatory = $true)]         [polje]$WaveHostnames,         [Parameter(Mandatory = $false)]         [string]$WinCSKey = "F33E0C8E002",         [Parameter(Mandatory = $false)]         [string]$WavePrefix = "SecureBoot-Rollout",         [Parameter(Mandatory = $false)]         [int]$WaveNumber = 1,         [Parameter(Mandatory = $false)]         [niz]$TargetOU,         [Parameter(Mandatory = $false)]         [bool]$DryRun = $false     )     $gpoName = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $securityGroup = "${WavePrefix}-WinCS-Wave${WaveNumber}"     $result = Deploy-WinCSGPOForWave '         -GPOName $gpoName '         -TargetOU $TargetOU '         -SecurityGroupName $securityGroup '         -WaveHostnames $WaveHostnames '         -WinCSKey $WinCSKey '         -DryRun $DryRun     # Pretvori u očekivani oblik povrata     vrati @{         Success = $result. Uspjeh         Primijenjeno = $result. ComputersAdded         Preskočeno = 0         Nije uspjelo = ako ($result. ComputersFailed) { $result. ComputersFailed } else { 0 }         Rezultati = @()     } }                                                            

# ============================================================================ # OMOGUĆI IMPLEMENTACIJU ZADATKA Ne, ne============================================================================ # Implementacija Enable-SecureBootUpdateTask.ps1 na uređaje s onemogućenim zakazanim zadatkom.# Koristi GPO s neposrednim zakazanim zadatkom koji se pokreće jednom.

function Deploy-EnableTaskGPO {     < broj     . SINOPSIS         Implementacija Enable-SecureBootUpdateTask.ps1 putem zakazanog GPO zadatka.. OPIS         Stvara GPO koji implementira jednokratni zakazani zadatak radi omogućivanja         Zakazani zadatak sigurnog pokretanja na ciljnim uređajima.. PARAMETAR TargetOU         OU za povezivanje GPO-a s.. NAZIVI PARAMETRA TargetHostnames         Nazivi glavnog računala uređaja s onemogućenim zadatkom (iz izvješća o zbirci).. PARAMETAR DryRun         Ako je istinito, zapisujte samo što će biti učinjeno.#>     param(         [Parameter(Mandatory = $false)]         [niz]$TargetOU,                  [Parameter(Mandatory = $true)]         [polje]$TargetHostnames,                  [Parameter(Mandatory = $false)]         [bool]$DryRun = $false     )          $GPOName = "SecureBoot-EnableTask-Remediation"     $SecurityGroupName = "SecureBoot-EnableTask-Devices"     $TaskName = "SecureBoot-EnableTask-OneTime"     $TaskDescription = "Jednokratni zadatak za omogućavanje zakazanog zadatka sigurnog pokretanja"          Write-Log "=" * 70 "INFO"     Write-Log "IMPLEMENTACIJA POPRAVKA ZADATKA" "INFORMACIJE"     Write-Log "=" * 70 "INFO"     Write-Log "Ciljni uređaji: $($TargetHostnames.Count)" "INFO"     Write-Log "GPO: $GPOName" "INFO"     Write-Log "Sigurnosna grupa: $SecurityGroupName" "INFO"          if ($DryRun) {         Write-Log "[DRYRUN] Bi stvorio GPO: $GPOName" "INFO"         Write-Log "[DRYRUN] Bi stvorio sigurnosnu grupu: $SecurityGroupName" "INFO"         Write-Log "[DRYRUN] Dodao bi $($TargetHostnames.Count) računala u grupu" "INFO"         Write-Log "[DRYRUN] implementira jednokratni zakazani zadatak radi omogućivanja ažuriranja sigurnog pokretanja" "INFO"         Write-Log "[DRYRUN] Želi povezati GPO na: $TargetOU" "INFO"         vrati @{             Success = $true             ComputersAdded = 0             DryRun = $true         }     }          pokušajte {         # Uvoz obaveznih modula         Import-Module GroupPolicy -ErrorAction Stop         Import-Module ActiveDirectory – zaustavljanje pogreške     } uhvatiti {         Write-Log "Uvoz obaveznih modula nije uspio: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Pogreška = $_. Exception.Message }     }          # Prvi korak: stvaranje ili dohvaćanje GPO-a     $gpo = Get-GPO -Name $GPOName -ErrorAction SilentlyContinue     if ($gpo) {         Write-Log "GPO već postoji: $GPOName" "INFO"     } 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 "Created GPO: $GPOName" "OK"         } uhvatiti {             Write-Log "Stvaranje GPO-a nije uspjelo: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = $_. Exception.Message }         }     }          # Drugi korak: implementacija zakazanog XML-a zadatka u GPO SYSVOL     # Zadatak pokreće naredbu ljuske PowerShell radi omogućivanja zadatka sigurnog pokretanja i ažuriranja     pokušajte {         $sysvolPath = "\\$($env:USERDNSDOMAIN)\SYSVOL\$($env:USERDNSDOMAIN)\Policies\{$($gpo. ID)}\Računalo\Preference\Zakazanizadaci"                  if (-not (Test-Path $sysvolPath)) {             New-Item -ItemType Direktorij -Put $sysvolPath -Force | Izlazna vrijednost -Null         }                  # PowerShell naredba za omogućivanje zadatka secure-boot-update         $enableCommand = 'schtasks.exe /Change /TN "\Microsoft\Windows\PI\Secure-Boot-Update" /ENABLE 2>$null; if ($LASTEXITCODE -ne 0) { Get-ScheduledTask -TaskPath "\Microsoft\Windows\PI\" -TaskName "Secure-Boot-Update" -ErrorAction SilentlyContinue | Enable-ScheduledTask }'                  # Naredba Kodiraj za sigurno ugrađivanje XML-a         $encodedCommand = [Pretvori]::ToBase64String([Text.Encoding]::Unicode.GetBytes($enableCommand))                  $taskGuid = [guid]::NewGuid(). ToString("B"). ToUpper()                  # GPP Scheduled Task XML – neposredni 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">       <verzija zadatka="1.3">         <RegistrationInfo>           <opisa>$TaskDescription</Description>         </RegistrationInfo>         <glavnice>           <id="Autor">             <UserId>S-1-5-18</UserId>             <RunLevel>Najvišadostupna</RunLevel>           </Glavni>         </Glavnice>         <postavke>           <idleSettings>             <trajanje>PT5M</Duration>             <waitTimeout>PT1H</WaitTimeout>             <stopOnIdleEnd>false</StopOnIdleEnd>             <RestartOnIdle>false</RestartOnIdle>           </IdleSettings>           <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>           <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>           <stopIfGoingOnBatteries>false</StopIfGoingOnBatteries>           <AllowHardTerminate>true</AllowHardTerminate>           <startWhenAvailable>true</StartWhenAvailable>           <AllowStartOnDemand>istinito</AllowStartOnDemand>           <omogućeno>true</Enabled>           <skriveno>false</Hidden>           <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>           <prioritet>7</priority>           <DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter>         </Settings>         <akcije>           <exec>             <naredbeni>powershell.exe</Command>             <->-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand</argumenti>           </Exec>         </Akcije>       </Task>     </Properties>   </ImmediateTaskV2> </ScheduledTasks> "@ ("@")                  $gppTaskXml | Out-File -FilePath (join-path $sysvolPath "ScheduledTasks.xml") -Encoding UTF8 -Force         Write-Log "Implementiran jednokratni zakazani zadatak u GPO: $TaskName" "U redu"              } uhvatiti {         Write-Log "Implementacija zakazanog XML-a zadatka nije uspjela: $($_. Exception.Message)" "ERROR"         return @{ Success = $false; Pogreška = $_. Exception.Message }     }          # Treći korak: stvaranje ili dohvaćanje sigurnosne grupe     $group = Get-ADGroup -Filter "Name -eq '$SecurityGroupName'" -ErrorAction SilentlyContinue     if (-not $group) {         pokušajte {             $group = New-ADGroup -Name $SecurityGroupName '                 -GroupCategory Security '                 -GroupScope DomainLocal '                 -Description "Computers with disabled Secure-Boot-Update task - targeted for remediation" '                 -Prolaz             Write-Log "Stvorena sigurnosna grupa: $SecurityGroupName" "U redu"         } uhvatiti {             Write-Log "Stvaranje sigurnosne grupe nije uspjelo: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = $_. Exception.Message }         }     } još {         Write-Log "Sigurnosna grupa postoji: $SecurityGroupName" "INFO"     }          # Četvrti korak: dodavanje računala u sigurnosnu grupu     $added = 0     $failed = 0     foreach ($hostname u $TargetHostnames) {         pokušajte {             $computer = Get-ADComputer -Identity $hostname -ErrorAction Stop             Add-ADGroupMember -Identity $SecurityGroupName -Members $computer -ErrorAction SilentlyContinue             $added++         } uhvatiti {             $failed++             Write-Log "Računalo nije pronađeno u AD: $hostname" "WARN"         }     }     Write-Log "Dodano $added računala u sigurnosnu grupu ($failed nije pronađeno u AD)" "U redu"          # Peti korak: konfiguriranje sigurnosnog filtriranja na servisu GPO     pokušajte {         Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType Group -PermissionLevel GpoRead -Replace -ErrorAction SilentlyContinue         Set-GPPermission -Name $GPOName -TargetName $SecurityGroupName -TargetType Group -PermissionLevel GpoApply -ErrorAction Stop         Write-Log "Konfigurirano sigurnosno filtriranje za: $SecurityGroupName" "U redu"     } uhvatiti {         Write-Log "Konfiguriranje sigurnosnog filtriranja nije uspjelo: $($_. Exception.Message)" "WARN"     }          # 6. korak: povezivanje GPO-a s OU-om     if ($TargetOU) {         pokušajte {             $existingLink = Get-GPInheritance -Target $TargetOU -ErrorAction SilentlyContinue |                  Select-Object -ExpandProperty GpoLinks |                  Where-Object { $_. DisplayName -eq $GPOName }                          if (-not $existingLink) {                 New-GPLink -Name $GPOName -Target $TargetOU -LinkEnabled Da -ErrorAction Stop                 Write-Log "Linked GPO to: $TargetOU" "OK"             } još {                 Write-Log "GPO je već povezan s ciljnim OU" "INFO"             }         } uhvatiti {             Write-Log "Nije uspjelo povezivanje GPO-a s OU: $($_. Exception.Message)" "ERROR"             return @{ Success = $false; Pogreška = "GPO veza nije uspjela: $($_. Exception.Message)" }         }     } još {         Write-Log "Nije naveden TargetOU – GPO će se morati ručno povezati" "UPOZORENJE"     }          Write-Log "" "INFO"     Write-Log "OMOGUĆI DOVRŠENJE IMPLEMENTACIJE ZADATKA" "U redu"     Write-Log "Uređaji će pokrenuti zadatak omogućivanja pri sljedećem GPO osvježavanju (gpupdate)" "INFO"     Write-Log " zadatak runs jedanput kao SISTEM i enables Secure-Boot-Update" "INFO"     Write-Log "" "INFO"          vrati @{         Success = $true         ComputersAdded = $added         ComputersFailed = $failed         GPOName = $GPOName         SecurityGroup = $SecurityGroupName     } }

# ============================================================================ # OMOGUĆI ZADATAK NA ONEMOGUĆENIM UREĐAJIMA Ne, ne============================================================================ if ($EnableTaskOnDisabled) {     Write-Host ""     Write-Host ("=" * 70) -Boja prednjeg plana Žuta     Write-Host " ENABLE TASK REMEDIATION - Fixing Disabled Scheduled Tasks" -ForegroundColor Yellow     Write-Host ("=" * 70) -Boja prednjeg plana Žuta     Write-Host ""     # Pronalaženje uređaja s onemogućenim zadatkom iz podataka o agregaciji     if (-not $AggregationInputPath) {         Write-Host "POGREŠKA: -AggregationInputPath je potreban za identifikaciju uređaja s onemogućenim zadatkom" -ForegroundColor Red         Write-Host "Korištenje: .\Start-SecureBootRolloutOrchestrator.ps1 -EnableTaskOnDisabled -AggregationInputPath <put> -ReportBasePath <put>" -ForegroundColor Gray         izlaz 1     }     Write-Host "Skeniranje za uređaje s onemogućenim zadatkom secure-boot-update..." -ForegroundColor Cyan     # Učitaj JSON datoteke i pronađi uređaje s onemogućenim zadatkom     $jsonFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -Recurse -ErrorAction SilentlyContinue |                  Where-Object { $_. Name -notmatch "ScanHistory|RolloutState|RolloutPlan" }     $disabledTaskDevices = @()     foreach ($file u $jsonFiles) {         pokušajte {             $device = Get-Content $file. FullName – neobrađeno | ConvertFrom-Json             ako ($device. SecureBootTaskEnabled -eq $false -ili                 $device je. SecureBootTaskStatus -eq 'Onemogućeno' -ili                 $device je. SecureBootTaskStatus -eq 'NotFound') {                 # Obuhvaća samo uređaje koji još nisu ažurirani (nema događaja 1808)                 if ([int]$device. Event1808Count -eq 0) {                     $disabledTaskDevices += $device. Hostname                 }             }         } uhvatiti {             # Preskoči datoteke koje nisu valjane         }     }     $disabledTaskDevices = $disabledTaskDevices | Select-Object – jedinstveno     if ($disabledTaskDevices.Count -eq 0) {         Write-Host ""         Write-Host "Nije pronađen nijedan uređaj s onemogućenim zadatkom secure-boot-update". -ForegroundColor Green         Write-Host "Svi uređaji imaju omogućen zadatak ili su već ažurirani." -ForegroundColor Gray         izlaz 0     }     Write-Host ""     Write-Host "Pronađeno $($disabledTaskDevices.Count) uređaji s onemogućenim zadatkom:" -ForegroundColor Yellow     $disabledTaskDevices | Select-Object - Prvih 20 | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray }     if ($disabledTaskDevices.Count -gt 20) {         Write-Host " ... and $($disabledTaskDevices.Count - 20) more" -ForegroundColor Gray     }     Write-Host ""     # Implementacija GPO-a omogućivanja zadatka     $result = Deploy-EnableTaskGPO -TargetHostnames $disabledTaskDevices -TargetOU $TargetOU -DryRun $DryRun     ako ($result. Uspjeh) {         Write-Host ""         Write-Host "SUCCESS: Enable Task GPO deployed" -ForegroundColor Green         Write-Host " Računala dodana u sigurnosnu grupu: $($result. ComputersAdded)" -ForegroundColor Cyan         ako ($result. ComputersFailed -gt 0) {             Write-Host " Računala nisu pronađena u ad: $($result. ComputersFailed)" - Boja prednjeg plana Žuta         }         Write-Host ""         Write-Host "SLJEDEĆI KORACI:" -ForegroundColor White         Write-Host " 1.                                              Uređaji će primiti GPO prilikom sljedećeg osvježavanja (gpupdate /force)" -ForegroundColor Gray         Write-Host " 2. Jednokratni zadatak omogućit će sigurnosno ažuriranje" -ForegroundColor Gray         Write-Host " 3. Ponovno pokretanje agregacije radi provjere je li zadatak sada omogućen" -ForegroundColor Gray     } još {         Write-Host ""         Write-Host "NIJE USPJELO: Nije moguće implementirati Enable Task GPO" -ForegroundColor Red         Write-Host "Pogreška: $($result. Error)" -ForegroundColor Red     }          izlaz 0 }

# ============================================================================ # GLAVNA PETLJA ORKESTRACIJE # ============================================================================

Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana Cyan Write-Host " SECURE BOOT ROLLOUT ORCHESTRATOR - CONTINUOUS DEPLOYMENT" -ForegroundColor Cyan Write-Host ("=" * 80) -Boja prednjeg plana Cyan Write-Host ""

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

if ($UseWinCS) {     Write-Host "[WinCS MODE]" - Boja prednjeg plana Žuta     Write-Host "Korištenje WinCsFlags.exe umjesto GPO/AvailableUpdatesPolicy" -ForegroundColor Yellow     Write-Host "WinCS ključ: $WinCSKey" -Boja prednjeg plana Siva     Write-Host "" }

Write-Log "Starting Secure Boot Rollout Orchestrator" "INFO" Write-Log "Put unosa: $AggregationInputPath" "INFO" Write-Log "Put izvješća: $ReportBasePath" "INFO" if ($UseWinCS) {     Write-Log "Način implementacije: WinCS (WinCsFlags.exe /apply --key '"$WinCSKey'")" "INFO" } još {     Write-Log "Način implementacije: GPO (AvailableUpdatesPolicy)" "INFO" }

# Resolve TargetOU - default to domain root for domain-wide coverage # Potrebno je samo za način implementacije GPO-a (WinCS ne zahtijeva AD/GPO) if (-not $UseWinCS -and -not $TargetOU) {     pokušajte {         # Isprobajte više metoda za dohvaćanje DN domene         $domainDN = $null         # Metoda 1: Get-ADDomain (potreban je RSAT-AD-PowerShell)         pokušajte {             Import-Module ActiveDirectory – zaustavljanje pogreške             $domainDN = (Get-ADDomain -ErrorAction Stop). Razlikovni Naziv         } uhvatiti {             Write-Log "Get-ADDomain nije uspio: $($_. Exception.Message)" "WARN"         }         # Metoda 2: Koristite RootDSE putem ADSI         if (-not $domainDN) {             pokušajte {                 $rootDSE = [ADSI]"LDAP://RootDSE"                 $domainDN = $rootDSE.defaultNamingContext.ToString()             } uhvatiti {                 Write-Log "ADSI RootDSE nije uspio: $($_. Exception.Message)" "WARN"             }         }         # Metoda 3: Raščlanjivanje iz članstva u domeni računala         if (-not $domainDN) {             pokušajte {                 $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()                 $domainDN = "DC=" + ($domain. Naziv -replace '\.', ',DC=')             } uhvatiti {                 Write-Log "GetComputerDomain nije uspio: $($_. Exception.Message)" "WARN"             }         }         if ($domainDN) {             $TargetOU = $domainDN             Write-Log "Target: Domain Root ($domainDN) - GPO will apply domain-wide via security group filtering" "INFO"         } još {             Write-Log "Ne može se odrediti domena DN – GPO će se stvoriti, ali NOT LINKED!" "POGREŠKA"             Write-Log "Navedite parametar -TargetOU ili povežite GPO ručno nakon stvaranja" "ERROR"             $TargetOU = $null         }     } uhvatiti {         Write-Log "Nije moguće dohvatiti DN domenu – GPO će se stvoriti, ali neće biti povezan.                                     Po potrebi ručno povežite vezu." "UPOZORI"         Write-Log "Pogreška: $($_. Exception.Message)" "WARN"         $TargetOU = $null     } } još {     Write-Log "Cilj OU: $TargetOU" "INFO" }

Write-Log "Max Wait Hours: $MaxWaitHours" "INFO" Write-Log "Interval ankete: $PollIntervalMinutes minuta" "INFO" if ($LargeScaleMode) {     Write-Log "LargeScaleMode enabled (veličina grupe: $ProcessingBatchSize, uzorak zapisnika: $DeviceLogSampleSize)" "INFO" }

# ============================================================================ # PROVJERA PREDUVJETA: provjera je li otkrivanje implementiran i radi # ============================================================================

Write-Host "" Write-Log "Provjera preduvjeta..." "INFO"

$detectionCheck = Test-DetectionGPODeployed -JsonPath $AggregationInputPath if (-not $detectionCheck.IsDeployed) {     Write-Log $detectionCheck.Poruka "POGREŠKA"     Write-Host ""     Write-Host "OBAVEZNO: prvo implementiraj infrastrukturu otkrivanja:" -Boja prednjeg plana Žuta     Write-Host " 1. Pokreni: Deploy-GPO-SecureBootCollection.ps1 -OUPath 'OU=...' -OutputPath '\\server\SecureBootLogs$'" -ForegroundColor Cyan     Write-Host 2. Pričekajte da uređaji prijave (12-24 sata)" -ForegroundColor Cyan     Write-Host " 3. Re-run this orchestrator" -ForegroundColor Cyan     Write-Host ""     if (-not $DryRun) {         Vratiti     } } još {     Write-Log $detectionCheck.Poruka "U redu" }

# Check data freshness $freshness = Get-DataFreshness -JsonPath $AggregationInputPath Write-Log "Svježina podataka: $($freshness. Datoteke TotalFiles, $($freshness. FreshFiles) fresh (<24h), $($freshness. StaleFiles) zastarjelo (>72h)" "INFO" ako ($freshness. Upozorenje) {     Write-Log $freshness, ne. Upozorenje "UPOZORENJE" }

# Load Allow List (targeted rollout - ONLY these devices will be rolled out) $allowedHostnames = @() if ($AllowListPath -ili $AllowADGroup) {     $allowedHostnames = Get-AllowedHostnames -AllowFilePath $AllowListPath -ADGroupName $AllowADGroup     if ($allowedHostnames.Count -gt 0) {         Write-Log "AllowList: ONLY $($allowedHostnames.Count) uređaji će se smatrati za implementaciju" "INFO"     } još {         Write-Log "AllowList naveden, ali nije pronađen nijedan uređaj – time će se blokirati sva rollouts!" "UPOZORI"     } }

# Load VIP/exclusion list (BlockList) $excludedHostnames = @() if ($ExclusionListPath -or $ExcludeADGroup) {     $excludedHostnames = Get-ExcludedHostnames -ExclusionFilePath $ExclusionListPath -ADGroupName $ExcludeADGroup     if ($excludedHostnames.Count -gt 0) {         Write-Log "VIP izuzetak: $($excludedHostnames.Count) uređaji će se preskočiti s implementacije" "INFO"     } }

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

if ($rolloutState.Status -eq "NotStarted") {     $rolloutState.Status = "InProgress"     $rolloutState.StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     Write-Log "Pokretanje novog izdanja" "WAVE" }

Write-Log "Current Wave: $($rolloutState.CurrentWave)" "INFO" Write-Log "Blokirane grupe: $($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 Bijela     Write-Log "=== ITERACIJA $iterationCount ===" "VAL"     Write-Host ("=" * 80) -Boja prednjeg plana Bijela     # Korak 1: Pokretanje agregacije     Write-Log "Korak 1: pokretanje agregacije..." "INFO"     # Orchestrator uvijek ponovno koristi jednu mapu (LargeScaleMode) da bi izbjegao bloat diska     # Administratori koji izvode agregator ručno dohvaćaju vremenske oznake mapa za brze snimke u točkama u vremenu     $aggregationPath = Join-Path $ReportBasePath "Aggregation_Current"     # Provjerite svježinu podataka prije agregacije     $freshness = Get-DataFreshness -JsonPath $AggregationInputPath     Write-Log "Svježina podataka: $($freshness. FreshFiles)/$($freshness. TotalFiles) uređaji prijavljeni u posljednjih 24h" "INFO"     ako ($freshness. Upozorenje) {         Write-Log $freshness, ne. Upozorenje "UPOZORENJE"     }     $aggregateScript = Join-Path $ScriptRoot "Aggregate-SecureBootData.ps1"     $scanHistoryPath = Join-Path $ReportBasePath "ScanHistory.json"     $rolloutSummaryPath = Join-Path $stateDir "SecureBootRolloutSummary.json"     if (test-path $aggregateScript) {         if (-not $DryRun) {             # Orchestrator uvijek koristi strujanje + inkrementalno za učinkovitost             # Agregator se automatski uzdiže na PS7 ako je dostupan za najbolje performanse             $aggregateParams = @{                 InputPath = $AggregationInputPath                 OutputPath = $aggregationPath                 StreamingMode = $true                 IncrementalMode = $true                 SkipReportIfUnchanged = $true                 ParallelThreads = 8             }             # Sažetak o unošavanja prolaza ako postoji (za podatke o brzini/projekciji)             if (test-path $rolloutSummaryPath) {                 $aggregateParams['RolloutSummaryPath'] = $rolloutSummaryPath             }             & $aggregateScript @aggregateParams             # Pokaži naredbu za generiranje pune HTML nadzorne ploče s tablicama uređaja             Write-Host ""             Write-Host "Za generiranje pune HTML nadzorne ploče s tablicama proizvođača/modela, pokreni:" -ForegroundColor Yellow             Write-Host " $aggregateScript -InputPath '"$AggregationInputPath'" -OutputPath '"$aggregationPath'"" -ForegroundColor Yellow             Write-Host ""         } još {             Write-Log "[DRYRUN] Bi pokrenuti agregaciju" "INFO"             # Na servisu DryRun izravno koristite postojeće podatke o agregaciji iz programa ReportBasePath             $aggregationPath = $ReportBasePath         }     }     $rolloutState.LastAggregation = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     # Korak 2: Učitavanje trenutnog statusa uređaja     Write-Log "2. korak: učitavanje statusa uređaja..." "INFO"     $notUptodateCsv = Get-ChildItem -Path $aggregationPath -Filter "*NotUptodate*.csv" -ErrorAction SilentlyContinue |          Where-Object { $_. Naziv -notlike "*Buckets*" } |          Sort-Object LastWriteTime -Descending |          Select-Object - Prvi 1     if (-not $notUptodateCsv -and -not $DryRun) {         Write-Log "Nisu pronađeni podaci o agregaciji.                                            Čekanje..." "UPOZORI"         Start-Sleep -seconds ($PollIntervalMinutes * 60)         Nastaviti     }     $notUpdatedDevices = ako ($notUptodateCsv) { Import-Csv $notUptodateCsv.FullName } još { @() }     Write-Log "Uređaji nisu ažurirani: $($notUpdatedDevices.Count)" "INFO"     $notUpdatedIndexes = Get-NotUpdatedIndexes -Devices $notUpdatedDevices     # Treći korak: ažuriranje povijesti uređaja (praćenje prema nazivu glavnog računala)     Write-Log "Treći korak: ažuriranje povijesti uređaja..." "INFO"     Update-DeviceHistory -CurrentDevices $notUpdatedDevices -DeviceHistory $deviceHistory     Save-DeviceHistory -Povijest $deviceHistory     # Četvrti korak: traženje blokiranih grupa (nedostupnih uređaja)     $existingBlockedCount = $blockedBuckets.Count     Write-Log "Korak 4: Provjera blokiranih grupa (pinging uređaji nakon razdoblja čekanja)..." "INFO"     if ($existingBlockedCount -gt 0) {         Write-Log "Trenutno blokirane grupe iz prethodnih izvođenja: $existingBlockedCount" "INFO"     }     if ($adminApproved.Count -gt 0) {         Write-Log "Administrator-odobrene grupe (neće se ponovno blokirati): $($adminApproved.Count)" "INFO"     }     $newlyBlocked = Update-BlockedBuckets -RolloutState $rolloutState -BlockedBuckets $blockedBuckets -AdminApproved $adminApproved -NotUpdatedDevices $notUpdatedDevices -NotUpdatedIndexes $notUpdatedIndexes -MaxWaitHours $MaxWaitHours -DryRun:$DryRun     if ($newlyBlocked.Count -gt 0) {         Save-BlockedBuckets -blokirano $blockedBuckets         Write-Log "Novo blokirane grupe (ova iteracija): $($newlyBlocked.Count)" "BLOKIRANO"     }     # Korak 4b: automatsko deblokiranje grupa u kojima su se uređaji ažurirali     $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 deblokirane grupe (ažurirani uređaji): $($autoUnblocked.Count)" "U redu"     }     # Peti korak: izračun preostalih uređaja koji ispunjavaju uvjete     $eligibleCount = 0     foreach ($device u $notUpdatedDevices) {         $bucketKey = Get-BucketKey $device         if (-not $blockedBuckets.Contains($bucketKey)) {             $eligibleCount++         }     }     Write-Log "Preostali uređaji koji ispunjavaju uvjete: $eligibleCount" "INFO"     Write-Log "Blokirane grupe: $($blockedBuckets.Count)" "INFO"     # 6. korak: provjera dovršetka     if ($eligibleCount -eq 0) {         Write-Log "ROLLOUT COMPLETE – svi uređaji koji ispunjavaju uvjete su ažurirani!" "U redu"         $rolloutState.Status = "Dovršeno"         $rolloutState.CompletedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"         Save-RolloutState - savezna $rolloutState         Slomiti     }     # 6. korak: generiranje i implementacija sljedećeg vala     Write-Log "6. korak: generiranje vala za postavljanje..." "INFO"     $waveDevices = New-RolloutWave -AggregationPath $aggregationPath -BlockedBuckets $blockedBuckets -RolloutState $rolloutState -AllowedHostnames $allowedHostnames -ExcludedHostnames $excludedHostnames     # Provjerite imamo li uređaje za implementaciju ($waveDevices mogu biti $null, prazni ili sa stvarnim uređajima)     $hasDevices = $waveDevices -i @($waveDevices | Where-Object { $_ }). Broj -gt 0     if ($hasDevices) {         # Samo broj vala inkrementa kada zapravo imamo uređaje za implementaciju         $rolloutState.CurrentWave++         Write-Log "Val $($rolloutState.CurrentWave): $(@($waveDevices). Count) uređaji" "WAVE"         # Implementirajte GPO pomoću inlined funkcije         $gpoName = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $securityGroup = "${WavePrefix}-Wave$($rolloutState.CurrentWave)"         $hostnames = @($waveDevices | ForEach-Object {             ako ($_. Naziv glavnog računala) { $_. Hostname } elseif ($_. HostName) { $_. HostName } još { $null }         } | Where-Object { $_ })         # Spremi datoteku naziva glavnog računala za referencu/nadzor         $hostnamesFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_Hostnames.txt"         $hostnames | Out-File $hostnamesFile kodiranje UTF8         # Provjerite imamo li nazive glavnog računala za implementaciju         ako ($hostnames. Count -eq 0) {             Write-Log "Valjani nazivi glavnog računala nisu pronađeni u valu $($rolloutState.CurrentWave) – na uređajima možda nedostaje svojstvo Hostname" "WARN"             Write-Log "Preskakanje implementacije za ovaj val – provjera podataka uređaja" "UPOZORENJE"             # Još uvijek čekati prije sljedećeg iteracija             if (-not $DryRun) {                 Write-Log "Spavanje na $PollIntervalMinutes minuta prije ponovnog pokušaja..." "INFO"                 Start-Sleep sekundi ($PollIntervalMinutes * 60)             }             Nastaviti         }         Write-Log "Implementacija u $($hostnames. Count) nazivi glavnog računala u valu $($rolloutState.CurrentWave)" "INFO"         # Implementirajte pomoću metode WinCS ili GPO na temelju parametra -UseWinCS         if ($UseWinCS) {             # Metoda WinCS: stvorite GPO s zakazanim zadatkom koji će se WinCsFlags.exe kao SUSTAV na svakoj krajnjoj točki             Write-Log "Using WinCS deployment method (Key: $WinCSKey)" "WAVE"             $wincsResult = Deploy-WinCSForWave -WaveHostnames $hostnames '                 -WinCSKey $WinCSKey '                 -WavePrefix $WavePrefix '                 -WaveNumber $rolloutState.CurrentWave '                 -TargetOU $TargetOU '                 -DryRun:$DryRun             if (-not $wincsResult.Success) {                 Write-Log "Implementacija winCS-a nije uspjela – primijenjeno: $($wincsResult.Applied), Failed: $($wincsResult.Failed)" "WARN"             } još {                 Write-Log "Uspješna implementacija winCS-a – primijenjeno: $($wincsResult.Applied), Preskočeno: $($wincsResult.Skipped)" "U redu"             }             # Save WinCS results for audit             $wincsResultFile = Join-Path $stateDir "Wave$($rolloutState.CurrentWave)_WinCS_Results.json"             $wincsResult | ConvertTo-Json -Dubina 5 | Out-File $wincsResultFile kodiranje UTF8         } još {             # Metoda GPO: stvaranje GPO-a s postavkom registra AvailableUpdatesPolicy             $gpoResult = Deploy-GPOForWave -GPOName $gpoName -TargetOU $TargetOU -SecurityGroupName $securityGroup -WaveHostnames $hostnames -DryRun:$DryRun             if (-not $gpoResult) {                 Write-Log "GPO implementacija nije uspjela – ponovno će pokušati sljedeće iteraciju" "ERROR"             }         }         # Record wave in state         $waveRecord = @{             WaveNumber = $rolloutState.CurrentWave             StartedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"             DeviceCount = @($waveDevices). Računati             Uređaji = @($waveDevices | ForEach-Object {                 @{ (1000                     Naziv glavnog računala = ako ($_. Naziv glavnog računala) { $_. Hostname } elseif ($_. HostName) { $_. HostName } još { $null }                     BucketKey = Get-BucketKey $_                 }             Ne, ne, ne, ne.         }         # Provjerite je li WaveHistory uvijek polje prije dodavanja (sprječava probleme sa spajanjem raspršivanja)         $rolloutState.WaveHistory = @($rolloutState.WaveHistory) + @($waveRecord)         $rolloutState.TotalDevicesTargeted += @($waveDevices). Računati         Save-RolloutState - savezna $rolloutState         Write-Log "Val $($rolloutState.CurrentWave).                                                                                                                                                                                        Pričekajte $PollIntervalMinutes minuta..." "U redu"     } još {         # Prikaži status implementiranih uređaja koji čekaju ažuriranja         Write-Log "" "INFO"         Write-Log "========== SVI UREĐAJI IMPLEMENTIRANA - ČEKANJE NA STATUS ==========" "INFO"                  # Nabavite sve implementirane uređaje iz povijesti vala         $allDeployedLookup = @{}         foreach ($wave in $rolloutState.WaveHistory) {             ($device $wave. Uređaji) {                 ako ($device. Naziv glavnog računala) {                     $allDeployedLookup[$device. Naziv glavnog računala] = @{                         Naziv glavnog računala = $device. Hostname                         BucketKey = $device. Ključ kante                         DeployedAt = $wave. StartedAt                         WaveNumber = $wave. WaveNumber                     }                 }             }         }         $allDeployedDevices = @($allDeployedLookup.Vrijednosti)                  if ($allDeployedDevices.Count -gt 0) {             # Pronađite koji su implementirani uređaji i dalje na čekanju (na popisu NotUpdated)             $stillPendingCount = 0             $noLongerPendingCount = 0             $pendingSample = @()             foreach ($deployed u $allDeployedDevices) {                 if ($notUpdatedIndexes.HostSet.Contains($deployed. Naziv glavnog računala)) {                     $stillPendingCount++                     if ($pendingSample.Count -lt $DeviceLogSampleSize) {                         $pendingSample += $deployed. Hostname                     }                 } još {                     $noLongerPendingCount++                 }             }                          # Get actual Updated counts from aggregation - differentiate Event 1808 vs UEFICA2023Status             $summaryCsv = Get-ChildItem -Path $aggregationPath -Filter "*Summary*.csv" |                  Sort-Object LastWriteTime -Descending | Select-Object - Prvi 1             $actualUpdated = 0             $totalDevicesFromSummary = 0             $event 1808Count = 0             $uefiStatusUpdated = 0             $needsRebootSample = @()                          if ($summaryCsv) {                 $summary = Import-Csv $summaryCsv.FullName | Select-Object - Prvi 1                 ako ($summary. Ažurirano) { $actualUpdated = [int]$summary. Ažurirano }                 ako ($summary. TotalDevices) { $totalDevicesFromSummary = [int]$summary. TotalDevices }             }                          # Izračun brzine iz povijesti vala (uređaji ažurirani po danu)             $devicesPerDay = 0             if ($rolloutState.StartedAt -and $actualUpdated -gt 0) {                 $startDate = [datetime]::P arse($rolloutState.StartedAt)                 $daysElapsed = ((Get-Date) - $startDate). TotalDays                 if ($daysElapsed -gt 0) {                     $devicesPerDay = $actualUpdated / $daysElapsed                 }             }                          # Save rollout summary with weekend-aware projections             # Koristite zbroj za agregator NotUptodate (isključuje SB OFF uređaje) radi dosljednosti             $notUpdatedCount = ako ($summary -i $summary. NotUptodate) { [int]$summary. NotUptodate } još { $totalDevicesFromSummary - $actualUpdated }             Save-RolloutSummary -State $rolloutState '                 -TotalDevices $totalDevicesFromSummary '                 -UpdatedDevices $actualUpdated '                 -NotUpdatedDevices $notUpdatedCount '                 -DevicesPerDay $devicesPerDay                          # Provjerite neobrađene podatke za uređaje s UEFICA2023Status=Ažurirano, ali bez događaja 1808 (potrebno je ponovno pokretanje)             $dataFiles = Get-ChildItem -Path $AggregationInputPath -Filter "*.json" -ErrorAction SilentlyContinue             $totalDataFiles = @($dataFiles). Računati             $batchSize = [Matematika]::Max(500, $ProcessingBatchSize)             if ($LargeScaleMode) {                 $batchSize = [Matematika]::Max(2000, $ProcessingBatchSize)             }

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

                    foreach ($file in $batchFiles) {                         pokušajte {                             $deviceData = Get-Content $file. FullName – neobrađeno | ConvertFrom-Json                             $hostname = $deviceData.Hostname                             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                                 }                             }                         } uhvatiti { }                     }                                                          

                    Save-ProcessingCheckpoint -Stage "RebootStatusScan" -Processed ($end + 1) -Total $totalDataFiles -Metrics @{                         Event1808Count = $event 1808Count                         UEFIUpdatedAwaitingReboot = $uefiStatusUpdated                     }                 }             }             Write-Log "Ukupna implementacija: $($allDeployedDevices.Count)" "INFO"             Write-Log "Ažurirano (događaj 1808 potvrđen): $event 1808Count" "U redu"             if ($uefiStatusUpdated -gt 0) {                 Write-Log "Ažurirano (UEFICA2023Status=Ažurirano, čeka se ponovno pokretanje): $uefiStatusUpdated" "U redu"                 $rebootSuffix = ako ($uefiStatusUpdated -gt $DeviceLogSampleSize) { " ... (+$($uefiStatusUpdated - $DeviceLogSampleSize) više)" } još { "" }                 Write-Log " Uređaji kojima je potrebno ponovno pokretanje za događaj 1808 (uzorak): $($needsRebootSample -join ', ')$rebootSuffix" "INFO"                 Write-Log " Ti će uređaji prijaviti događaj 1808 nakon sljedećeg ponovnog pokretanja" "INFO"             }             Write-Log "Više nije na čekanju: $noLongerPendingCount (obuhvaća SecureBoot OFF, nedostaju uređaji)" "INFO"             Write-Log "Čeka se status: $stillPendingCount" "INFO"             if ($stillPendingCount -gt 0) {                 $pendingSuffix = ako ($stillPendingCount -gt $DeviceLogSampleSize) { " ... (+$($stillPendingCount - $DeviceLogSampleSize) više)" } još { "" }                 Write-Log "Uređaji na čekanju (uzorak): $($pendingSample -join ', ')$pendingSuffix" "UPOZORENJE"             }         } još {             Write-Log "Još nisu implementirali nijedan uređaj" "INFO"         }         Write-Log "================================================================" "INFO"         Write-Log "" "INFO"     }     # Čekaj prije sljedećeg iteracije     if (-not $DryRun) {         Write-Log "Spavanje na $PollIntervalMinutes minuta..." "INFO"         Start-Sleep sekundi ($PollIntervalMinutes * 60)     } još {         Write-Log "[DRYRUN] Bi $PollIntervalMinutes minuta" "INFO"         break # Izlaz nakon jedne iteracije na suhom     } }                               

# ============================================================================ # KONAČNI SAŽETAK # ============================================================================

Write-Host "" Write-Host ("=" * 80) -Boja prednjeg plana Zelena Write-Host " ROLLOUT ORCHESTRATOR SUMMARY" -ForegroundColor Green Write-Host ("=" * 80) - Boja prednjeg plana Zelena Write-Host ""

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

Write-Host "Status:              $($finalState.Status)" -ForegroundColor $(if ($finalState.Status -eq "Completed") { "Green" } else { "Yellow" }) Write-Host "Total Waves: $($finalState.CurrentWave)" Write-Host "Ciljani uređaji: $($finalState.TotalDevicesTargeted)" Write-Host "Blokirane grupe: $($finalBlocked.Count)" -ForegroundColor $(if ($finalBlocked.Count -gt 0) { "Red" } else { "Green" }) Write-Host "Uređaji praćeni: $($deviceHistory.Count)" -ForegroundColor Gray Write-Host ""

if ($finalBlocked.Count -gt 0) {     Write-Host "BLOKIRANE KANTE (zahtijeva ručno pregledavanje):" -ForegroundColor Red     foreach ($key in $finalBlocked.Keys) {         $info = $finalBlocked[$key]         Write-Host " - $key" - Crveni prednji plan         Write-Host " Razlog: $($info. Reason)" -ForegroundColor Gray     }     Write-Host ""     Write-Host "Blocked buckets file: $blockedBucketsPath" -ForegroundColor Yellow }

Write-Host "" Write-Host "State files:" - ForegroundColor Cyan Write-Host " Stanje rollout: $rolloutStatePath" Write-Host " Blokirane grupe: $blockedBucketsPath" Write-Host " Povijest uređaja: $deviceHistoryPath" Write-Host ""  

​​​​​​​

Potrebna vam je dodatna pomoć?

Želite dodatne mogućnosti?

Istražite pogodnosti pretplate, pregledajte tečajeve za obuku, saznajte kako zaštititi uređaj i još mnogo toga.