Core/Invoke-TestParallel.ps1

function Invoke-TestParallel {
    <#
    .SYNOPSIS
        Runs one script block over many items on a pool of runspaces that have this module loaded
    .DESCRIPTION
        For a provider whose API answers one request in about a second and offers no batch
        endpoint, the seed's time is spent waiting on the wire one object at a time, and the only
        lever is to wait on several at once. This runs the block for every item on a runspace pool
        of -ThrottleLimit workers, in module scope, so the block can call the module's own request
        function. Windows PowerShell 5.1 has no ForEach-Object -Parallel, and a runspace pool is
        what that cmdlet is built on.

        Each worker runspace imports this module afresh, so it has the module's functions and its
        constants but none of the session's state: no active connection, no cached anything. The
        same rule as Start-Job applies, and a contract test enforces it: the block reads no
        $script: variable, and everything it needs travels in through -Parameter or the item.
        The block receives $Item and $Parameter, in that order.

        Results come back one per item, in the order the items were given, whatever order the
        workers finished in. A block that throws for one item fails that item alone: its message
        is in Error, Success is $false, and every other item is unaffected. A warning the block
        writes reaches the host as it happens, through the pool's host.

        Pester mocks live in the test runspace and do not reach a worker, so a suite that covers a
        caller of this function mocks the function itself with a body that runs the block inline.
    .PARAMETER InputObject
        The items, one block invocation each
    .PARAMETER ScriptBlock
        The work, taking $Item and $Parameter
    .PARAMETER Parameter
        What every invocation needs beyond its item: the connection, a password, a marker
    .PARAMETER ThrottleLimit
        How many workers run at once
    .PARAMETER Activity
        A progress bar's activity; nothing is drawn when it is not given
    .PARAMETER ShowProgress
        Draws the progress bar
    .OUTPUTS
        PSCustomObject typed TestParallelResult per item: Index, Input, Output, Error, Success
    .EXAMPLE
        PS> Invoke-TestParallel -InputObject $rows -Parameter @{ Connection = $connection } -ScriptBlock {
                param($Item, $Parameter)
                Invoke-AuthentikRequest -Method POST -Path '/core/users/' -Body $Item.Body -Connection $Parameter.Connection
            }

        Creates the users four at a time and returns one result per row, in row order.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [object[]]$InputObject,

        [Parameter(Mandatory = $true)]
        [scriptblock]$ScriptBlock,

        [Parameter()]
        [hashtable]$Parameter = @{},

        [Parameter()]
        [ValidateRange(1, 32)]
        [int]$ThrottleLimit = 4,

        [Parameter()]
        [string]$Activity,

        [Parameter()]
        [switch]$ShowProgress
    )

    $items = @($InputObject | Where-Object { $null -ne $_ })
    if ($items.Count -eq 0) { return }

    $manifest = Join-Path -Path $script:TestEnvironmentModuleRoot -ChildPath 'TestEnvironment.psd1'
    $initial = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $initial.ImportPSModule($manifest)
    $pool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThrottleLimit, $initial, $Host)
    $pool.Open()

    # The block travels as text and is rebuilt in the worker, then run inside the module there,
    # so it sees the module's functions rather than the worker's global scope.
    $body = $ScriptBlock.ToString()
    $runner = @'
param($ModuleName, $Body, $Item, $Parameter)
$module = Get-Module -Name $ModuleName
if (-not $module) { throw "The $ModuleName module did not load in the worker runspace." }
& $module ([scriptblock]::Create($Body)) $Item $Parameter
'@


    $jobs = New-Object System.Collections.Generic.List[object]
    try {
        for ($i = 0; $i -lt $items.Count; $i++) {
            $shell = [System.Management.Automation.PowerShell]::Create()
            $shell.RunspacePool = $pool
            $null = $shell.AddScript($runner).AddArgument('TestEnvironment').AddArgument($body).AddArgument($items[$i]).AddArgument($Parameter)
            $jobs.Add([PSCustomObject]@{ Index = $i; Shell = $shell; Handle = $shell.BeginInvoke() })
        }

        $done = 0
        foreach ($job in $jobs) {
            $output = @()
            $failure = $null
            try {
                $output = @($job.Shell.EndInvoke($job.Handle))
            }
            catch {
                $exception = $_.Exception
                # The worker's own exception is wrapped in the invocation's; the inner one is the
                # message the block threw.
                while ($exception.InnerException -and $exception -is [System.Management.Automation.RuntimeException] -and $exception.Message -match 'Exception calling') { $exception = $exception.InnerException }
                $failure = $exception.Message
            }
            if (-not $failure -and $job.Shell.Streams.Error.Count -gt 0) {
                $failure = @($job.Shell.Streams.Error | ForEach-Object { $_.ToString() }) -join '; '
            }
            $job.Shell.Dispose()

            $done++
            if ($Activity) {
                Write-TestProgress -Activity $Activity -Status "$done of $($items.Count)" `
                    -PercentComplete ([int](100 * $done / $items.Count)) -ShowProgress:$ShowProgress
            }

            [PSCustomObject]@{
                PSTypeName = 'TestParallelResult'
                Index      = $job.Index
                Input      = $items[$job.Index]
                Output     = $output
                Error      = $failure
                Success    = ($null -eq $failure)
            }
        }
        if ($Activity) { Write-TestProgress -Activity $Activity -Completed -ShowProgress:$ShowProgress }
    }
    finally {
        $pool.Close()
        $pool.Dispose()
    }
}