如何防止 Powershell 在通过 SSH 生成时杀死自己的后台进程?
How to prevent Powershell from killing its own background processes when spawned via SSH?
我们在 C:\test\test.ps1
中有一个 Powershell 脚本。该脚本具有以下内容(未删除任何内容):
Start-Process -NoNewWindow powershell { sleep 30; }
sleep 10
当我们打开命令行 window (cmd.exe
) 并通过以下命令执行该脚本时
c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1
我们可以在 Windows 任务管理器(以及 Sysinternals Process Explorer)中看到脚本的行为符合预期:
- 执行上述命令后,进程列表中立即出现两个新条目,一个是执行“主”脚本的Powershell(
test.ps1
),一个是执行“后台脚本”的Powershell( { sleep 30; }
).
- 10 秒后,第一个条目(与
test.ps1
相关)从进程列表中消失,而第二个条目保留在进程列表中。
- 再过 20 秒后(即总共 30 秒),第二个条目(与
{ sleep 30; }
相关)也会从进程列表中消失。
这是预期的行为,因为 Start-Process
无论如何都会在后台启动新进程,除非给出 -Wait
。到目前为止,还不错。
但是现在我们遇到了一个毛茸茸的问题,它已经花费了我们两天的调试时间,直到我们最终找出其中一个脚本行为不当的原因:
其实test.ps1
是通过SSH执行的
也就是说,我们已经在 Windows Server 2019 上安装了 Microsoft 的 OpenSSH 服务器实现,并已正确配置。在其他机器(Linux和Windows)上使用SSH客户端,我们可以登录到Windows服务器,我们可以通过执行以下命令在服务器上通过SSH执行test.ps1
客户端命令:
ssh -i <appropriate_key> administrator@ip.of.windows.server c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1
观察Windows服务器上的任务管理器,我们可以再次看到进程列表中的两个新条目,如上所述,只要在客户端上执行此命令。
但是,两个 条目在 10 秒后从服务器上的进程列表中消失。
这意味着主进程一结束,后台进程 ({ sleep 30; }
) 就会被杀死。这与记录的内容和应该发生的事情相反,我们确实需要防止它。
所以问题是:
我们如何更改 test.ps1
以便在脚本结束时后台进程 ({ sleep 30; }
) 在任何情况下都不会被终止,即使脚本是通过 SSH 启动的?
一些旁注:
这不是学术上的例子。实际上,我们在该服务器上有一个相当复杂的脚本系统,它由大约十几个 Powershell 脚本组成,其中一个是“主”脚本,它在后台执行其他脚本,如上所示;后台脚本本身可能会启动更多的后台脚本。
重要的是不要 首先通过 SSH 在后台启动主脚本。客户端必须处理主脚本的输出和退出代码,并且必须等到主脚本完成一些工作并且 returns.
这意味着我们必须使用 Powershell 的功能来启动主脚本中的后台进程(或启动能够启动后台进程的第三方程序,这些进程不会在主脚本时被杀死结束)。
Note: The original advice to use jobs here was incorrect, as when the job is stopped along with the parent session, any child processes still get killed. As it was incorrect advice for this scenario, I've removed that content from this answer.
不幸的是,当 PowerShell 会话结束时,从该会话创建的子进程也会结束。但是,当使用 PSRemoting 时,我们可以使用 -InDisconnectedSession
参数(别名为 -Disconnected
)告诉 Invoke-Command
到 运行 断开连接的会话中的命令:
$icArgs = @{
ComputerName = 'RemoteComputerName'
Credential = ( Get-Credential )
Disconnected = $true
}
Invoke-Command @icArcs { ping -t 127.0.0.1 }
根据我对无限 ping 的测试,如果父 PowerShell 会话关闭,远程会话应该继续执行。在我的例子中,ping
命令持续 运行ning 直到我自己在远程服务器上停止了进程。
这里的缺点是您似乎没有使用 PowerShell Remoting,而是通过 SSH 调用 shell 命令,而这些命令恰好是 PowerShell 脚本。如果您需要用于 PowerShell 远程处理的 SSH 传输,您还必须使用 PowerShell Core。
我们在 C:\test\test.ps1
中有一个 Powershell 脚本。该脚本具有以下内容(未删除任何内容):
Start-Process -NoNewWindow powershell { sleep 30; }
sleep 10
当我们打开命令行 window (cmd.exe
) 并通过以下命令执行该脚本时
c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1
我们可以在 Windows 任务管理器(以及 Sysinternals Process Explorer)中看到脚本的行为符合预期:
- 执行上述命令后,进程列表中立即出现两个新条目,一个是执行“主”脚本的Powershell(
test.ps1
),一个是执行“后台脚本”的Powershell({ sleep 30; }
). - 10 秒后,第一个条目(与
test.ps1
相关)从进程列表中消失,而第二个条目保留在进程列表中。 - 再过 20 秒后(即总共 30 秒),第二个条目(与
{ sleep 30; }
相关)也会从进程列表中消失。
这是预期的行为,因为 Start-Process
无论如何都会在后台启动新进程,除非给出 -Wait
。到目前为止,还不错。
但是现在我们遇到了一个毛茸茸的问题,它已经花费了我们两天的调试时间,直到我们最终找出其中一个脚本行为不当的原因:
其实test.ps1
是通过SSH执行的
也就是说,我们已经在 Windows Server 2019 上安装了 Microsoft 的 OpenSSH 服务器实现,并已正确配置。在其他机器(Linux和Windows)上使用SSH客户端,我们可以登录到Windows服务器,我们可以通过执行以下命令在服务器上通过SSH执行test.ps1
客户端命令:
ssh -i <appropriate_key> administrator@ip.of.windows.server c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1
观察Windows服务器上的任务管理器,我们可以再次看到进程列表中的两个新条目,如上所述,只要在客户端上执行此命令。
但是,两个 条目在 10 秒后从服务器上的进程列表中消失。
这意味着主进程一结束,后台进程 ({ sleep 30; }
) 就会被杀死。这与记录的内容和应该发生的事情相反,我们确实需要防止它。
所以问题是:
我们如何更改 test.ps1
以便在脚本结束时后台进程 ({ sleep 30; }
) 在任何情况下都不会被终止,即使脚本是通过 SSH 启动的?
一些旁注:
这不是学术上的例子。实际上,我们在该服务器上有一个相当复杂的脚本系统,它由大约十几个 Powershell 脚本组成,其中一个是“主”脚本,它在后台执行其他脚本,如上所示;后台脚本本身可能会启动更多的后台脚本。
重要的是不要 首先通过 SSH 在后台启动主脚本。客户端必须处理主脚本的输出和退出代码,并且必须等到主脚本完成一些工作并且 returns.
这意味着我们必须使用 Powershell 的功能来启动主脚本中的后台进程(或启动能够启动后台进程的第三方程序,这些进程不会在主脚本时被杀死结束)。
Note: The original advice to use jobs here was incorrect, as when the job is stopped along with the parent session, any child processes still get killed. As it was incorrect advice for this scenario, I've removed that content from this answer.
不幸的是,当 PowerShell 会话结束时,从该会话创建的子进程也会结束。但是,当使用 PSRemoting 时,我们可以使用 -InDisconnectedSession
参数(别名为 -Disconnected
)告诉 Invoke-Command
到 运行 断开连接的会话中的命令:
$icArgs = @{
ComputerName = 'RemoteComputerName'
Credential = ( Get-Credential )
Disconnected = $true
}
Invoke-Command @icArcs { ping -t 127.0.0.1 }
根据我对无限 ping 的测试,如果父 PowerShell 会话关闭,远程会话应该继续执行。在我的例子中,ping
命令持续 运行ning 直到我自己在远程服务器上停止了进程。
这里的缺点是您似乎没有使用 PowerShell Remoting,而是通过 SSH 调用 shell 命令,而这些命令恰好是 PowerShell 脚本。如果您需要用于 PowerShell 远程处理的 SSH 传输,您还必须使用 PowerShell Core。