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

2 Answers 2

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
    }))
}
Sign up to request clarification or add additional context in comments.

1 Comment

Nice! Glad to see a solution to actual problem instead of a workaround.
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

9 Comments

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 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('<xml>...code...</xml>'))[$([System.Management.Automation.PSSerializer]::Deserialize('<xml>...test...</xml>'))].property ; ugly but most likely functional.
@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).
@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...
@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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.