在带有运行空间的循环中使用变量内部的脚本块扩展变量

Expand variable with scriptblock inside variable in a loop with runspaces

$RunspaceCollection = @()
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
$RunspacePool.Open()
$code = @({'somecode'},{'someothercode'})

Foreach ($test in $case) {

    $finalcode= {
        Invoke-Command -ScriptBlock [scriptblock]::create($code[$test])
    }.GetNewClosure()

    $Powershell = [PowerShell]::Create().AddScript($finalcode)
    $Powershell.RunspacePool = $RunspacePool
    [Collections.Arraylist]$RunspaceCollection += New-Object -TypeName PSObject -Property @{
        Runspace = $PowerShell.BeginInvoke()
        PowerShell = $PowerShell
}}

GetNewClosure() 发生时,finalcode 变量不会扩展,因此 $code[$test] 进入运行空间而不是实际代码,我无法获得我想要的结果。有什么建议吗?

使用答案中的方法,我在运行空间中得到了这个,但它没有正确执行。我可以确认我的命令已加载到运行空间中(至少在运行空间内的调试器中我可以在没有点源的情况下执行它)

[System.Management.Automation.PSSerializer]::Deserialize('<ObjsVersion="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <SBK> My-Command -Par1 "egweg" -Par2 "qwrqwr" -Par3 "wegweg"</SBK>
</Objs>')

这是我在运行空间的调试器中看到的

Stopped at: $a = Invoke-Command -ScriptBlock { $([System.Management.Automation.PSSerializer]::Deserialize('<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
[DBG]: [Process:8064]: [Runspace12]: PS C:\git\infrastructure_samples>> 

Stopped at: $a = Invoke-Command -ScriptBlock { $([System.Management.Automation.PSSerializer]::Deserialize('<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
[DBG]: [Process:8064]: [Runspace12]: PS C:\git\infrastructure_samples>> s

Stopped at: </Objs>')) }

我不确定是否有更好的方法,但您可以自己用相同的序列化版本替换变量。

在这种情况下你不能使用$Using:but I wrote a function that replaces all $Using: variables manually.

我的用例是使用 DSC,但它也适用于这种情况。它允许您仍然将脚本块编写为脚本块(而不是字符串),并支持具有复杂类型的变量。

这是我博客 (also available as a GitHub gist) 中的代码:

function Replace-Using {
[CmdletBinding(DefaultParameterSetName = 'AsString')]
[OutputType([String], ParameterSetName = 'AsString')]
[OutputType([ScriptBlock], ParameterSetName = 'AsScriptBlock')]
param(
    [Parameter(
        Mandatory,
        ValueFromPipeline
    )]
    [String]
    $Code ,

    [Parameter(
        Mandatory,
        ParameterSetName = 'AsScriptBlock'
    )]
    [Switch]
    $AsScriptBlock
)

    Process {
        $cb = {
            $m = $args[0]
            $ser = [System.Management.Automation.PSSerializer]::Serialize((Get-Variable -Name $m.Groups['var'] -ValueOnly))
            "`$([System.Management.Automation.PSSerializer]::Deserialize('{0}'))" -f $ser
        }
        $newCode = [RegEx]::Replace($code, '$Using:(?<var>\w+)', $cb, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)

        if ($AsScriptBlock.IsPresent) {
            [ScriptBlock]::Create($newCode)
        } else {
            $newCode
        }
    }
}

我做这个替换的更好方法可能是使用 AST 而不是字符串替换,但是..努力。

您的用例

$finalcode= {
    Invoke-Command -ScriptBlock [scriptblock]::create($Using:code[$Using:test])
} | Replace-Using

为了获得更好的结果,您可以先分配一个变量,然后插入该变量:

$value = [scriptblock]::Create($code[$test])

$finalcode= {
    Invoke-Command -ScriptBlock $Using:value
} | Replace-Using

您的代码的问题是 PowerShell class 的 AddScript 方法需要一个字符串,而不是 ScriptBlock。当您将 ScriptBlock 转换为字符串时,任何闭包都将丢失。要解决这个问题,您可以使用 AddArgument 方法将参数传递给脚本:

$RunspaceCollection = New-Object System.Collections.Generic.List[Object]
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
$RunspacePool.Open()
$code = @({'somecode'},{'someothercode'})
$finalcode= {
    param($Argument)
    Invoke-Command -ScriptBlock ([scriptblock]::create($Argument))
}

Foreach ($test in $case) {
    $Powershell = [PowerShell]::Create().AddScript($finalcode).AddArgument($code[$test])
    $Powershell.RunspacePool = $RunspacePool
    $RunspaceCollection.Add((New-Object -TypeName PSObject -Property @{
        Runspace = $PowerShell.BeginInvoke()
        PowerShell = $PowerShell
    }))
}