mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
3a898b5071
- Loader 1.3 downloads into a protected run folder of its own (%ProgramData%\AutopilotMonitor-Loader-<8 hex>), runs the installer only when it is signed by the pinned publisher and declares the installer role and version 2.7 or later, removes the folder after a successful run and never touches the agent folder. Unbound arguments now reach the installer by name; the array splat bound them by position. - Installer 2.7 refuses without elevation and decides before it writes: guards read HKLM through the 64-bit view (a 32-bit host never saw the deployment marker), guard 5 is covered by the agent check. An agent's folder stays as it is, a skip without agent removes the folder, an install starts from an emptied, protected folder and stages the ZIP in a run folder. It moves the MSI's working directory out, writes stderr only on failure and removes the copy older loaders left at the fixed path. - Help texts of both scripts state what customers need; .CHANGELOG, which made Get-Help ignore the help, gives way to the Platform Changelog. - OOBE go-script: the installer's shared helpers, its own elevation check (#Requires is ignored under irm | iex), a run folder. - Publish: version read line-anchored, the loader's own check on the exact publish bytes, installers before loaders, -Channel dev (dispatch input). - Bootstrap MSI 1.2.0; Test-ShouldBootstrapAgent reads the 64-bit view; Uninstall removes loader run folders and keeps the Deployed marker. - Tests: Pester unit suites, an integration harness running the real scripts (loader 1.2 and the MSI 1.0 runner frozen in TestData), BootstrapContractTests; the gate runs on windows-2022 and windows-2025, once more as SYSTEM, and against a real standard user. [skip actions]: published by a dispatch of publish-scripts.yml, channel dev first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
3.3 KiB
PowerShell
73 lines
3.3 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Runs the bootstrap integration tests once as SYSTEM, the account the bootstrap runs as on a device.
|
|
|
|
.DESCRIPTION
|
|
Registers a one-time scheduled task as SYSTEM that runs
|
|
scripts/Bootstrap/Bootstrap.Integration.Tests.ps1 under Windows PowerShell 5.1, waits for it,
|
|
prints its output and fails when the run did not happen as SYSTEM or a test failed. Run as
|
|
SYSTEM, the tests use the real folder permissions, including Administrators as owner.
|
|
|
|
CI only (bootstrap-script-gates.yml): it needs an elevated runner and Pester installed for all
|
|
users.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path,
|
|
[int]$TimeoutMinutes = 20
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$work = Join-Path ([System.IO.Path]::GetTempPath()) "apm-system-pass-$PID"
|
|
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
|
$runner = Join-Path $work 'run.ps1'
|
|
$output = Join-Path $work 'output.txt'
|
|
$result = Join-Path $work 'result.txt'
|
|
$tests = Join-Path $RepoRoot 'scripts\Bootstrap\Bootstrap.Integration.Tests.ps1'
|
|
|
|
$runnerText = @'
|
|
$ErrorActionPreference = 'Stop'
|
|
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
try {
|
|
Import-Module Pester -MinimumVersion 5.5.0
|
|
$configuration = New-PesterConfiguration
|
|
$configuration.Run.Path = '__TESTS__'
|
|
$configuration.Run.PassThru = $true
|
|
$configuration.Output.Verbosity = 'Detailed'
|
|
$run = Invoke-Pester -Configuration $configuration
|
|
Set-Content -LiteralPath '__RESULT__' -Value ('{0}|{1}|{2}' -f $identity, $run.PassedCount, $run.FailedCount)
|
|
} catch {
|
|
Set-Content -LiteralPath '__RESULT__' -Value ('{0}|error|{1}' -f $identity, $_.Exception.Message)
|
|
}
|
|
'@
|
|
Set-Content -LiteralPath $runner -Encoding ASCII -Value ($runnerText.Replace('__TESTS__', $tests).Replace('__RESULT__', $result))
|
|
|
|
$taskName = "apm-bootstrap-system-pass-$PID"
|
|
$arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"& '$runner' *> '$output'`""
|
|
$action = New-ScheduledTaskAction -Execute "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument $arguments
|
|
$principal = New-ScheduledTaskPrincipal -UserId 'S-1-5-18' -LogonType ServiceAccount -RunLevel Highest
|
|
Register-ScheduledTask -TaskName $taskName -Action $action -Principal $principal -Force | Out-Null
|
|
try {
|
|
Start-ScheduledTask -TaskName $taskName
|
|
$deadline = (Get-Date).AddMinutes($TimeoutMinutes)
|
|
do {
|
|
Start-Sleep -Seconds 5
|
|
$state = (Get-ScheduledTask -TaskName $taskName).State
|
|
} while ($state -eq 'Running' -and (Get-Date) -lt $deadline)
|
|
if ($state -eq 'Running') {
|
|
Stop-ScheduledTask -TaskName $taskName
|
|
throw "The SYSTEM pass did not finish within $TimeoutMinutes minutes."
|
|
}
|
|
} finally {
|
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $output) { Get-Content -LiteralPath $output }
|
|
if (-not (Test-Path -LiteralPath $result)) { throw 'The SYSTEM pass wrote no result.' }
|
|
$identity, $passed, $failed = (Get-Content -LiteralPath $result -Raw).Trim().Split('|', 3)
|
|
if ($identity -ne 'S-1-5-18') { throw "The SYSTEM pass ran as $identity, not as SYSTEM." }
|
|
if ($passed -eq 'error') { throw "The SYSTEM pass could not run the tests: $failed" }
|
|
if ([int]$failed -gt 0 -or [int]$passed -eq 0) { throw "SYSTEM pass: $passed passed, $failed failed." }
|
|
Write-Host "SYSTEM pass: $passed passed, 0 failed."
|