【问题标题】:PowerShell exit event does not work when referencing function引用函数时,PowerShell 退出事件不起作用
【发布时间】:2018-05-08 15:47:56
【问题描述】:

我有一个 .ps1 脚本,一旦 PowerShell 会话结束,该脚本需要执行一些代码来进行清理。我遇到的问题的最简单重现:

function ExitLogic(){
     Write-Host 'closing'
}
Write-Host 'started'
Register-EngineEvent `
    -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) `
    -Action { ExitLogic }

ExitLogic 永远不会发生。如果我在我的 PowerShell 会话中手动使用exit 命令,如果我单击X 窗口按钮,如果我在cmd.exe 中运行 PowerShell 则不会...我不知所措。 但是,如果我将 Action 参数从引用 ExitLogic 作为函数更改为只是 Write-Host 'inline closing',那么它确实有效。

【问题讨论】:

  • 为什么不使用Finally {} 块?
  • Write-Host 不是一个很好的证据。使用[System.Console]::Beep()
  • @TheIncorrigible1 我没有包含这个细节,但我的用例最终在一个模块中。当事情被关闭时,需要一些自动的东西。
  • @Reza 感谢您的建议!我忘记了控制台哔声。这会更容易检测到它,我通过 CMD 提示符启动 PowerShell,然后我可以 exit 而不关闭原始窗口,我将能够看到写入主机的文本。

标签: powershell


【解决方案1】:

tl;dr

根据调用脚本的方式,-Action 脚​​本块可能看不到您的 ExitLogic 函数 - 为了确保它在那里可见,请在 global 范围内定义该函数。


一般要点:

  • *.ps1 文件不会在子进程中运行,因此退出 脚本 并不等于退出 PowerShell 引擎作为一个整体。

  • 脚本默认在子作用域中运行,因此其中定义的函数仅在脚本运行时才在作用域内。

  • 只有在 global 范围内定义的函数才能在 -Action 脚本块中引用,因为它在 动态模块 中运行(内存中的模块,与持久化的modules 一样,只能看到全局范围内的定义。

  • -Action 脚本块执行时,大部分常规 PowerShell 功能不再可用从 v6.2.0 开始编写

    • 值得注意的是,PowerShell 自己的输出流不再可用 - 不再打印常规输出和错误消息。

    • 但是,您可以 使用Write-Host 产生显示输出(尽管外部调用者收到通过标准输出),但请注意,如果退出的引擎也关闭了当前控制台窗口,您甚至不会看到。Read-Host 命令可以延迟关闭。

    • 似乎只有来自 Microsoft.PowerShell.Utility 模块的命令可用,而所有其他模块都已卸载 - 请参阅 this GitHub issue

  • 重要事件处理程序只有在 PowerShell 本身退出会话时才能运行(无论是正常还是通过触发的脚本终止错误使用throw) - 如果您通过关闭控制台/终端窗口间接终止 PowerShell,则事件处理程序将不会运行 - 请参阅@ 987654323@了解详情。

您的具体情况

  • 除非您碰巧通过 PowerShell powershell.exe-File 参数(而不是 -Command)调用脚本文件powershell.exe Windows PowerShell,pwsh 用于 PowerShell (Core) 7+),-Action 脚本块看到 ExitLogic 函数,因为它只被定义在 脚本文件 的范围内,而不是全局范围内。

    • 使ExitLogic全局 范围内可用的一种方法是“点源”您的脚本(例如,. .\script.ps1),但请注意,这仅适用于 em> 全局范围(这本质上是 -File CLI 参数所做的 - 它在全局范围内点源脚本文件);需要做更多的工作才能将函数从子作用域或模块作用域添加到全局作用域。

下面的 sn-p 演示了这一点:

假设存在脚本.\script.ps1,内容如下:

function ExitLogic {
  Write-Host 'closing'
}

Write-Host 'started'

$null = Register-EngineEvent `
 -SourceIdentifier PowerShell.Exiting `
 -Action { 
   try { ExitLogic } catch { Write-Host $_ }
   Read-Host -Prompt 'Press ENTER to exit' 
 }

注意:正如Get-Help Register-EngineEvent所说,支持以下-SourceIdentifier值:PowerShell.ExitingPowerShell.OnIdlePowerShell.OnScriptBlockInvoke,它们对应于枚举的值-like [System.Management.Automation.PSEngineEvent] class;显式使用后者(如在 OP 中)为您提供更高的类型安全性,但输入起来也更麻烦。

警告以下示例命令退出正在运行的 PowerShell 会话;只有Read-Host 命令使窗口保持打开状态,允许您检查事件处理程序的Write-Host 输出;按Enter,窗口关闭

  • 直接调用(相当于外部powershell.exe -Command "& .\script.ps1 ..."):
# Due to direct invocation, the -Action block does NOT see ExitLogic()
PS> .\script.ps1; exit
started
The term 'ExitLogic' is not recognized as the name of a cmdlet, function, 
script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Press ENTER to exit: 
  • 点源调用(相当于从外部调用powershell.exe -File script.ps1 ...):
# Thanks to dot-sourcing in the global scope, the -Action block DOES see ExitLogic()
PS> . .\script.ps1; exit
started
closing
Press ENTER to exit: 

【讨论】:

  • 感谢您非常彻底的回答!现在我需要弄清楚这个在模块中的含义......这就是所有这些的根源。我正在尝试集成一个 .net 日志库并让我的模块运行 .ps1 文件作为模块初始化的一部分,以设置日志记录,然后在使用该模块的任何处理完成后,我需要关闭并刷新日志记录。也许 dot sourcing 会在这里起作用,或者全局选项。
  • @Ethan:很高兴听到它有帮助。不幸的是,点源仅加载到 current 范围内,因此如果您从模块内进行点源,则生成的定义将在全局范围内(I'已经更新了答案以使其更清楚)。您可以在全局范围内显式创建退出函数的副本,也可以将其主体(脚本块)直接传递给-Action(假设不再依赖于自定义函数)。如果您对此有困难,我建议您提出一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 1970-01-01
  • 2021-04-06
相关资源
最近更新 更多