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 = 50 [Int]$doneUntil = $batchSize [Array]$failedJobs = @() 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]$failedJobsCount = ($global:octo.ScanJobs.$($Title).Jobs | Where-Object {$_.Status -eq "Failed"}).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 done of which $failedJobsCount have failed, $runningJobs active and $queuedJobs queued" -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"){ #handle timed out jobs if((Get-Date) -gt $global:octo.ScanJobs.$($Title).Jobs[$i].StartTime.AddMinutes($global:octo.defaultTimeoutMinutes)){ $failedJobs += $global:octo.ScanJobs.$($Title).Jobs[$i].Target $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Failed" Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has been running for more than $($global:octo.defaultTimeoutMinutes) minutes, killing it :(" -ForegroundColor DarkRed } #handle completed jobs if($global:octo.ScanJobs.$($Title).Jobs[$i].Handle.IsCompleted -eq $True){ try{ if($global:octo.ScanJobs.$($Title).Jobs[$i].Thread.HadErrors){ #check if the errors were terminating or not $terminatingErrors= @(); $terminatingErrors = @($global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Error.Exception | Where-Object {$_ -and $_ -is [System.Management.Automation.RuntimeException]}) if($terminatingErrors.Count -gt 0){ Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has completed with critical errors :(" -ForegroundColor DarkRed $global:octo.ScanJobs.$($Title).Jobs[$i].Attempts++ if($global:octo.ScanJobs.$($Title).Jobs[$i].Attempts -lt $global:octo.maxJobRetries){ Write-Host "Retrying $($global:octo.ScanJobs.$($Title).Jobs[$i].Target) after $($global:octo.ScanJobs.$($Title).Jobs[$i].Attempts) failure(s)" -ForegroundColor Green Write-Host "---------OUTPUT START---------" -ForegroundColor DarkYellow $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Error | fl * $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Warning | fl * $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Information | fl * if($VerbosePreference -eq "Continue"){ $global:octo.ScanJobs.$($Title).Jobs.Thread.Streams.Debug | fl * $global:octo.ScanJobs.$($Title).Jobs.Thread.Streams.Verbose | fl * } Write-Host "---------OUTPUT END-----------" -ForegroundColor DarkYellow $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Queued" }else{ $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Failed" Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) failed $($global:octo.ScanJobs.$($Title).Jobs[$i].Attempts) times, abandoning Job..." -ForegroundColor DarkRed $failedJobs += $global:octo.ScanJobs.$($Title).Jobs[$i].Target } }else{ Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has completed, but had $($global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Streams.Error.Count) non-retryable errors :|" -ForegroundColor Yellow $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Succeeded" } }else{ Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has completed without any errors :)" -ForegroundColor Green $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Succeeded" } }catch{ Write-Host "$($global:octo.ScanJobs.$($Title).Jobs[$i].Target) has crashed and will be retried" -ForegroundColor DarkRed $global:octo.ScanJobs.$($Title).Jobs[$i].Status = "Queued" } } #show progress bars from the child job $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($global:octo.ScanJobs.$($Title).Jobs[$i].Status -ne "Running" -or $progressBar.RecordType -eq "Completed"){ Write-Progress -Id $($i+$uniqueId) -Completed }else{ Write-Progress -Id $($i+$uniqueId) -Activity $progressBar.Activity -Status $progressBar.StatusDescription -PercentComplete $progressBar.PercentComplete } } } #dispose of threads that have completed if($global:octo.ScanJobs.$($Title).Jobs[$i].Status -in ("Succeeded", "Failed")){ Write-Host "---------OUTPUT START---------" -ForegroundColor DarkYellow try{ $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].StartTime = Get-Date if($global:octo.ScanJobs.$($Title).Jobs[$i].Handle){ $global:octo.ScanJobs.$($Title).Jobs[$i].Handle = $Null } if($global:octo.ScanJobs.$($Title).Jobs[$i].Thread){ $global:octo.ScanJobs.$($Title).Jobs[$i].Thread.Dispose() $global:octo.ScanJobs.$($Title).Jobs[$i].Thread = $Null } $global:octo.ScanJobs.$($Title).Jobs[$i].Handle = $handle $global:octo.ScanJobs.$($Title).Jobs[$i].Thread = $thread } } } Start-Sleep -Milliseconds 500 } if($failedJobs){ Write-Host "The following targets failed: $($failedJobs -join ', ') even after retries. Try running these individually, if issues persist log an Issue in Github with verbose logs" -ForegroundColor DarkRed if($global:VerbosePreference -ne "Continue"){ Write-Host "To run in Verbose mode, use set-M365PermissionsConfig -Verbose `$True before starting a scan." }else{ Write-Host "Verbose log path: $($global:octo.outputTempFolder)\M365PermissionsVerbose.log" } } Reset-ReportQueue } |