Copy and paste this sample script and modify as needed for your environment:
<# .SYNOPSIS Â Â Deploys the Secure Boot Rollout Orchestrator as a Windows Scheduled Task.
.DESCRIPTION   Creates a scheduled task that runs the orchestrator continuously in the background.   The task runs with bypass execution policy so no security prompts appear.      The orchestrator will:   - Poll for device updates on the specified interval   - Automatically generate waves and deploy GPOs   - Continue until all eligible devices are updated      Monitor progress using: Get-SecureBootRolloutStatus.ps1
.PARAMETER AggregationInputPath   UNC path to JSON device data (from detection GPO)
.PARAMETER ReportBasePath   Local path for reports and state files
.PARAMETER TargetOU Â Â OU to link GPOs (optional - defaults to domain root)
.PARAMETER PollIntervalMinutes   Minutes between status checks. Default: 30
.PARAMETER UseWinCS Â Â Use WinCS (Windows Configuration System) instead of AvailableUpdatesPolicy GPO. Â Â When enabled, deploys WinCsFlags.exe scheduled task to endpoints instead of registry GPO.
.PARAMETER WinCSKey   The WinCS key for Secure Boot configuration. Default: F33E0C8E002
.PARAMETER ServiceAccount   Account to run the task. Default: SYSTEM   For domain operations, use a domain admin service account.
.PARAMETER ScriptPath   Path to the orchestrator script. Default: Same folder as this script.
.PARAMETER Uninstall   Remove the scheduled task
.EXAMPLE Â Â .\Deploy-OrchestratorTask.ps1 -AggregationInputPath "\\server\SecureBootData$" -ReportBasePath "C:\SecureBootReports" -ServiceAccount "DOMAIN\svc_secureboot"
.EXAMPLE Â Â .\Deploy-OrchestratorTask.ps1 -AggregationInputPath "\\server\SecureBootData$" -ReportBasePath "C:\SecureBootReports"
.EXAMPLE   # Deploy using WinCS method instead of AvailableUpdatesPolicy   .\Deploy-OrchestratorTask.ps1 -AggregationInputPath "\\server\SecureBootData$" -ReportBasePath "C:\SecureBootReports" -UseWinCS
.EXAMPLE Â Â .\Deploy-OrchestratorTask.ps1 -Uninstall #>
[CmdletBinding()] param( Â Â [Parameter(Mandatory = $false)] Â Â [string]$AggregationInputPath, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [string]$ReportBasePath, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [string]$TargetOU, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [int]$PollIntervalMinutes = 30, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [switch]$UseWinCS, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [string]$WinCSKey = "F33E0C8E002", Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [string]$ServiceAccount = "SYSTEM", Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [string]$ScriptPath, Â Â Â Â Â [Parameter(Mandatory = $false)] Â Â [switch]$Uninstall )
$ErrorActionPreference = "Stop" $TaskName = "SecureBoot-Rollout-Orchestrator" $DownloadUrl = "https://aka.ms/getsecureboot" $DownloadSubPage = "Deployment and Monitoring Samples"
# ============================================================================ # DEPENDENCY VALIDATION # ============================================================================
function Test-ScriptDependencies {   <#   .SYNOPSIS     Validates that all required scripts are present.   .DESCRIPTION     Checks for required script dependencies and provides download instructions if missing.   #>   param(     [Parameter(Mandatory = $true)]     [string]$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) -ForegroundColor Red     Write-Host "  MISSING DEPENDENCIES" -ForegroundColor Red     Write-Host ("=" * 70) -ForegroundColor Red     Write-Host ""     Write-Host "The following required scripts were not found:" -ForegroundColor Yellow     foreach ($script in $missingScripts) {       Write-Host "  - $script" -ForegroundColor White     }     Write-Host ""     Write-Host "Please download the latest scripts from:" -ForegroundColor Cyan     Write-Host "  URL: $DownloadUrl" -ForegroundColor White     Write-Host "  Navigate to: '$DownloadSubPage'" -ForegroundColor White     Write-Host ""     Write-Host "Extract all scripts to the same directory and run again." -ForegroundColor Yellow     Write-Host ""     return $false   }      return $true }
# Required scripts for orchestrator deployment $requiredScripts = @( Â Â "Start-SecureBootRolloutOrchestrator.ps1", Â Â "Aggregate-SecureBootData.ps1", Â Â "Deploy-GPO-SecureBootCollection.ps1", Â Â "Detect-SecureBootCertUpdateStatus.ps1", Â Â "Get-SecureBootRolloutStatus.ps1", Â Â "Enable-SecureBootUpdateTask.ps1" )
if (-not (Test-ScriptDependencies -ScriptDirectory $PSScriptRoot -RequiredScripts $requiredScripts)) { Â Â exit 1 }
# ============================================================================ # UNINSTALL # ============================================================================
if ($Uninstall) {   Write-Host ""   Write-Host "Removing scheduled task: $TaskName" -ForegroundColor Yellow      $existingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue   if ($existingTask) {     Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue     Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false     Write-Host "Task removed successfully." -ForegroundColor Green   } else {     Write-Host "Task not found." -ForegroundColor Gray   }   exit 0 }
# ============================================================================ # VALIDATION # ============================================================================
if (-not $AggregationInputPath -or -not $ReportBasePath) {   Write-Host "ERROR: -AggregationInputPath and -ReportBasePath are required." -ForegroundColor Red   Write-Host ""   Write-Host "Example:" -ForegroundColor Yellow   Write-Host '  .\Deploy-OrchestratorTask.ps1 -AggregationInputPath "\\server\SecureBootData$" -ReportBasePath "C:\SecureBootReports"'   exit 1 }
# Find orchestrator script if (-not $ScriptPath) { Â Â $ScriptPath = Join-Path $PSScriptRoot "Start-SecureBootRolloutOrchestrator.ps1" }
if (-not (Test-Path $ScriptPath)) {   Write-Host "ERROR: Orchestrator script not found: $ScriptPath" -ForegroundColor Red   exit 1 }
# Find aggregation script (needed by orchestrator) $aggregateScript = Join-Path $PSScriptRoot "Aggregate-SecureBootData.ps1" if (-not (Test-Path $aggregateScript)) {   Write-Host "WARNING: Aggregate-SecureBootData.ps1 not found in script directory" -ForegroundColor Yellow   Write-Host "     Orchestrator may fail if it cannot find this script." -ForegroundColor Yellow }
Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host " Â Secure Boot Rollout Orchestrator - Task Deployment" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host ""
# For display, show relative paths (script names only) $displayScriptPath = Split-Path $ScriptPath -Leaf
Write-Host "Configuration:" -ForegroundColor Yellow Write-Host " Â Task Name: Â Â Â Â $TaskName" Write-Host " Â Orchestrator: Â Â Â $displayScriptPath" Write-Host " Â Input Path: Â Â Â Â $AggregationInputPath" Write-Host " Â Report Path: Â Â Â $ReportBasePath" Write-Host " Â Target OU: Â Â Â Â $(if ($TargetOU) { $TargetOU } else { '(domain root)' })" Write-Host " Â Poll Interval: Â Â $PollIntervalMinutes minutes" Write-Host " Â Service Account: Â $ServiceAccount" Write-Host " Â Deployment Method: $(if ($UseWinCS) { "WinCS (WinCsFlags.exe)" } else { "AvailableUpdatesPolicy (GPO)" })" if ($UseWinCS) { Â Â Write-Host " Â WinCS Key: Â Â Â Â $WinCSKey" } Write-Host ""
# ============================================================================ # GPO DETECTION - AUTO-DEPLOY IF MISSING # ============================================================================
$CollectionGPOName = "SecureBoot-EventCollection" $deployGpoScript = Join-Path $PSScriptRoot "Deploy-GPO-SecureBootCollection.ps1"
# Check if GroupPolicy module is available if (Get-Module -ListAvailable -Name GroupPolicy) {   Import-Module GroupPolicy -ErrorAction SilentlyContinue      Write-Host "Checking for Detection GPO..." -ForegroundColor Yellow      try {     # Get the domain from the AggregationInputPath (e.g., \\domain\share)     $domainFromPath = if ($AggregationInputPath -match '^\\\\([^\\]+)\\') {       $matches[1]     } else {       $env:USERDNSDOMAIN     }          # Check if GPO exists     $existingGpo = Get-GPO -Name $CollectionGPOName -ErrorAction SilentlyContinue          if ($existingGpo) {       Write-Host "  Detection GPO found: $CollectionGPOName" -ForegroundColor Green     } else {       Write-Host ""       Write-Host ("=" * 70) -ForegroundColor Yellow       Write-Host "  DETECTION GPO NOT FOUND" -ForegroundColor Yellow       Write-Host ("=" * 70) -ForegroundColor Yellow       Write-Host ""       Write-Host "The detection GPO '$CollectionGPOName' was not found." -ForegroundColor Yellow       Write-Host "This GPO is required to collect device status data." -ForegroundColor Yellow       Write-Host ""              # Ask user if they want to deploy the GPO now       Write-Host "Would you like to deploy the Detection GPO now? (Y/N)" -ForegroundColor Cyan       $response = Read-Host              if ($response -match '^[Yy]') {         Write-Host ""         Write-Host "Launching GPO Deployment..." -ForegroundColor Cyan         Write-Host ""                  # Build parameters for GPO deployment         $gpoParams = @{           DomainName = $domainFromPath           CollectionSharePath = $AggregationInputPath           ScriptSourcePath = Join-Path $PSScriptRoot "Detect-SecureBootCertUpdateStatus.ps1"         }                  if ($TargetOU) {           $gpoParams.OUPath = $TargetOU         } else {           # Use AutoDetectOU to let user select           $gpoParams.AutoDetectOU = $true         }                  # Run the GPO deployment script         & $deployGpoScript @gpoParams                  if ($LASTEXITCODE -ne 0) {           Write-Host "GPO deployment may have encountered issues. Review the output above." -ForegroundColor Yellow           Write-Host "You can continue with orchestrator deployment or press Ctrl+C to abort." -ForegroundColor Yellow           Write-Host ""           Read-Host "Press Enter to continue"         } else {           Write-Host ""           Write-Host "Detection GPO deployed successfully!" -ForegroundColor Green           Write-Host ""         }       } else {         Write-Host ""         Write-Host "Skipping GPO deployment. The orchestrator will not receive device data" -ForegroundColor Yellow         Write-Host "until the Detection GPO is deployed manually." -ForegroundColor Yellow         Write-Host ""         Write-Host "To deploy the Detection GPO later, run:" -ForegroundColor Cyan         Write-Host "  .\Deploy-GPO-SecureBootCollection.ps1 -DomainName `"$domainFromPath`" -AutoDetectOU" -ForegroundColor White         Write-Host ""       }     }   } catch {     Write-Host "  Unable to check for GPO: $($_.Exception.Message)" -ForegroundColor Yellow     Write-Host "  Continuing with orchestrator deployment..." -ForegroundColor Gray   } } else {   Write-Host "  GroupPolicy module not available - skipping GPO check" -ForegroundColor Gray   Write-Host "  Ensure Detection GPO is deployed separately." -ForegroundColor Gray }
Write-Host ""
# ============================================================================ # BUILD ARGUMENTS # ============================================================================
$arguments = @( Â Â "-NoProfile" Â Â "-ExecutionPolicy Bypass" Â Â "-File `"$ScriptPath`"" Â Â "-AggregationInputPath `"$AggregationInputPath`"" Â Â "-ReportBasePath `"$ReportBasePath`"" Â Â "-PollIntervalMinutes $PollIntervalMinutes" )
if ($TargetOU) { Â Â $arguments += "-TargetOU `"$TargetOU`"" }
if ($UseWinCS) { Â Â $arguments += "-UseWinCS" Â Â $arguments += "-WinCSKey `"$WinCSKey`"" }
$argumentString = $arguments -join " "
# Don't display raw arguments with full paths - it's confusing for published scripts # The task will use the full path internally
# ============================================================================ # CREATE SCHEDULED TASK # ============================================================================
# Check for existing task $existingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue if ($existingTask) {   Write-Host "Task already exists. Updating..." -ForegroundColor Yellow   Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue   Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false }
# Create task action $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $argumentString -WorkingDirectory $PSScriptRoot
# Create trigger - run once, immediately (orchestrator loops internally) $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)
# Create principal if ($ServiceAccount -eq "SYSTEM") {   $principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest } else {   # Prompt for password for domain account   Write-Host "Enter password for $ServiceAccount" -ForegroundColor Yellow   $cred = Get-Credential -UserName $ServiceAccount -Message "Service account credentials for scheduled task"   $principal = New-ScheduledTaskPrincipal -UserId $ServiceAccount -LogonType Password -RunLevel Highest }
# Task settings $settings = New-ScheduledTaskSettingsSet ` Â Â -AllowStartIfOnBatteries ` Â Â -DontStopIfGoingOnBatteries ` Â Â -StartWhenAvailable ` Â Â -RunOnlyIfNetworkAvailable ` Â Â -RestartCount 3 ` Â Â -RestartInterval (New-TimeSpan -Minutes 5) ` Â Â -ExecutionTimeLimit (New-TimeSpan -Days 30) Â # Allow long-running
# Register task try {   if ($ServiceAccount -eq "SYSTEM") {     Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Secure Boot Certificate Rollout - Automated GPO deployment"   } else {     Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Secure Boot Certificate Rollout - Automated GPO deployment" -User $ServiceAccount -Password $cred.GetNetworkCredential().Password   }   Write-Host "Scheduled task created successfully!" -ForegroundColor Green } catch {   Write-Host "Failed to create scheduled task: $($_.Exception.Message)" -ForegroundColor Red   exit 1 }
# ============================================================================ # CREATE STATUS SHORTCUT # ============================================================================
$statusScript = Join-Path $PSScriptRoot "Get-SecureBootRolloutStatus.ps1" if (Test-Path $statusScript) {   Write-Host ""   Write-Host "To check rollout status, run:" -ForegroundColor Yellow   Write-Host "  .\Get-SecureBootRolloutStatus.ps1 -ReportBasePath `"$ReportBasePath`"" -ForegroundColor Cyan }
# ============================================================================ # OUTPUT # ============================================================================
Write-Host "" Write-Host ("=" * 70) -ForegroundColor Green Write-Host " Â DEPLOYMENT COMPLETE" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green Write-Host "" Write-Host "The orchestrator will start in approximately 1 minute." -ForegroundColor White Write-Host "" Write-Host "MONITORING:" -ForegroundColor Yellow Write-Host " Â View task status: Â Â Get-ScheduledTask -TaskName '$TaskName' | Select State" Write-Host " Â View task log: Â Â Â Get-Content '$ReportBasePath\RolloutState\Orchestrator_$(Get-Date -Format 'yyyyMMdd').log' -Tail 50" Write-Host " Â View rollout state: Â Get-Content '$ReportBasePath\RolloutState\RolloutState.json' | ConvertFrom-Json" Write-Host " Â View dashboard: Â Â Â Start '$ReportBasePath\Aggregation_*\SecureBoot_Dashboard*.html'" Write-Host " Â Quick status: Â Â Â Â .\Get-SecureBootRolloutStatus.ps1 -ReportBasePath '$ReportBasePath'" Write-Host "" Write-Host "MANAGEMENT:" -ForegroundColor Yellow Write-Host " Â Start manually: Â Â Â Start-ScheduledTask -TaskName '$TaskName'" Write-Host " Â Stop: Â Â Â Â Â Â Â Â Stop-ScheduledTask -TaskName '$TaskName'" Write-Host " Â Remove: Â Â Â Â Â Â Â .\Deploy-OrchestratorTask.ps1 -Uninstall" Write-Host "" Â