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.

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

Now, I’m basically doing this

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

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

# BLOCK 5: Wait for runspaces to finish
while ($runspaces.Status -ne $null)
{
    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
    foreach ($runspace in $completed)
    {
        $runspace.Pipe.EndInvoke($runspace.Status)
        $runspace.Status = $null
    }
}

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.

$pool = [RunspaceFactory]::CreateRunspacePool(1, [int]$env:NUMBER_OF_PROCESSORS + 1)
$pool.ApartmentState = "MTA"
$pool.Open()
$runspaces = @()

$scriptblock = {
    Param (
        [string]$server,
        [int]$count
    )
    # Pretend I connected to a server here and gathered some info
    Write-Output "Pretended to connect to $server $count times"
}

1..10 | ForEach-Object {
    $runspace = [PowerShell]::Create()
    $null = $runspace.AddScript($scriptblock)
    $null = $runspace.AddArgument("sql2016")
    $null = $runspace.AddArgument(++$i)
    $runspace.RunspacePool = $pool
    $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
}

while ($runspaces.Status -ne $null)
{
    $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
    foreach ($runspace in $completed)
    {
        $runspace.Pipe.EndInvoke($runspace.Status)
        $runspace.Status = $null
    }
}

$pool.Close()
$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!

Chrissy is a Cloud and Datacenter Management & Data Platform MVP who has worked in IT for over 20 years. She is the creator of the popular SQL PowerShell module dbatools, holds a master's degree in Systems Engineering and is coauthor of Learn dbatools in a Month of Lunches. Chrissy is certified in SQL Server, Linux, SharePoint and network security. You can follow her on Twitter at @cl.

Posted in PowerShell, SQL Server

Leave a Reply