How to pass variable by reference to Powershell job or runspace?

Viewed 2106

I have Powershell job.

$cmd = {
  param($a, $b)
  $a++
  $b++
}

$a = 1
$b = 2

Start-Job -ScriptBlock $cmd -ArgumentList $a, $b

How to pass $a and $b by a reference so when the job is done they will be updated? Alternatively how to pass variables by reference to runspaces?

3 Answers

Simple sample I just wrote (don't mind the messy code)

# Test scriptblock
$Scriptblock = {
param([ref]$a,[ref]$b)
$a.Value = $a.Value + 1
$b.Value = $b.Value + 1
}

$testValue1 = 20 # set initial value
$testValue2 = 30 # set initial value

# Create the runspace
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = [System.Threading.ApartmentState]::STA
$Runspace.Open()
# create the PS session and assign the runspace
$PS = [powershell]::Create()
$PS.Runspace = $Runspace

# add the scriptblock and add the argument as reference variables
$PS.AddScript($Scriptblock)
$PS.AddArgument([ref]$testValue1)
$PS.AddArgument([ref]$testValue2)

# Invoke the scriptblock
$PS.BeginInvoke()

After running this the for the testvalues are updated since they are passed by ref.

Passing parameters by reference is always awkward in PowerShell, and probably won't work for PowerShell jobs anyway, as @bluuf pointed out.

I would probably do something like this instead:

$cmd = {
    Param($x, $y)
    $x+1
    $y+1
}

$a = 1
$b = 2

$a, $b = Start-Job -ScriptBlock $cmd -ArgumentList $a, $b |
         Wait-Job |
         Receive-Job

The above code passes the variables $a and $b to the scriptblock and assigns the modified values back to the variables after receiving the job output.

a more comprehensive script with example .

  • it should also include ability to pass $host or something, to make write-host from the passed script, output to the console . but i don't have time to figure out how to do this .
$v = 1

function newThread ([scriptblock]$script, [Parameter(ValueFromPipeline)]$param, [Parameter(ValueFromRemainingArguments)]$args) {
    process {
        $Powershell = [powershell]::Create()
        $Runspace = [runspacefactory]::CreateRunspace()
        
        # allows to limit commands available there
        # $InitialSessionState = InitialSessionState::Create()
        # $Runspace.InitialSessionState = $InitialSessionState
        
        $Powershell.Runspace = $Runspace

        $null = $Powershell.AddScript($script)
        $null = $Powershell.AddArgument($param)
        foreach ($v_f in $args) {
            $null = $Powershell.AddArgument($v_f)
        }

        $Runspace.Open()
        $Job = $Powershell.BeginInvoke()
        
        [PSCustomObject]@{
            Job=$Job
            Powershell=$Powershell
        }
    }
}

$script = {
    param([ref]$v,$v2)
    $v.Value++
    $v2
}
$thread = newThread $script ([ref]$v) 3

do {} until ($thread.Job.IsCompleted)
$v1 = $thread.Powershell.EndInvoke($thread.Job)
$thread.Powershell.Dispose()

write-host "end $($v,$v1[0])"
Related