fix(windows): defer missing tray task during trust reset
This commit is contained in:
1 parent
97a0139e3d
commit
2d89e80410
3 files changed
+137
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user