Powershell 最终阻止在 Windows 任务计划程序中执行

Pet*_*ter 1 powershell scheduled-tasks task-scheduler

我正在运行 Windows 任务计划程序任务:

Powershell 
-command C:\file.ps1  2>&1 > c:\test.log
Run Code Online (Sandbox Code Playgroud)

file.ps1包含

Try{
    echo "a"
    sleep 5
    echo "b"
}
Finally{
    echo "c"
}
Run Code Online (Sandbox Code Playgroud)

当我手动启动任务并等待其完成时,test.log 包含“abc”。但是当我通过手动退出(“结束”按钮)来中断它时,它只包含“a”。

我认为Finally甚至是在退出 powershell 脚本时运行?我的预期结果是“ac”

Ben*_*n N 5

正如 Lieven Keersmaekers 在评论中提到的,当您使用 End 中断任务时,finally 块没有机会运行。这是因为 End 只是终止了该进程。PowerShell 很乐意在执行其他退出操作之前运行finally 块,但是 PowerShell 在进程终止信号之后根本无法执行任何操作 - 它已经死了。任何进程都没有办法处理这个问题。用陈雷蒙的话来说:

TerminateProcess 是低级进程终止函数。它绕过 DLL_PROCESS_DETACH 和进程中的任何其他内容。一旦使用 TerminateProcess 终止,该进程中将不再运行用户模式代码。它消失了。不过去就走。不要收取 200 美元。

不过,有一个解决方法。由于任务计划程序的 End 命令不会终止任务进程的子进程,因此您的主 PowerShell 脚本可以启动看门狗进程来执行任何最终清理。该脚本可能看起来像这样(我们称之为watchdog.ps1):

$watched = Get-Process -Id $args[0]
$watched.WaitForExit()
'Cleanup' >> c:\test.log
Run Code Online (Sandbox Code Playgroud)

它需要一个进程 ID,等待该进程退出,然后才进行一些清理,相当于您的 finally 块。

那么你的主脚本可能如下所示:

$myPid = [System.Diagnostics.Process]::GetCurrentProcess().Id
$watchdog = Start-Process 'powershell' "-c .\watchdog.ps1 $myPid" -PassThru -WindowStyle Hidden
'Starting'
sleep 10
'Finished'
Run Code Online (Sandbox Code Playgroud)

首先,它启动看门狗脚本,并提供自己的进程 ID。然后它继续正常的业务,完成后正常退出。

如果您希望仅在主脚本被中断时才进行看门狗的清理,请将此行添加到主脚本的末尾:

$watchdog.Kill()
Run Code Online (Sandbox Code Playgroud)

这将阻止看门狗继续越过第二行。

您的重定向仅适用于主进程,因此看门狗必须处理其自己的输出方向。根据计划任务的配置,您可能需要将 watchdog 脚本的完整路径放入主脚本的 watchdog-launching 命令中。