fix(windows): defer missing tray task during trust reset

This commit is contained in:
lussierj committed 2026-10-14 13:50:36 +00:00
1 parent 97a0139e3d
commit 2d89e80410
3 files changed
+137

No files matched your search

+14
View File
@@ -197,6 +197,20 @@ and other runtime data. Existing Scheduled Task definitions are authoritative
and are not rewritten. Missing tasks are created for a fresh or incomplete
installation without hard-coded user names.
The per-user `MinerOpsAgentTray` task is optional UI and is not credential or
core Agent authority. During an explicit filesystem trust reset, runtime
publication may run noninteractively as `NT AUTHORITY\SYSTEM`, where no safe
interactive user principal can be inferred. The installer still publishes
`MinerOpsAgentTray.ps1`, but deliberately defers creation of a missing tray task
until a later interactive maintenance install. It never creates the tray task
as SYSTEM or invents a username. An existing tray task remains authoritative
and is preserved unchanged rather than silently repaired under SYSTEM; inspect
and remediate a suspected malformed definition separately. The core
`MinerOpsAgent` task remains mandatory and strictly verified by credential
recovery. `MinerOpsApi3Enrichment` and `MinerOpsPoolPoller` are non-interactive
background tasks with explicit SYSTEM principals, so missing instances continue
to be created during trust-reset publication.
If MinerOpsAgent was running before an upgrade, the installer stops only that
Scheduled Task, waits for it to stop, replaces the managed files, and starts it
again. A previously stopped Agent remains stopped. Before replacing runtime
+27
View File
@@ -953,6 +953,19 @@ function Get-MinerOpsScheduledTask {
return $tasks[0]
}
function Test-MinerOpsInstallerShouldCreateMissingTask {
param(
[Parameter(Mandatory)]$Definition,
[Parameter(Mandatory)][bool]$TrustResetMode
)
if (-not $TrustResetMode) { return $true }
if ($null -eq $Definition.CreateWhenMissingDuringTrustReset) {
throw "Scheduled Task trust-reset creation policy is missing."
}
return [bool]$Definition.CreateWhenMissingDuringTrustReset
}
function Stop-MinerOpsScheduledTaskForUpgrade {
param(
[Parameter(Mandatory)][string]$Name,
@@ -1101,22 +1114,26 @@ try {
Name = "MinerOpsAgent"; Script = "MinerOpsAgentService.ps1"
Trigger = $startup; Principal = $system; Settings = $agentSettings
ExtraArguments = ""
CreateWhenMissingDuringTrustReset = $true
},
@{
Name = "MinerOpsAgentTray"; Script = "MinerOpsAgentTray.ps1"
Trigger = (New-ScheduledTaskTrigger -AtLogOn); Principal = $null
Settings = $commonSettings; ExtraArguments = "-WindowStyle Hidden"
CreateWhenMissingDuringTrustReset = $false
},
@{
Name = "MinerOpsApi3Enrichment"
Script = "MinerOpsApi3EnrichmentPoller.ps1"
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
ExtraArguments = "-Limit 0 -TimeoutMs 8000"
CreateWhenMissingDuringTrustReset = $true
},
@{
Name = "MinerOpsPoolPoller"; Script = "MinerOpsPoolPollerTask.ps1"
Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings
ExtraArguments = ""
CreateWhenMissingDuringTrustReset = $true
}
)
foreach ($definition in $definitions) {
@@ -1129,6 +1146,16 @@ try {
continue
}
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
-Definition $definition -TrustResetMode $trustResetMode)) {
# Trust-reset publication can run noninteractively as SYSTEM. A
# missing per-user tray has no safe interactive principal in that
# context. Its runtime file is already published; task creation is
# intentionally deferred to a later interactive maintenance run.
Write-Host "Deferring missing per-user Scheduled Task $($definition.Name) until an interactive install."
continue
}
$arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f `
(Join-Path $Target $definition.Script)
if ($definition.ExtraArguments) {
@@ -37,6 +37,10 @@ Assert-SourceContains '\$existingTask\s*=\s*Get-MinerOpsScheduledTask' `
"Existing task preservation guard is missing."
Assert-SourceContains 'Preserving existing Scheduled Task' `
"Existing task preservation is not explicit."
Assert-SourceContains 'Test-MinerOpsInstallerShouldCreateMissingTask' `
"Missing-task trust-reset policy helper is absent."
Assert-SourceContains 'Deferring missing per-user Scheduled Task' `
"Missing per-user task deferral is not explicit."
Assert-SourceContains 'ExtraArguments\s*=\s*"-Limit 0 -TimeoutMs 8000"' `
"Fresh enrichment task arguments are missing."
Assert-SourceContains 'Stop-MinerOpsScheduledTaskForUpgrade' `
@@ -52,6 +56,98 @@ Assert-SourceContains 'could not restart restored MinerOpsAgent' `
Assert-SourceContains 'Assert-MinerOpsInstallerTrustedAncestors' `
"Installer does not validate ancestor namespace protection."
$taskDefinitionsStart = $source.IndexOf('$definitions = @(')
$taskLoopStart = $source.IndexOf(
'foreach ($definition in $definitions)', $taskDefinitionsStart
)
if ($taskDefinitionsStart -lt 0 -or $taskLoopStart -le $taskDefinitionsStart) {
throw "Scheduled Task definitions or reconciliation loop were not found."
}
$taskDefinitions = $source.Substring(
$taskDefinitionsStart, $taskLoopStart - $taskDefinitionsStart
)
foreach ($policy in @(
@{ Name = "MinerOpsAgent"; Expected = '\$true' },
@{ Name = "MinerOpsAgentTray"; Expected = '\$false' },
@{ Name = "MinerOpsApi3Enrichment"; Expected = '\$true' },
@{ Name = "MinerOpsPoolPoller"; Expected = '\$true' }
)) {
$definition = [regex]::Match(
$taskDefinitions,
('(?s)@\{{.*?Name\s*=\s*"{0}".*?\}}' -f
[regex]::Escape($policy.Name))
).Value
if ([string]::IsNullOrWhiteSpace($definition) -or
$definition -notmatch (
'CreateWhenMissingDuringTrustReset\s*=\s*{0}' -f
$policy.Expected
)) {
throw "Scheduled Task $($policy.Name) has the wrong trust-reset creation policy."
}
}
$taskLoopEnd = $source.IndexOf(
'if ($mainTaskWasRunning', $taskLoopStart
)
if ($taskLoopEnd -le $taskLoopStart) {
throw "Scheduled Task reconciliation loop end was not found."
}
$taskLoop = $source.Substring($taskLoopStart, $taskLoopEnd - $taskLoopStart)
$preserveIndex = $taskLoop.IndexOf('$null -ne $existingTask')
$deferIndex = $taskLoop.IndexOf(
'Test-MinerOpsInstallerShouldCreateMissingTask'
)
$actionIndex = $taskLoop.IndexOf('New-ScheduledTaskAction')
if (
$preserveIndex -lt 0 -or
$deferIndex -le $preserveIndex -or
$actionIndex -le $deferIndex
) {
throw "Existing-task preservation and missing-task deferral are ordered unsafely."
}
if ($taskLoop -match '80070534|No mapping between account names') {
throw "Missing per-user task handling must be policy-based, not HRESULT-based."
}
$taskPolicyFunction = [regex]::Match(
$source,
'(?s)function Test-MinerOpsInstallerShouldCreateMissingTask \{.*?\n\}'
).Value
if ([string]::IsNullOrWhiteSpace($taskPolicyFunction)) {
throw "Missing-task trust-reset policy function source was not found."
}
Invoke-Expression $taskPolicyFunction
$trayPolicy = [pscustomobject]@{
CreateWhenMissingDuringTrustReset = $false
}
$systemTaskPolicy = [pscustomobject]@{
CreateWhenMissingDuringTrustReset = $true
}
if (Test-MinerOpsInstallerShouldCreateMissingTask `
-Definition $trayPolicy -TrustResetMode $true) {
throw "Trust reset would create a missing per-user tray task."
}
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
-Definition $trayPolicy -TrustResetMode $false)) {
throw "Normal interactive installation would not repair a missing tray task."
}
if (-not (Test-MinerOpsInstallerShouldCreateMissingTask `
-Definition $systemTaskPolicy -TrustResetMode $true)) {
throw "Trust reset would skip a missing explicit-SYSTEM task."
}
$missingPolicyRejected = $false
try {
[void](Test-MinerOpsInstallerShouldCreateMissingTask `
-Definition ([pscustomobject]@{}) -TrustResetMode $true)
}
catch {
$missingPolicyRejected = $_.Exception.Message -match
'trust-reset creation policy is missing'
}
if (-not $missingPolicyRejected) {
throw "Trust reset accepted a task definition without explicit policy."
}
foreach ($functionName in @(
"Assert-MinerOpsInstallerNoReparsePath",
"Assert-MinerOpsInstallerTrustedAncestors"