private/Start-ScanJobs.ps1

function Start-ScanJobs{
    <#
        Author = "Jos Lieben (jos@lieben.nu)"
        CompanyName = "Lieben Consultancy"
        Copyright = "https://www.lieben.nu/liebensraum/commercial-use/"
    #>

       
    Param(        
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Title
    )

    $baseScriptBlock = {
        param (
            [string]$ModulePath,
            [string]$FunctionName,
            [hashtable]$Arguments,
            [hashtable]$octo
        )
        $global:octo = $octo
        Import-Module -Name $ModulePath -Force
        & $FunctionName @Arguments
    }

    Write-Verbose "Start multithreading $Title $($global:octo.ScanJobs.$($Title).Jobs.Count) jobs $($global:octo.maxThreads) at a time using $($global:octo.ScanJobs.$($Title).FunctionToRun)"

    Write-Progress -Id 1 -Activity $Title -Status "Starting initial threads" -PercentComplete 0
    
    [Int]$batchSize = 25
    [Int]$doneUntil = $batchSize
    while($true){
        [Int]$queuedJobs = ($global:octo.ScanJobs.$($Title).Jobs | Where-Object {$_.Status -eq "Queued"}).Count
        [Int]$runningJobs = ($global:octo.ScanJobs.$($Title).Jobs | Where-Object {$_.Status -eq "Running"}).Count
        [Int]$totalJobs = $global:octo.ScanJobs.$($Title).Jobs.Count
        [Int]$completedJobs = $totalJobs - $queuedJobs - $runningJobs
        try{$percentComplete = (($completedJobs / $totalJobs) * 100)}catch{$percentComplete = 0}
        Write-Progress -Id 1 -Activity $Title -Status "$completedJobs/$totalJobs Processing targets" -PercentComplete $percentComplete
        
        if($queuedJobs -eq 0 -and $runningJobs -eq 0){
            Write-Verbose "All jobs for $Title have finished"
            break
        }

        if($doneUntil -le $completedJobs){
            $doneUntil += $batchSize
            Reset-ReportQueue
        }

        #cycle over all jobs
        for($i = 0; $i -lt $totalJobs; $i++){
            #if job is running, check if it has completed
            if($global:octo.ScanJobs.$($Title).Jobs[$i].Status -eq "Running"){
                $jobProgressBars = $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Progress
                if($jobProgressBars){
                    $uniqueIds = $jobProgressBars | Select-Object -ExpandProperty ActivityId -Unique
                    foreach($uniqueId in $uniqueIds){
                        $progressBar = @($jobProgressBars | Where-Object {$_.ActivityId -eq $uniqueId})[-1]
                        if($progressBar.RecordType -eq "Completed" -or $global:octo.ScanJobs.$($Title).Jobs[$i].Handle.IsCompleted){
                            Write-Progress -Id $($i+$uniqueId) -Completed
                        }else{
                            Write-Progress -Id $($i+$uniqueId) -Activity $progressBar.Activity -Status $progressBar.StatusDescription -PercentComplete $progressBar.PercentComplete
                        }
                    }
                }
                if($global:octo.ScanJobs.$($Title).Jobs[$i].Handle.IsCompleted -eq $True){
                    try{
                        if($global:octo.ScanJobs.$($Title).Jobs[$i].Thread.HadErrors){
                            Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has completed with errors :(" -ForegroundColor DarkRed
                            $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Failed"
                        }else{
                            Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has completed without errors :)" -ForegroundColor Green
                            $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Succeeded"
                        }
                        Write-Host "---------OUTPUT START---------" -ForegroundColor DarkYellow
                        $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.EndInvoke($global:octo.ScanJobs.$($Title).Jobs[$i].Handle)
                        $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Error
                        $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Warning
                        $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Information
                        if($VerbosePreference -eq "Continue"){
                            $global:octo.ScanJobs.$($Title).Jobs.Thread.Streams.Debug
                            $global:octo.ScanJobs.$($Title).Jobs.Thread.Streams.Verbose
                        }
                        Write-Host "---------OUTPUT END-----------" -ForegroundColor DarkYellow

                    }catch{}                    

                    $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Dispose()
                    $global:octo.ScanJobs.$($Title).Jobs[$i].Thread = $Null
                    $global:octo.ScanJobs.$($Title).Jobs[$i].Handle = $Null
                }
            }
            #if job is queued, start it if we have room
            if($global:octo.ScanJobs.$($Title).Jobs[$i].Status -eq "Queued"){
                if($runningJobs -lt $global:octo.maxThreads){
                    Write-Host "Starting $($global:octo.ScanJobs.$($Title).Jobs[$i].Target)"
                    $runningJobs++
                    $thread = [powershell]::Create().AddScript($baseScriptBlock)
                    $Null = $thread.AddParameter('ModulePath', $global:octo.modulePath)
                    $Null = $thread.AddParameter('FunctionName', $global:octo.ScanJobs.$Title.FunctionToRun)
                    $Null = $thread.AddParameter('Arguments', $global:octo.ScanJobs.$($Title).Jobs[$i].FunctionArguments)
                    $Null = $thread.AddParameter('octo', $global:octo)
                    $handle = $thread.BeginInvoke()
                    $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Running"
                    $global:octo.ScanJobs.$($Title).Jobs[$i].Handle = $handle
                    $global:octo.ScanJobs.$($Title).Jobs[$i].Thread = $thread
                }
            }
        }

        Start-Sleep -Milliseconds 500
    }

    Reset-ReportQueue
}