如何防止主机退出并返回退出码?
Posted
技术标签:
【中文标题】如何防止主机退出并返回退出码?【英文标题】:How to prevent exit of host and return exit code? 【发布时间】:2020-02-20 01:44:10 【问题描述】:Ansgar Wiechers 的回答在启动新的 PowerShell 进程时效果很好。 https://***.com/a/50202663/447901 这在 cmd.exe 和 powershell.exe 中都有效。
C:>type .\exit1.ps1
function ExitWithCode($exitcode)
$host.SetShouldExit($exitcode)
exit $exitcode
ExitWithCode 23
在 cmd.exe 交互式 shell 中。
C:>powershell -NoProfile -Command .\exit1.ps1
C:>echo %ERRORLEVEL%
23
C:>powershell -NoProfile -File .\exit1.ps1
C:>echo %ERRORLEVEL%
23
在 PowerShell 交互式外壳中。
PS C:>powershell -NoProfile -Command .\exit1.ps1
PS C:>$LASTEXITCODE
23
PS C:>powershell -NoProfile -File .\exit1.ps1
PS C:>$LASTEXITCODE
23
但是...在现有的交互式 PowerShell 主机中运行 .ps1 脚本将完全退出主机。
PS C:>.\exit1.ps1
<<<poof! gone! outahere!>>>
如何防止它退出主机外壳?
【问题讨论】:
病人:医生,我做“这个”的时候很痛。医生:那就不要做“这个”!!您在问“我怎样才能让灯在关闭时不关闭?”。 :)exit
命令将退出当前的命令解析器。如果您正在运行的主机程序位于顶层,exit
命令将关闭该解析器,而没有其他任何东西处于打开状态。我相信答案是,如果您不想让它退出,就不要再告诉它退出了吗?
顾客:我把这辆车带回来了,因为它不能右转。销售:那就不要右转!!
【参考方案1】:
不要使用$host.SetShouldExit()
:它不应该被用户代码调用。
相反,它由 PowerShell在内部使用,以响应用户代码中的 exit
语句。
只需在您的exit1.ps1
脚本中直接使用exit 23
,即可完成您想要的操作:
在 PowerShell 会话中运行时,脚本将设置退出代码23
,而不退出整个 PowerShell 进程;之后使用$LASTEXITCODE
查询。
.\exit.ps1; $LASTEXITCODE # -> 23
通过PowerShell CLI运行时:
使用-File
,脚本设置的退出码自动成为PowerShell进程的退出码,调用者可以查看;当从cmd.exe
调用时,%ERRORLEVEL%
反映了该退出代码。
powershell -File .\exit.ps1
:: This outputs 23
echo %ERRORLEVEL%
使用 -Command
,需要额外的工作,因为 PowerShell 然后只是将任何非零退出代码映射到 1
,这会导致要丢失的特定退出代码;为了弥补这一点,只需将exit $LASTEXITCODE
作为最后一条语句执行:
powershell -Command '.\exit.ps1; exit $LASTEXITCODE'
:: This outputs 23
echo %ERRORLEVEL%
如需详细了解 PowerShell 如何设置退出代码,请see this answer。
如果:
您无法控制通过 CLI 调用脚本的方式,但必须确保即使通过 -Command
调用脚本也能报告正确的退出代码,
并且您愿意承担使用$host.SetShouldExit()
的风险,即使它不是为直接使用而设计的,
您可以尝试以下方法:
function ExitWithCode($exitcode)
if ([Environment]::CommandLine -match ( # Called via the CLI? (-File or -Command)
' .*?\b' +
[regex]::Escape([IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) +
'(?:\.ps1\b| |$)')
)
# CAVEAT: While this sets the exit code as desired even with -Command,
# the process terminates instantly.
$host.SetShouldExit($exitcode)
else
# Exit normally, which in interactive session exits the script only.
exit $exitcode
ExitWithCode 23
函数通过自动$PSCommandPath
变量在进程命令行中查找执行脚本的文件名,以检测是否通过CLI直接调用封闭脚本,该变量包含脚本的完整路径。
如果是这样,则应用 $host.SetShouldExit()
调用以确保即使在通过 -Command
调用的情况下也能按预期设置退出代码。
请注意,这相当于重新利用有效的内部 .SetShouldExit()
方法。
令人惊讶的是,即使在-Command
字符串内的脚本调用之后出现了其他命令,这种重新调整用途仍然有效,但请注意,这总是意味着真正最后一个命令的成功状态 - 如果它不是脚本调用 - 然后被有效地忽略.
这种方法并非万无一失[1],但在实践中可能效果很好。
[1]
可能存在 误报,因为只查找文件 name,没有扩展名(因为-Command
允许省略脚本的 .ps1
扩展名)调用)。
如果脚本是通过另一个脚本或别名调用的,则可能存在假阴性。
【讨论】:
谢谢!。以下是我从反复试验中学到的一些东西: 1.PowerShell_ISE.exe
10.0.19041.1 将始终返回退出代码 1 或 0 - 无论我使用 -File
还是 -Command
,而 VS Code
1.60.2效果很好。 2. 如果我同时写了return X
和exit X
- 返回会弄乱退出代码。
@itsho,我在 ISE 中看不到 -File
的这种行为,而且我通常不希望主机环境有所作为。请注意,return
用于返回数据,而不是退出代码。有关退出代码的更多信息,请参阅this answer。有关return
与exit
的信息,请参阅this answer。【参考方案2】:
如何防止它退出主机外壳?
您可以检查当前运行的 PowerShell 进程是否是另一个 PowerShell 父进程的子进程,并且仅在该条件为真时调用 $host.SetShouldExit()
。例如:
function ExitWithCode($exitcode)
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
$parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name
if ('powershell.exe' -eq $parentProcName) $host.SetShouldExit($exitcode)
exit $exitcode
ExitWithCode 23
希望这会有所帮助。
【讨论】:
这看起来不错。使用-Command
时它仍然不适用于cmd.exe,但无论如何这可能不是一个好的用途。我猜将来的版本需要if ('powershell.exe', 'pwsh.exe' -contains $parentProcName)
。以上是关于如何防止主机退出并返回退出码?的主要内容,如果未能解决你的问题,请参考以下文章