[CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)][ValidateSet('Check', 'Start', 'Stop')][string]$Action, [Parameter(Mandatory = $true)][string]$RuntimeDirectory, [switch]$Apply, [switch]$Force, [ValidateRange(0, 600)][int]$GraceSeconds = 30 ) . (Join-Path $PSScriptRoot 'TaskHost.Common.ps1') $runtime = [IO.Path]::GetFullPath($RuntimeDirectory) $manifest = Join-Path $runtime 'task-manifest.json' if (-not (Test-Path -LiteralPath $manifest -PathType Leaf)) { throw '未找到项目安装清单,拒绝管理其他任务。' } $saved = Get-Content -LiteralPath $manifest -Raw | ConvertFrom-Json if (-not $saved.PSObject.Properties['ServiceSid'] -or -not $saved.PSObject.Properties['LogonType']) { throw '安装清单缺少账号核验信息,请管理员核对后重新准备安装。' } $plan = Get-PayrollTaskPlan -ProjectRoot $saved.ProjectRoot -PythonPath $saved.PythonPath -RuntimeDirectory $runtime ` -ConnectionFile $saved.ConnectionFile -TaskPath $saved.TaskPath -ApiPort $saved.ApiPort $tasks = foreach ($expected in $plan.Tasks) { $actual = Get-ScheduledTask -TaskPath $plan.TaskPath -TaskName $expected.Name -ErrorAction Stop Assert-PayrollTaskPrincipal -Actual $actual.Principal -Expected $saved if (@($actual.Actions).Count -ne 1 -or $actual.Actions[0].Execute -ne $expected.Execute -or $actual.Actions[0].Arguments -ne $expected.Arguments -or $actual.Actions[0].WorkingDirectory -ne $expected.WorkingDirectory) { throw '任务动作与项目清单不一致,拒绝启动或停止。' } $info = Get-ScheduledTaskInfo -TaskPath $plan.TaskPath -TaskName $expected.Name [pscustomobject]@{ Name = $expected.Name; State = [string]$actual.State; LastTaskResult = $info.LastTaskResult; LastRunTime = $info.LastRunTime; NextRunTime = $info.NextRunTime; SettingsValid = ($actual.Settings.MultipleInstances -eq 'IgnoreNew' -and $actual.Settings.RestartCount -eq 3 -and $actual.Settings.RestartInterval -eq 'PT1M' -and $actual.Settings.ExecutionTimeLimit -eq 'PT0S') } } if ($Action -eq 'Check') { [pscustomobject]@{ TaskPath = $plan.TaskPath; StopRequested = (Test-Path -LiteralPath $plan.StopFile); Tasks = @($tasks) } | ConvertTo-Json -Depth 4 if (@($tasks | Where-Object { -not $_.SettingsValid }).Count) { exit 1 } exit 0 } if (-not $Apply -or -not $PSCmdlet.ShouldProcess($plan.TaskPath, $Action)) { [pscustomobject]@{ Preview = $true; Action = $Action; TaskPath = $plan.TaskPath; Names = @($tasks.Name) } | ConvertTo-Json return } if (@($tasks | Where-Object { -not $_.SettingsValid }).Count) { throw '任务恢复设置不符合交付约定,拒绝改变运行状态。' } if ($Action -eq 'Start') { Assert-PayrollHostAccess -Plan $plan -ServiceSid $saved.ServiceSid if (Test-Path -LiteralPath $plan.StopFile) { Remove-Item -LiteralPath $plan.StopFile } foreach ($task in $tasks) { Enable-ScheduledTask -TaskPath $plan.TaskPath -TaskName $task.Name | Out-Null; Start-ScheduledTask -TaskPath $plan.TaskPath -TaskName $task.Name } Write-Output '托管任务已启动;是否自动排队仍由T_PLUS_AUTOMATION_ENABLED决定。' } else { '运维请求停止领取任务' | Set-Content -LiteralPath $plan.StopFile -Encoding UTF8 foreach ($task in $tasks) { Disable-ScheduledTask -TaskPath $plan.TaskPath -TaskName $task.Name | Out-Null } $deadline = (Get-Date).AddSeconds($GraceSeconds) do { $running = @($tasks | Where-Object { (Get-ScheduledTask -TaskPath $plan.TaskPath -TaskName $_.Name).State -eq 'Running' }) if (-not $running.Count) { break } Start-Sleep -Seconds 1 } while ((Get-Date) -lt $deadline) if ($running.Count -and $Force) { foreach ($task in $running) { Stop-ScheduledTask -TaskPath $plan.TaskPath -TaskName $task.Name } Write-Output '已强制停止明确的项目任务;未结束的采集需在重启后人工核对中断记录。' } elseif ($running.Count) { Write-Output '已停止领取新任务,现有长采集尚在退出;未强制终止,可稍后Check。' exit 2 } else { Write-Output '项目托管任务已停止。' } }