3
$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
}}

The finalcode variable doesn't expand when the GetNewClosure() happens, so $code[$test] gets into the runspace instead of actual code and I can't get my desired results. Any advice?

Using the method from the answer I'm getting this in the runspace, but it doesn't execute properly. I can confirm that my command is loaded into runspace (at least while in debugger inside runspace I can execute it without dot sourcing)

[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>')

This is what I see in debugger in runspace

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>')) }
4c74356b41
  • 69,186
  • 6
  • 100
  • 141

2 Answers2

4

The problem with your code is that AddScript method of PowerShell class is expecting a string, not ScriptBlock. And any closure will be lost when you convert ScriptBlock to string. To solve this, you can pass argument to script with AddArgument method:

$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
    }))
}
user4003407
  • 21,204
  • 4
  • 50
  • 60
3

I'm not sure if there's a better way off the top of my head, but you can replace the variables yourself with serialized versions of the same.

You can't use $Using: in this case, but I wrote a function that replaces all $Using: variables manually.

My use case was with DSC, but it would work in this case as well. It allows you to still write your script blocks as scriptblocks (not as strings), and supports variables with complex types.

Here's the code from my blog (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
        }
    }
}

A better way for me to do this replacement would probably be to use the AST instead of string replacement, but.. effort.

Your Use Case

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

For better results you might assign a variable first and then just insert that:

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

$finalcode= {
    Invoke-Command -ScriptBlock $Using:value
} | Replace-Using
briantist
  • 45,546
  • 6
  • 82
  • 127
  • test is just a string that maps to hash key, will that work if `$code` is a hash and I need to access a property? i.e. `$code[$test].property`? also, does that work with the `GetNewCloseru()` or I don't need that with this function of yours? – 4c74356b41 Apr 10 '17 at 17:24
  • 1
    @4c74356b41 I _think_ so, but if you can resolve that outside of the scriptblock and then insert the result, it would be "cleaner". If you did it as `$using:code[$using:test].property` with my function the result will be something like this: `$([System.Management.Automation.PSSerializer]::Deserialize('...code...'))[$([System.Management.Automation.PSSerializer]::Deserialize('...test...'))].property` ; ugly but most likely functional. – briantist Apr 10 '17 at 17:30
  • @4c74356b41 No that's not expected; are you still creating a closure? If so, I think you should remove that and leave it a normal scriptblock (it's going to be a string once my function is done with it anyway). – briantist Apr 10 '17 at 17:42
  • @4c74356b41 oh wait I see what's wrong; it's case-sensitive so won't work with lowercase `$using` (try `$Using`). I should update that... – briantist Apr 10 '17 at 17:44
  • @4c74356b41 code in the question and in the gist have been updated, along with new-and-improved output types and an option to return a scriptblock. – briantist Apr 10 '17 at 17:49
  • @4c74356b41 What does the resulting replaced scriptblock look like? Is it referencing any variables that are defined outside of the scope? Are the objects in question "live" (connected to some existing state that will be lost in serialization)? – briantist Apr 10 '17 at 19:36
  • @4c74356b41 newlines where? Which piece is choking on them? – briantist Apr 10 '17 at 20:15
  • not the new lines, sorry, for some reason it works with `iex` but not with `icm` – 4c74356b41 Apr 10 '17 at 20:17
  • @4c74356b41 hm ok, well I'd love to see the final version, whatever you end up doing :-p – briantist Apr 10 '17 at 20:42