【问题标题】:How to prevent exit of host and return exit code?如何防止主机退出并返回退出码?
【发布时间】:2023-04-06 09:03:01
【问题描述】:

Ansgar Wiechers 的回答在启动新的 PowerShell 进程时效果很好。 https://stackoverflow.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 命令将关闭该解析器,而没有其他任何东西处于打开状态。我相信答案是,如果您不想让它退出,就不要再告诉它退出了吗?
  • 顾客:我把这辆车带回来了,因为它不能右转。销售:那就不要右转!!

标签: powershell exit-code


【解决方案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)
猜你喜欢
  • 2011-10-02
  • 1970-01-01
  • 2010-10-09
  • 2015-03-26
  • 2012-04-18
  • 2011-08-17
  • 1970-01-01
  • 2017-11-09
  • 2017-02-11
相关资源
最近更新 更多