如何防止主机退出并返回退出码?

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 Xexit X - 返回会弄乱退出代码。 @itsho,我在 ISE 中看不到 -File 的这种行为,而且我通常不希望主机环境有所作为。请注意,return 用于返回数据,而不是退出代码。有关退出代码的更多信息,请参阅this answer。有关returnexit 的信息,请参阅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)

以上是关于如何防止主机退出并返回退出码?的主要内容,如果未能解决你的问题,请参考以下文章

kali终端如何保存并退出

如何获取程序返回值,退出码,错误码

linux保存退出命令

mysql 如何退出

不同的 BASH 退出状态码

如何避免Docker容器启动脚本运行后自动退出