PoshCode Logo PowerShell Code Repository

Start-ScriptThreading by The13thDoctor 27 months ago (modification of post by The13thDoctor view diff)
diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/5759"></script>download | new post

Ever wanted to start some threads on a block of code but hated the headache of creating, watching, and collecting job data? This is my attempt to put a bandaid on that issue.
There are some assumptions made in this function, such as assuming you are threading to talk to a bunch of computers. Also assuming you only have one parameter to pass into your custom scriptblock, as well as assuming that the computer is a parameter for your scriptblock.

I hope you enjoy this nugget as I return to my retirement on Gallifrey…

  1. function Start-ScriptThreading{
  2. <#  
  3. .SYNOPSIS
  4.    
  5.     Runs a scriptblock against multiple computers simultaneously
  6.  
  7. .DESCRIPTION
  8.  
  9.     The ThreadedFunction function takes an array (usually a list of computers) and a scriptblock. The scriptblock will be run against
  10.     each item in the array expecting each item of the array to also be a parameter expected in the scriptblock.
  11.  
  12. .PARAMETER arComputers
  13.  
  14.     List of computers to run scriptblock against.
  15.  
  16. .PARAMETER ScriptBlock
  17.  
  18.     Block of script to run against list from parameter -Computers  
  19.  
  20. .PARAMETER maxThreads
  21.  
  22.     Maximum number of threads to run at a time  
  23.  
  24. .PARAMETER SleepTimer
  25.  
  26.     Time to wait between loops. This is a spacing timer to prevent the main loop from hogging cpu time.
  27.     A smaller number may create and check for jobs faster.
  28.     A large number will allow the separate threads more cpu time.  
  29.     Any longer than 1000 milliseconds may render the progress indicator incorectly.
  30.     (default is 500 milliseconds)  
  31.  
  32. .PARAMETER MaxWaitAtEnd
  33.  
  34.     Sets a timeout for threads before killing jobs. This timer is started after the last thread is created.
  35.     (default is 180 seconds)              
  36.  
  37. .NOTES
  38.    
  39.     Name: Start-ScriptThreading
  40.     Author: Kenneth W Hightower JR (The13thDoctor)
  41.     DateCreated: 11Feb2015
  42.     DateModified: 25Feb2015
  43.  
  44.  
  45.    
  46.     To load this function into the current shell for use, or to use this file seperate from your main script,
  47.  
  48.     dot-source as follows '. .\ThreadedFunction.ps1'    
  49.  
  50. .EXAMPLE
  51.    
  52.     Start-ScriptThreading -Computers 'server01', 'server02' -ScriptBlock {ping $Computer}
  53.  
  54.     The command 'ping' is threaded and ran against both server01 and server02. Declaring $Computer is required as is.
  55.  
  56. .EXAMPLE
  57.    
  58.     'server01', 'server02' | Start-ScriptThreading -ScriptBlock {Get-WMIObject win32_computersystem -ComputerName $Computer}
  59.  
  60.     Pipes the array as Computers and gets the Win32_ComputerSystem WMI object from server01 and server02 simultaneously.  
  61.  
  62. .INPUTS
  63.  
  64.     System.String. Function will accept an array of strings and assume each as a parameter for the provided scriptblock.
  65.  
  66. .OUTPUTS
  67.  
  68.     System.Object. All returned data from each iteration that the scriptblock may create is returned as a single array.
  69.            
  70.  
  71. #>  
  72.     param(
  73.         [parameter(Mandatory=$true,ValueFromPipeline=$true)]
  74.         [string[]]$arComputers,
  75.         [parameter(Mandatory=$true,ValueFromPipeline=$false)]
  76.         [scriptblock]$ScriptBlock,
  77.         [parameter(Mandatory=$false)]
  78.         [int]$maxThreads = 30,
  79.         [parameter(Mandatory=$false)]
  80.         [int]$SleepTimer = 500,
  81.         [parameter(Mandatory=$false)]
  82.         [int]$MaxWaitAtEnd = 180
  83.     )
  84.     BEGIN{
  85.         Write-Verbose "Initializing..."
  86.         $return = @()
  87.         $StartTime= Get-Date
  88.    
  89.         #Create working scriptblock
  90.         $Script = [ScriptBlock]::Create( @"
  91. param(`$Computer)
  92. &{ $ScriptBlock } @PSBoundParameters
  93. "@ )
  94.     $Computers = @()
  95.     }
  96.     PROCESS{
  97.    
  98.         foreach($Computer in $arComputers){
  99.             $Computers += $Computer #Yes, this will flatten multi-dimentional arrays...
  100.         }
  101.     }
  102. #when you pipeline to this function the begin section is called once, then for each item in the pipeline the entire process block is called.
  103. #In order to collect all items in the pipeline so that everything passed in can be threaded simultaneously as intended, we handle the threading at the end.
  104. #Otherwise each item will get threaded and collected before the next item is threaded.
  105.     END{
  106.         #Start Making Jobs
  107.         Write-Verbose "creating threads..."
  108.         Foreach($Computer in $Computers){
  109.        
  110.             #Check for running threads
  111.             while ($(Get-Job -State Running | measure-object).count -ge $maxThreads){
  112.                     write-host "$((Get-Job -State Running | measure-object).count) threads running, please wait..."
  113.                     start-sleep -s 3
  114.             } # End while ($(Get-Job -State Running
  115.        
  116.             Write-Verbose "starting $($Computer)"
  117.             Start-Job -name $Computer -scriptblock $Script -ArgumentList $Computer
  118.        
  119.             #Pretty progress indicator
  120.             $ComputersStillRunning = ""
  121.             ForEach ($System  in $(Get-Job -state running)){
  122.                 $ComputersStillRunning += ", $($System.name)"
  123.             }
  124.             $ComputersStillRunning = $ComputersStillRunning.TrimStart(", ")
  125.             Write-Progress  -Activity "Creating Jobs" `
  126.                             -Status "$($(Get-Job -State Running).count) threads running..." `
  127.                             -CurrentOperation "$ComputersStillRunning" `
  128.                             -PercentComplete ($(Get-Job -State Completed).count / $(Get-Job).count * 100)
  129.  
  130.             #Check for completed Jobs. Depending on number of jobs, we might as well collect data as we go.
  131.             ForEach($Job in $(Get-Job -State Completed)){
  132.                 If($Job.HasMoreData -eq "True"){
  133.                     Write-Verbose "Job $($Job.Id) finished early"
  134.                     $Data = Receive-Job $Job
  135.                     $Return+=$Data
  136.                 }#end if($job.hasmoredata...
  137.             }#end foreach($Job...
  138.         }#end Foreach($Computer in...
  139.    
  140.         #Done creating jobs.
  141.         Write-Verbose "Waiting for final threads..."
  142.         $Complete = Get-date
  143.  
  144.         #Wait for the last created jobs to finish
  145.         #Following loop gets skipped if pipeline is used to pass $Computers
  146.         While ($(Get-Job -State Running | measure-object).count -gt 0){
  147.        
  148.             #random information for progress screen
  149.             $ComputersStillRunning = ""
  150.             $TimeDiff = New-TimeSpan $StartTime $(Get-Date)
  151.             [string]$strTimingStatus = "{0} minutes, {1} seconds so far" -f $TimeDiff.Minutes, $TimeDiff.Seconds
  152.             ForEach ($System  in $(Get-Job -state running)){
  153.                 $ComputersStillRunning += ", $($System.name)"
  154.             }
  155.             $ComputersStillRunning = $ComputersStillRunning.TrimStart(", ")
  156.             Write-Progress  -Activity "Waiting for completion..." `
  157.                             -Status "$($(Get-Job -State Running).count) threads remaining: $strTimingStatus" `
  158.                             -CurrentOperation "$ComputersStillRunning" `
  159.                             -PercentComplete ($(Get-Job -State Completed).count / $(Get-Job).count * 100)
  160.  
  161.             #Collecting jobs as we wait
  162.             ForEach($Job in $(Get-Job -State Completed)){
  163.                 If($Job.HasMoreData -eq "True"){
  164.                     Write-Verbose "Job $($Job.Id) just completed"
  165.                     $Data = Receive-Job $Job
  166.                     $return+=$Data
  167.                 }#end if($job.hasmoredata
  168.             }#end foreach($Job
  169.  
  170.             #Done waiting. Threads most likely hung up on something, throw them away.
  171.             If ($(New-TimeSpan $Complete $(Get-Date)).totalseconds -ge $MaxWaitAtEnd){
  172.                     Write-Verbose "Killing all jobs still running, tired of waiting..."
  173.                     Get-Job -State Running | Remove-Job -Force
  174.             }
  175.  
  176.             #Lets not bog the CPU with lightning speed looping
  177.             Start-Sleep -Milliseconds $SleepTimer
  178.         }
  179.    
  180.         #Final cleaning, just in case....
  181.         ForEach($Job in $(Get-Job -State Completed)){
  182.             If($Job.HasMoreData -eq "True"){
  183.                 Write-Verbose "Job $($Job.Id) completed late"
  184.                 $Data = Receive-Job $Job
  185.                 $return+=$Data
  186.             }#end if($job.hasmoredata
  187.         }#end foreach($Job
  188.  
  189.         Write-Verbose "Cleaning up threads"
  190.         Get-Job | Remove-Job -Force | Out-Null
  191.  
  192.         #All this data needs to go somewhere....
  193.         return $return
  194.     }
  195. }#end function ThreadedScript

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me