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

问题描述

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


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

    $ErrorActionPreference = 'Continue'

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

    Write-@R_906_4045@ion "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

@H_404_5@

只要Invoke-ScriptBlock cmdlet不在模块内部,此方法就很好用。如果是这样,我将丢失“调用命令”消息中捕获的变量的闭包。

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

有没有一种方法可以使用传入的$ 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
}