0

quick story: for the sake of experimentation with powershell I'm trying to learn how to effectively multithread a script.

now i know how to start jobs and pass variables to my second script, however i have decided to try and figure out how to turn this:

start-job ((Split-Path -parent $PSCommandPath) + "\someScript.ps1") -ArgumentList (,$argList)

into this:

start-job (. ((Split-Path -parent $PSCommandPath) + "\someScript.ps1")) -ArgumentList (,$argList)

reason for this is i have a variable declared in the parent script like this:

New-Variable var -value 0 -Option AllScope

and in the child script: var = "something" the first start-job passes my argument but the child doesn't set the global 'var' variable

the second doesn't pass my argument but the child script sets the global variable defined in the the parent just fine. $argList variable will be populated right up to this line of code in the second start-job but right after execution of the line, debug reveals the $argList variable to be null and i get "Start-Job : Cannot bind argument to parameter 'ScriptBlock' because it is null."

for the sake of argument assume that right up to the stated lines of code the variables contain the data they should.

can someone help me out with what is wrong with both attempts. Google has failed to give me any specifics answers to my problem. thanks in advance for any help i can get.

EDIT: using Start-Job (. ((Split-Path -parent $PSCommandPath) + "\someScript.ps1") $argList) accomplishes my goals however i keep getting Start-Job : Cannot bind argument to parameter 'ScriptBlock' because it is null. even though the arguments are in the script block and the child script is getting and processing the argument.

phuclv
  • 37,963
  • 15
  • 156
  • 475
LuckyFalkor84
  • 547
  • 1
  • 5
  • 14
  • That won't work. A job runs in a completely separate session. You can't "dot source" it to make it run in currrent session. – mjolinor Jan 06 '14 at 18:34
  • can you explain to me how the second example works (except the argument part) then? in the example a variable defined in parent script (to equal 0) is modifiable by the child script (to equal 500), to which after the child runs i do a print statement on the parents variable and it equals 500 as set by the child? – LuckyFalkor84 Jan 06 '14 at 19:10

1 Answers1

0

When you call Start-Job, the script runs in an entirely separate scope (PowerShell Runspace). You can't dot-source a script called directly through Start-Job. You'll have to have the external script process the parameter that's passed in via -ArgumentList, and then return it to the original host Runspace via Receive-Job.

Here's a complete example:

$a = 1;
$Job = Start-Job -FilePath C:\test\script.ps1 -ArgumentList $a;


Write-Host -Object "Before: $a"; # Before
Wait-Job -Job $Job;
$a = Receive-Job -Job $Job -Keep;
Write-Host -Object "After: $a"; # After

c:\test\script.ps1

Here's the contents of the file c:\test\script.ps1:

Write-Output -InputObject (([int]$args[0]) += 5);

Thread and Runspace Exploration

If you want to prove my earlier point about Start-Job creating a new thread and PowerShell Runspace, and a new Thread, then run this script:

# 1. Declare a thread block that retrieves the Runspace ID & ThreadID
$ThreadBlock = { 
    [runspace]::DefaultRunspace.InstanceId.ToString();
    [System.Threading.Thread]::CurrentThread.ManagedThreadId; 
    };

# 2. Start a job and wait for it to finish
$Job = Start-Job -ScriptBlock $ThreadBlock;
[void](Wait-Job -Job $Job);
Receive-Job -Job $Job -Keep;

# 3. Call the same ScriptBlock locally
& $ThreadBlock;

# 4. Note the differences in the Runspace InstanceIDs and ThreadIDs

Receiving Results Before Job Finish

You can call Receive-Job multiple times before a PowerShell Job has completed, to retrieve results. Here's an example of how that could theoretically work:

$ScriptBlock = { 
    1..5 | % { Start-Sleep -Seconds 2; Write-Output -InputObject $_; };
};

$Job = Start-Job -ScriptBlock $ScriptBlock;

while ($Job.JobStateInfo.State -notin ([System.Management.Automation.JobState]::Completed,[System.Management.Automation.JobState]::Failed)) {
    Start-Sleep -Seconds 3;
    $Results = Receive-Job -Job $Job;
    Write-Host -Object ('Received {0} items: {1}' -f $Results.Count, ($Results -join ' '));
}
  • can you explain to me how the second example works (except the argument part) then? in the example a variable defined in parent script (to equal 0) is modifiable by the child script (to equal 500), to which after the child runs i do a print statement on the parents variable and it equals 500 as set by the child? the variable i'm changing gets updated in the child alot in a loop and i check for that value in realtime for another function before the child script finishes. – LuckyFalkor84 Jan 06 '14 at 19:13
  • Basically, you're passing in the variable `$a`, which has a value of 1, into the external script via `Start-Job`. The external script take the value of `1`, passed into it, adds `5` to it, and then returns the value. The `Receive-Job` cmdlet retrieves the output from the external script (which was running as a PowerShell Job), and overwrites the `$a` variable. –  Jan 06 '14 at 19:47
  • i know of that, thanks though, however i am trying to get data from the script prior to it finishing. i edited my question a little bit. i managed to accomplish what i was trying to do though i am getting an error as well. – LuckyFalkor84 Jan 06 '14 at 20:01
  • You can call `Receive-Job` multiple times, before the job has finished, to get results from it. –  Jan 06 '14 at 20:12
  • return in the child is returning an array, i just need a single value, if nothing else i need the last array element, preferably i would like to limit the return stream just have the one value at any point in time, perhaps a way for the child to clear the stream prior to putting in a new value. – LuckyFalkor84 Jan 06 '14 at 20:45
  • You have direct control over what gets outputted by the PowerShell job. If you use `Write-Output`, or simply emit an object(s) to the pipeline (for example: `Get-Process`), then it will be part of the output. –  Jan 06 '14 at 21:23
  • i may have to find a different idea to solve my issue. i'm not getting consistent results out of the write-output or return statements. i do a receive-job - $job and i either get the job details or i get the output i was expecting or nothing at all. i am looping through the output of "(Get-Job -State Running)" to get the data from each job. what would have really helped was a way to have the child to push the data up to the parent as it's processing, however that seems not possible. – LuckyFalkor84 Jan 06 '14 at 21:42