如何在PowerShell全局作用域创建带原脚本错误路径的函数?
问题
假设有一个名为s.ps1的PowerShell脚本,内容如下:
1/0
我们需要实现一个New-GlobalFunction函数,它的定义如下:
NAME New-GlobalFunction SYNOPSIS Creates a new function in global scope from a script. SYNTAX New-GlobalFunction [-ScriptPath] <string> [-FunctionName] <string>
目标是通过New-GlobalFunction定义一个全局函数Invoke-S,调用Invoke-S时产生的错误位置信息要能显示原文件s.ps1,示例输出如下:
PS C:\> New-GlobalFunction -ScriptPath .\s.ps1 -FunctionName Invoke-S; Invoke-S RuntimeException: C:\s.ps1:1 Line | 1 | 1/0 | ~~~ | Attempted to divide by zero. ---------- Attempted to divide by zero. at <ScriptBlock>, C:\s.ps1: line 1
注意错误信息中明确指向了原文件s.ps1。
PowerShell的架构支持这种需求,因为脚本和函数本质上都是脚本块:
在PowerShell编程语言中,脚本块是可作为单个单元使用的语句或表达式集合。这些语句集合可以用大括号({})括起来、定义为函数,或者保存到脚本文件中。
实际上,s.ps1对应的脚本块可以通过Get-Command .\s.ps1 | % ScriptBlock获取,直接调用这个脚本块就能得到包含正确位置信息的错误。甚至可以通过以下命令将其转换为本地函数:
New-Item -Path Function: -Name Invoke-S -Value (Get-Command .\s.ps1 | % ScriptBlock)
但目前无法成功创建具备同样错误位置信息的全局函数。
请问是否存在一种方式实现New-GlobalFunction,使得由-ScriptPath指定文件中的脚本块产生的错误能在位置信息中显示正确的原文件?
无效的方法
以下是尝试过的几种创建Invoke-S的方法及结果:
| method | new_error | invoke_error | stacktrace |
|---|---|---|---|
| iex function global | Attempted to divide by zero. | at global:Invoke-S, | |
| function global: $sb | Missing function body in… | The term 'Invoke-S' is not recognized… | at |
| New-Item Function: | The term 'Invoke-S' is not recognized… | at | |
| New-Item global:Function: | Cannot find drive. A dri… | The term 'Invoke-S' is not recognized… | at |
| Get-Variable function: | Cannot find a variable w… | The term 'Invoke-S' is not recognized… | at |
这些方法要么无法在调用者作用域中创建可用函数(错误信息为'Invoke-S' is not recognized),要么堆栈跟踪中无法正确指向原文件(错误信息为at global:Invoke-S, <No file>)。
对应的测试代码如下:
function New-GlobalFunction { param( [Parameter(Mandatory)][string] $ScriptPath , [Parameter(Mandatory)][string] $FunctionName, [Parameter(Mandatory)][ValidateSet( 'iex function global' , 'function global: $sb' , 'New-Item Function:' , 'New-Item global:Function:', 'Get-Variable function:' )]$Method ) switch ($Method) { 'iex function global' { Invoke-Expression ` -Command "function global:$FunctionName { $(Get-Content $ScriptPath) }" } 'function global: $sb' { $sb = Get-Command ` -Name $ScriptPath | % ScriptBlock # function global:Invoke-S $sb throw 'Missing function body in function declaration.' } 'New-Item Function:' { New-Item ` -ErrorAction Stop ` -Path Function: ` -Name $FunctionName ` -Value (Get-Command $ScriptPath | % ScriptBlock ) } 'New-Item global:Function:' { New-Item ` -ErrorAction Stop ` -Path global:Function: ` -Name $FunctionName ` -Value (Get-Command $ScriptPath | % ScriptBlock ) } 'Get-Variable function:' { Get-Variable ` -Name 'function:' ` -ErrorAction Stop } }} $(foreach ($method in 'iex function global' , 'function global: $sb', 'New-Item Function:' , 'New-Item global:Function:' , 'Get-Variable function:' ) { [pscustomobject]@{ method = $method new_error = $(try {$null = New-GlobalFunction ` -ScriptPath .\s.ps1 ` -FunctionName Invoke-S ` -Method $method } catch {$_}) invoke_error = ($e = try {Invoke-S -ErrorAction Stop} catch {$_} ) stacktrace = $e.ScriptStackTrace e = $e } Remove-Item Function:Invoke-S -ErrorAction SilentlyContinue }) | Format-Table ` -Property @{e='method' ;width=25}, @{e='new_error' ;width=25}, @{e='invoke_error';width=38}, @{e='stacktrace' ;width=30}
参考资料
- about_Functions
- about_Scopes
- about_Scripts
内容的提问来源于stack exchange,提问作者alx9r
相关产品推荐
相关产品推荐

