1

I'm trying to get a better understanding of how powershell handles jobs.

Both of these article posts helped a lot:

However, with the following sample script, I am getting unexpected results while calling Receive-Job. I expect a result on each line, but sometimes I see multiple results per line or extra blank carriage returns. Any idea on how to write out just the information that streams in from the console from each job?

$loops = 1..10
$jobs = new-object System.Collections.ArrayList

$loops | % {
    $jobs.add( 
        (start-job -ScriptBlock {
            param($list)
            $list | % {
                sleep -seconds (get-random -Maximum 3 -Minimum 1)
                write-host "Number is: $_"
            }
        } -ArgumentList (,$loops))
    ) | out-null
}


while ($jobs.count -gt 0)
{
    if ($jobs -ne $null)
    {
        $list = $jobs | ? { $_.HasMoreData -eq $true }
        $list | % { Receive-Job -Job $_ }
        $list2 = $jobs.Clone() | ? { $_.State -eq "Completed" }
        $list2 | % {
            $jobs.Remove($_) | out-null
        }
        $list = $null
    }
}

Output can be...

Number is: 1
Number is: 1
Number is: 2

Or sometimes...

Number is: 1
Number is: 2
Number is: 1Number is: 2
Number is: 1Number is: 2Number is: 2

1 Answers1

3

You are overcomplicating this by a lot. You don't need all of those loops to process things manually. You should take advantage of the cmdlets that are available to you. This should achieve the basics of what you want:

$loops = 1..10

$scriptBlock = {
  param($list)
  $list | % {
    sleep -seconds (get-random -Maximum 3 -Minimum 1)
    write-host "Number is: $_"
  }
}

$jobs = $loops | % { start-job -ScriptBlock $scriptBlock -ArgumentList (,$loops) }

$jobs | Wait-Job | Receive-Job

However, this will group all output together without any way to know what job did what, though. And the fact that you are using Write-Host means you can't actually access the data, it is merely printed to the screen.

If you want to actually store or process the resulting job output, you can do something like this:

$loops = 1..10

$scriptBlock = {
  param($list)
  $list | % {
    sleep -seconds (get-random -Maximum 3 -Minimum 1)
    "Number is: $_"  # don't use write-host, just output the string
  }
}

$jobs = $loops | % { start-job -ScriptBlock $scriptBlock -ArgumentList (,$loops) }

$jobs | Wait-Job  # wait for all jobs to be complete
$jobs |%{
   $output = $_ | Receive-Job
   # process $output here, do what you want with it
}
latkin
  • 16,402
  • 1
  • 47
  • 62
  • thanks! You are right in that I over-complicated the script. I tend to do that in experimenting. Your example works perfect, except what if I wanted to get the data while the job is running and not call wait-job? For instance, I was trying get any output (via write-host) piped from the job into the main powershell console while the job was running (which was the point of all the loops in the bottom of my script). Am I going down the road of "that's not the purpose/function of a job" by doing this? Should all jobs wait to be completed before calling `Receive-Job` ? – The Unique Paul Smith Aug 23 '12 at 21:09
  • 1
    It's not that that's not the "purpose" of jobs, it's just that that's not what they do best, and that's not what you have cmdlet support for. You can do all sorts of crazy loops and completion checks and get some semblance of "streaming" behavior, but it will be very complex and error-prone. That's a cost/benefit decision for you. What jobs do best is "I'll go do this work, and you can process the results when I'm done." If you want streaming, you could just have each job write to a different log file, and start a "tail" process to stream each log. – latkin Aug 23 '12 at 23:13
  • Thank you, you've been very helpful. I'll keep your advise in mind while working on some sample projects that use jobs. – The Unique Paul Smith Aug 24 '12 at 02:53