<# .SYNOPSIS This runs anything in the specified scriptblock the specified number of times concurrently. Output must be single-line with this sample script. Rewrite and adapt as needed. .DESCRIPTION This runs anything in the specified scriptblock the specified number of times concurrently. Output must be single-line with this sample script. Rewrite and adapt as needed. The online web page article about Wrap-Jobs.ps1 is at: http://wiki.powershelladmin.com/mediawiki/index.php/Powershell_jobs The specified script block needs to be specified on the command line, you cannot enter it when prompted for it when omitted, because that will cause it to be cast to a string and fail. The specified script block runs once for each line in the specified input file. Each line will be passed to the script block in $args[0]. Lines containing only whitespace or starting with a "#" are skipped. .PARAMETER inputFile This specifies a file with input from which each line will be passed to the specified script block in $args. It will be in $args[0] or you can use the param() keyword. .PARAMETER scriptBlock A script block that is run once for each line in the input file. You should return single-line output. .PARAMETER outputFile Output destination file name. .PARAMETER jobTimeout Maximum wait time for each job in seconds. .PARAMETER jobNumber Maximum number of concurrently running jobs. .PARAMETER clobber Overwrite existing specified output file without prompting. #> param([Parameter(Mandatory=$true)][string] $inputFile, [Parameter(Mandatory=$true)][scriptblock] $scriptBlock, [Parameter(Mandatory=$true)][string] $outputFile, [int] $jobTimeout = 30, [int] $jobNumber = 30, [switch] $clobber) $startTime = Get-Date "Script start time: $startTime" function Process-Jobs { param([Parameter(Mandatory=$true)] $private:jobs) 'Processing jobs...' $private:jobs | Foreach { $private:waitResult = Wait-Job $_.Job -Timeout $jobTimeout $private:outputLine = Receive-Job $_.Job -ErrorAction SilentlyContinue # Populate the $script:output hash. # This should mean there's a timeout. if ( (! $private:waitResult) -and (! $private:outputLine) ) { $script:output.($_.Name) = "Timeout ($jobTimeout seconds)" # Stop the job so we don't build up a list of processes # that are waiting for a timeout beyond what we specified. Stop-Job $_ -ErrorAction SilentlyContinue | Out-Null } # This means we didn't get anything so there was probably an error. # I think there's some danger of the last error being for a different # job due to the asynchronous aspects... elseif (! $private:outputLine) { # Store the error in the output hash, with the type stripped # (there has to be a better way...) $script:output.($_.Name) = 'Error: ' + ($error[0].Exception -replace '^[^:]+:\s*') } # This should mean there's output from the command in the script block. else { $script:output.($_.Name) = $private:outputLine } } } # Exit if the file doesn't exist. if (-not (Test-Path $inputFile)) { "$inputFile does not exist. Cannot proceed." exit 1 } # Prompt to overwrite if the specified output file exists, # unless the switch parameter -clobber is specified. if (! $clobber) { if (Test-Path $outputFile) { $private:answer = Read-Host "$outputFile exists. Overwrite? (Y/n) [yes]" if ($private:answer -imatch 'n') { 'Aborted.' exit 2 } } } # Collect the computer names in an array. # Allow for comments by skipping lines that start with "#". Also skip blank lines. $private:input = Get-Content $inputFile | Where { $_ -notmatch '^#' -and $_ -match '\S' } $private:mainJobs = @() $private:jobCounter = 0 $private:totalJobCounter = 0 # Declare output data structure. $script:output = @{} # Iterate the computer array $private:input | Foreach { $private:jobCounter++ $private:totalJobCounter++ "${_}: Starting job $private:totalJobCounter ($private:jobCounter of $jobNumber in current batch)" $private:mainJobs += @{Name = $_ Job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $_ } if ($private:jobCounter -eq $jobNumber) { $private:jobCounter = 0 Process-Jobs $private:mainJobs $private:mainJobs = @() } } # Process remainder Process-Jobs $private:mainJobs $private:mainJobs = @() # Produce output and pipe it to a file. Use utf8 encoding. # Initialize counter $private:counter = 0 $private:output.GetEnumerator() | Sort-Object Name | Foreach { '{0, -30}{1}{2}' -f $_.Name, ' | ', $_.Value } | Out-File -Encoding utf8 $outputFile @" `n`nDone! Output file: $outputFile Script start time: $startTime Script end time: $(Get-Date) "@