Sample Deploy-OrchestratorTask.ps1 script

Note

IMPORTANT This article containing this sample script has been retired. Starting with the Windows updates released on and after May 12, 2026, the sample script is located in the %systemroot%\SecureBoot\ExampleRolloutScripts folder on your device.

Copy and paste this sample script and modify as needed for your environment:

Note

  • <#
    .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 AllowListPath
        Path to a file containing hostnames to ALLOW for rollout (targeted/pilot rollout).
        Supports .txt (one hostname per line) or .csv (with Hostname/ComputerName/Name column).
        When specified, ONLY these devices will be included in rollout.
  • .PARAMETER AllowADGroup
        Name of an AD security group containing computer accounts to ALLOW.
        Example: "SecureBoot-Pilot-Computers"
  • .PARAMETER ExclusionListPath
        Path to a file containing hostnames to EXCLUDE from rollout (VIP/executive devices).
        Supports .txt (one hostname per line) or .csv (with Hostname/ComputerName/Name column).
  • .PARAMETER ExcludeADGroup
        Name of an AD security group containing computer accounts to exclude.
        Example: "VIP-Computers"
  • .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]$AllowListPath,
        
        [Parameter(Mandatory = $false)]
        [string]$AllowADGroup,
        
        [Parameter(Mandatory = $false)]
        [string]$ExclusionListPath,
        
        [Parameter(Mandatory = $false)]
        [string]$ExcludeADGroup,
        
        [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`""
    }
  • if ($AllowListPath) {
        $arguments += "-AllowListPath `"$AllowListPath`""
    }
  • if ($AllowADGroup) {
        $arguments += "-AllowADGroup `"$AllowADGroup`""
    }
  • if ($ExclusionListPath) {
        $arguments += "-ExclusionListPath `"$ExclusionListPath`""
    }
  • if ($ExcludeADGroup) {
        $arguments += "-ExcludeADGroup `"$ExcludeADGroup`""
    }
  • $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 {
        # Normalize local account format: COMPUTERNAME\User -> User
        # Register-ScheduledTask does not accept COMPUTERNAME\User or .\User for local accounts
        $taskUser = $ServiceAccount
        if ($ServiceAccount -match "^$([regex]::Escape($env:COMPUTERNAME))\\(.+)$") {
            $taskUser = $Matches[1]
            Write-Host "  Note: Local account detected, using '$taskUser' for task registration" -ForegroundColor Gray
        }
  •     # Prompt for password
        Write-Host "Enter password for $ServiceAccount" -ForegroundColor Yellow
        $cred = Get-Credential -UserName $taskUser -Message "Service account credentials for scheduled task"
        if (-not $cred) {
            Write-Host "Credential entry cancelled. Aborting." -ForegroundColor Red
            exit 1
        }
        $principal = New-ScheduledTaskPrincipal -UserId $taskUser -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 {
            # Use New-ScheduledTask to embed the principal (with RunLevel Highest) into a task object,
            # then register with -InputObject + -User/-Password (separate parameter set from -Principal)
            $taskDefinition = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
            $taskDefinition.Description = "Secure Boot Certificate Rollout - Automated GPO deployment"
            # Note: Register-ScheduledTask requires plain text password - no SecureString overload exists
            $netCred = $cred.GetNetworkCredential()
            Register-ScheduledTask -TaskName $TaskName -InputObject $taskDefinition -User $taskUser -Password $netCred.Password -ErrorAction Stop
            $netCred = $null
        }
        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 ""

​​​​​​​