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  
#&gt;
- `[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 {`  
    &lt;#  
    .SYNOPSIS  
        Validates that all required scripts are present.  
    .DESCRIPTION  
        Checks for required script dependencies and provides download instructions if missing.  
    #&gt;  
    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 -&gt; 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 ""  
  
​​​​​​​