使用范围重新创建 PowerShell 脚本块

Recreate PowerShell Script Block Using Scope

我有以下 cmdlet 来调用任意脚本块(通常调用 exe)并处理 return 代码。这里的目标是打印一个命令行,运行它,然后如果失败就抛出一个错误....


function Invoke-ScriptBlock {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$Block
    )

    $ErrorActionPreference = 'Continue'

    $stringBlock = "@`"`n" + $Block.ToString().Trim() + "`n`"@"
    $stringValue = . ([scriptblock]::create($stringBlock))

    Write-Information "Invoking command: $stringValue"

    . $Block
    if ($lastexitcode -ne 0) { Write-Error "Command exited with code $lastexitcode" -EA Stop }
}

$comment = 'acomment'
Invoke-ScriptBlock { git commit -m $comment }
# prints Invoking command: git commit -m acomment

只要 Invoke-ScriptBlock cmdlet 不在模块内,它就可以很好地工作。如果是,我将失去对“调用命令”消息中捕获的变量的关闭。

Import-Module .\Util.psm1
$comment = 'acomment'
Invoke-ScriptBlock { git commit -m $comment }
# prints Invoking command: git commit -m

有没有一种方法可以使用传入的 $Block 中的原始上下文重新创建脚本块?

没有受支持的方法,但您可以通过将会话状态引用从原始 $Block 复制到重新创建的脚本块来使用反射来实现:

function Invoke-ScriptBlock {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$Block
    )

    # To prove local and module-scoped variables won't affect the new script block
    $comment = ''

    # Obtain a reference to the relevant internal ScriptBlock property 
    $ssip = [scriptblock].GetProperty('SessionStateInternal',[System.Reflection.BindingFlags]'NonPublic,Instance')

    # Copy the value from $Block
    $ssi = $ssip.GetMethod.Invoke($Block, @())

    # Create new block with the same content 
    $newBlock = [scriptblock]::create("@`"`n" + $Block.ToString().Trim() + "`n`"@")

    # Overwrite session state value with the one with copied from $Block
    $ssip.SetMethod.Invoke($newBlock, @($ssi))

    $stringValue = & $newBlock

    Write-Information "Invoking command: $stringValue"
    & $Block
}