Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- function Invoke-Parallel {
- <#
- .SYNOPSIS
- Run a scriptblock against input objects in parallel.
- .DESCRIPTION
- Execute a scriptblock against all input objects. Uses PSJobs to run multiple jobs in parallel. Note that variables, functions, and modules will not be available in the scriptblock. For variables and functions this can be overridden on an individual basis using the -Variable and -Function parameters.
- .PARAMETER Scriptblock
- The script block to execute. Refer to the object using the $_ (or $PSItem) automatic variable.
- .PARAMETER InputObject
- The set of objects to run against. This can be specified on the command line or through the pipeline.
- .PARAMETER Function
- Specifies a list of FunctionInfo objects that will be available within the scriptblock. Use the "Get-Item function:<FunctionName>" cmdlet to get the object(s).
- .PARAMETER Variable
- Specifies a list of variables names whose names and values will be available within the scriptblock. Any changes to the value of these variables in the scriptblock will not be reflected in the calling scope.
- .PARAMETER Throttle
- The number of jobs allowed to run in parallel. The default value is 10.
- .PARAMETER Timeout
- The number of seconds any single job is allowed to run before being forcibly terminated. The default is 0, meaning no time limit.
- .PARAMETER SleepMilliseconds
- The number of milliseconds to pause between checking the job queue for finished jobs. The default is 200, meaning the queue is checked 5 times a second.
- .PARAMETER Quiet
- Suppress the progress bar. By default the progress bar is displayed.
- .PARAMETER Activity
- Specifies the first line of text in the heading above the status bar. This text describes the activity whose progress is being reported.
- .PARAMETER Format
- A scriptblock used to format the input object into text. Use $PSItem (or $_) for the input object. The result of the scriptblock is used in the progress bar.
- .PARAMETER Property
- A property of the input object to use in the progress bar text. The property's value is converted to a string for display purposes.
- .EXAMPLE
- Get-Content c:\servers.txt | Invoke-Parallel -Scriptblock { Test-Connection $_ }
- .EXAMPLE
- Get-Something 'One value' 32
- .INPUTS
- Whatever is expected by the scriptblock specified using the -Scriptblock parameter
- .OUTPUTS
- The type of object output by the -Scriptblock parameter, if any
- .NOTES
- Any job terminated through timeout will of course not produce any additional output.
- Uses ProgressManager class from https://pastebin.com/QrVLYZST
- Uses Remove-Member function from https://pastebin.com/gjwmFA4v
- #>
- [CmdletBinding(DefaultParameterSetName = 'Format')]
- Param (
- [Parameter(Mandatory, Position = 0)][scriptblock]$ScriptBlock,
- [Parameter(Mandatory, ValueFromPipeline)]$InputObject,
- [System.Management.Automation.FunctionInfo[]]$Function,
- [string[]]$Variable,
- [int]$Throttle = 10,
- [int]$Timeout = 0,
- [Alias('SleepTimer')][int]$SleepMilliseconds = 200,
- [Parameter(ParameterSetName = 'Quiet')][Alias('HideProgress')][switch]$Quiet,
- [Parameter(ParameterSetName = 'Format')][Parameter(ParameterSetName = 'Property')][string]$Activity = 'Monitoring Progress',
- [Parameter(ParameterSetName = 'Format')][scriptblock]$Format = { $_.ToString() },
- [Parameter(ParameterSetName = 'Property')][ValidateNotNullOrEmpty()][string]$Property
- )
- BEGIN {
- if ((-not $PSBoundParameters.ContainsKey('Quiet')) -and (-not [Environment]::UserInteractive)) {
- $Quiet = $true
- }
- $NewScriptBlock = [scriptblock]::Create('Set-Variable x -Value @($input) -Option Private; $private:x | ForEach-Object { ' + $ScriptBlock.ToString() + ' }')
- [hashtable]$StartJobParams = @{ ScriptBlock = $NewScriptBlock }
- #region Generate InitializationScript for all jobs
- [System.Collections.Generic.List`1[string]]$InitScript = @()
- [void]$InitScript.Add("Set-Location '$PWD'")
- if ($PSBoundParameters['Function']) {
- foreach ($f in $Function) {
- $Name = $f.Name
- try {
- [string]$Definition = $f.Definition
- [void]$InitScript.Add("function $Name { $Definition }")
- } catch {
- Write-Warning "${Name}: $($_.Exception.Message)"
- }
- }
- }
- if ($PSBoundParameters['Variable']) {
- [string]$TempfileName = (New-TemporaryFile).FullName
- $Vars = $Variable | % {
- [PSCustomObject]@{
- Name = $_
- Value = $PSCmdlet.GetVariableValue($_)
- }
- }
- $Vars | Export-Clixml $TempfileName -Encoding UTF8 -Depth 4
- [void]$InitScript.Add("Import-Clixml '$TempfileName' | ForEach-Object { try { Set-Variable -Name `$_.Name -Value `$_.Value -WhatIf:`$false -Scope Script } catch {} }")
- }
- $StartJobParams.InitializationScript = [scriptblock]::Create($InitScript -join [System.Environment]::NewLine)
- # Write-Verbose "InitializationScript: $($StartJobParams.InitializationScript.ToString())"
- #endregion
- [boolean]$IgnoreProcess = $false
- [System.Collections.Generic.List`1[System.Object]]$List = @()
- [ProgressManager]$pm = [ProgressManager]::new(0)
- $pm.Activity = $Activity
- If ($PSBoundParameters.ContainsKey('InputObject')) {
- [void]$List.AddRange(@($InputObject))
- $IgnoreProcess = $true
- $pm.Total = $List.Count
- }
- $pm.Format = switch ($PSCmdlet.ParameterSetName) {
- 'Format' { $Format; break }
- 'Property' { { $_ | Select-Object -Property $Property | % { $_.$Property } | Out-String }; break }
- default { { $_.ToString() } }
- }
- [System.Management.Automation.Job]$Job = $null
- [System.Collections.Generic.List`1[System.Management.Automation.Job]]$Jobs = @()
- [System.Collections.Generic.List`1[System.Management.Automation.Job]]$CompletedJobs = @()
- [System.Collections.Generic.List`1[System.Management.Automation.Job]]$TimedOutJobs = @()
- $RemoveMemberParams = @{
- Name = 'PSComputerName', 'PSShowComputerName', 'RunspaceId', 'PSSourceJobInstanceId'
- PassThru = $true
- }
- [scriptblock]$Cleanup = {
- # test-ending
- trap { continue }
- $ErrorActionPreference = 'SilentlyContinue'
- # Write-Verbose "END finally block starting"
- if ((Get-Variable TempfileName -ErrorAction Ignore) -and (Test-Path $TempfileName)) {
- Remove-Item -Path $TempfileName -Verbose:$false -Confirm:$false -ErrorAction Ignore
- }
- $pm.Complete()
- $Jobs | Stop-Job -PassThru | Wait-Job | Remove-Job -Force
- [void]$Jobs.Clear()
- }
- [scriptblock]$ProcessJobs = {
- param ([string]$Phase)
- # Write-Verbose "Start ProcessJobs ($Phase)"
- if ($Phase -ne 'PROCESS') {
- # Write-Verbose "Sleeping for $SleepMilliseconds milliseconds"
- Start-Sleep -Milliseconds $SleepMilliseconds
- }
- # Output any data from jobs
- $Jobs.where{ $_.HasMoreData }.foreach{ Receive-Job -Job $_ | Remove-Member @RemoveMemberParams }
- #region Process completed jobs
- [void]$CompletedJobs.Clear()
- $Jobs | ? { $_.State -in @('Completed', 'Failed') } | % { [void]$CompletedJobs.Add($_) }
- $CompletedJobs.where{ $_ }.foreach{
- $Job = $_
- if ($Job.HasMoreData) { Receive-Job -Job $Job | Remove-Member @RemoveMemberParams }
- # Write-Verbose "Removing job $($_.Name) from queue"
- Remove-Job -Job $Job -ErrorAction Ignore
- [void]$Jobs.Remove($Job)
- $pm.Update()
- }
- #endregion
- #region Kill any jobs over time limit
- if ($Timeout -and $Jobs.Count) {
- [void]$TimedOutJobs.Clear()
- $Jobs | ? { (New-TimeSpan $_.PSBeginTime).TotalSeconds -gt $Timeout } | % { [void]$TimedOutJobs.Add($_) }
- $TimedOutJobs.where{ $_ }.foreach{
- $Job = $_
- Write-Verbose "Removing job $($Job.Name) because of timeout"
- if ($Job.HasMoreData) { Receive-Job -Job $Job | Remove-Member @RemoveMemberParams }
- Stop-Job -Job $Job -ErrorAction Ignore
- Remove-Job -Job $Job -ErrorAction Ignore
- [void]$Jobs.Remove($Job)
- $pm.Update()
- }
- }
- #endregion
- #region Start new jobs if under limit
- while ($Jobs.Count -lt $Throttle -and $List.Count) {
- $Object = $List[0]
- if (-not $Object) {
- # Write-Verbose "Null object...skipping"
- [void]$List.RemoveAt(0)
- continue
- }
- $Job = $Object | Start-Job @StartJobParams
- $Job | Add-Member -NotePropertyName InputObject -NotePropertyValue $Object -Force
- # Write-Verbose "Adding job $($Job.Name) to queue"
- [void]$Jobs.Add($Job)
- [void]$List.RemoveAt(0)
- }
- #endregion
- #region Update progress bar
- if (-not $Quiet -and $Jobs.Count -and ($Phase -ne 'BEGIN')) {
- $pm.WriteProgress($Jobs[-1].InputObject)
- }
- # Write-Verbose "End ProcessJobs ($Phase)"
- #endregion
- } # $ProcessJobs
- . $ProcessJobs 'BEGIN'
- }
- PROCESS {
- if ($IgnoreProcess) { return }
- # Write-Verbose "Adding $($InputObject | Out-String -Stream) to list"
- [void]$List.Add($InputObject)
- $pm.Total += 1
- # Start-Sleep -Milliseconds 10
- [bool]$Breaking = $true
- try {
- . $ProcessJobs 'PROCESS'
- $Breaking = $false
- } finally {
- if ($Breaking) { . $Cleanup }
- }
- }
- END {
- try {
- while ($Jobs.Count -gt 0) { . $ProcessJobs 'END' }
- } finally {
- . $Cleanup
- # Write-Verbose "END finally block ending"
- }
- }
- } # Invoke-Parallel
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement