starting a job with dot source

1.1k Views Asked by At

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.

1

There are 1 best solutions below

7
On

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