4

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?

Yoda
  • 17,363
  • 67
  • 204
  • 344
  • I'm not sure if this is actually possible with jobs since these actually spawn a new Powershell process (it is possible when using runspaces). – bluuf Jan 08 '19 at 10:10
  • @bluuf Thanks for the suggestion if you can run runspaces in parallel could you please show how to pass by reference a variable to it? I updated the question to reflect this comment. – Yoda Jan 08 '19 at 10:15

3 Answers3

4

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.

bluuf
  • 936
  • 1
  • 6
  • 14
  • This is great, I learned basics of runspaces thanks to your suggestion. Could you tell me should when should I use STA or MTA mode and also is there a reason or a way to limit of number of concurrent runspaces at a time? – Yoda Jan 08 '19 at 20:05
  • 1
    Boe Prox wrote an excellent blog about runspaces: https://learn-powershell.net/2012/05/13/using-background-runspaces-instead-of-psjobs-for-better-performance/ which is a great source to get started. – bluuf Jan 08 '19 at 22:31
  • is it possible to output something to the console, from such `[powershell]` ? i don't see why would you need `[System.Threading.ApartmentState]::STA` . – irvnriir Mar 29 '22 at 12:31
4

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.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • If system performance is part of the consideration, 'runspaces' are much lighter weight than 'Start-Job' cmdlet, as described and measured in this easy-to-read, well organized, excellent resource https://adamtheautomator.com/powershell-multithreading/ – BentChainRing Mar 25 '20 at 17:19
  • as far i understand, the values will be copied by `Start-Job` . References are small while the values can be 2GB strings, or huge arrays . – irvnriir Mar 31 '22 at 11:15
  • i've just been told that Reference Types already work as References, without need to `[ref]` them . – irvnriir Mar 31 '22 at 13:09
0

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])"
irvnriir
  • 673
  • 5
  • 14