Immediately Output Runspace Results to the Pipeline

In my previous post, I presented the template I use anytime I need to add multithreading to my scripts.

Recently, I had a request to add multi-threading to Read-DbaBackupHeader. This was the first runspace in which I had to output the results from the runspace to the host -- usually I just needed commands to run in the background, like with bulk-inserts to SQL Server.

So two interesting things came out of this particular change. First, I made use of the time that was spent waiting for runspaces to finish.

1# BLOCK 5: Wait for runspaces to finish
2 while ($runspaces.Status.IsCompleted -notcontains $true) {}

Now, I'm basically doing this

1# BLOCK 5: Wait for runspaces to finish
2 while ($runspaces.Status.IsCompleted -notcontains $true) { $runspaceresult }

Ultimately, though, this is what I had to do to get the output right to screen.

 1# BLOCK 5: Wait for runspaces to finish
 2while ($runspaces.Status -ne $null)
 3{
 4    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
 5    foreach ($runspace in $completed)
 6    {
 7        $runspace.Pipe.EndInvoke($runspace.Status)
 8        $runspace.Status = $null
 9    }
10}

Now, I came upon an issue with the $restore sometimes not returning anything. I'm not sure why; perhaps it has something to do with pooling. SQL Server didn't return an error reading the backup header, it just returned nothing. To handle this issue, I basically restarted the runspace if the result came back as null. The code is here if you'd like to see how I handled it.

Copy/Pastable Code

I generally like to provide code that actually does something useful but in this case, it just complicated things. So this outputs text -- but it does it directly to the pipeline without waiting until all runspaces have finished.

 1$pool = [RunspaceFactory]::CreateRunspacePool(1, [int]$env:NUMBER_OF_PROCESSORS + 1)
 2$pool.ApartmentState = "MTA"
 3$pool.Open()
 4$runspaces = @()
 5
 6$scriptblock = {
 7    Param (
 8        [string]$server,
 9        [int]$count
10    )
11    # Pretend I connected to a server here and gathered some info
12    Write-Output "Pretended to connect to $server $count times"
13}
14
151..10 | ForEach-Object {
16    $runspace = [PowerShell]::Create()
17    $null = $runspace.AddScript($scriptblock)
18    $null = $runspace.AddArgument("sql2016")
19    $null = $runspace.AddArgument(++$i)
20    $runspace.RunspacePool = $pool
21    $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
22}
23
24while ($runspaces.Status -ne $null)
25{
26    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
27    foreach ($runspace in $completed)
28    {
29        $runspace.Pipe.EndInvoke($runspace.Status)
30        $runspace.Status = $null
31    }
32}
33
34$pool.Close()
35$pool.Dispose()

If you'd like to see this with a couple more comments, check out the gist.

Hmm, I wonder if this runspace will be easier for others to understand. Looks like I'll be linking this blog post in my previous one. Hope this helped!