Overview

This function will take in a script or scriptblock, and run it against specified objects(s) in parallel.  It uses runspaces, as there are many situations where jobs or PSRemoting are not appropriate.

This is similar to Foreach-Parallel, with better logging, and the ability to specify timeouts for each runspace.

For the most recent version of Invoke-Parallel, visit the GitHub Invoke-Parallel repository.  Will periodically update the ps1 on TechNet.

Dependencies

 I recommend that you download and install Windows Management Framework 3 (PowerShell 3) regardless.

Instructions

The PS1 file here contains a function.  You can either add this function to a profile or module of your own, copy and paste the code to your current session, or dot source it like this: ."\\path\to\Invoke-Parallel.ps1".  Once the function is available per above, please run get-help Invoke-Parallel -full for more details and examples.

PowerShell
Edit|Remove
#dot source the function into our session 
    ."\\path\to\Invoke-Parallel.ps1" 
 
#Get full help details 
    Get-Help Invoke-Parallel -full 
 
#Basic examples 
 
    #Use ImportVariables to pull in variables from your session, multiply. 
        $Test = 2 
        1..20 | Invoke-Parallel -ImportVariables {$_ * $Test} 
 
    #Define list of servers.  Two simple examples provided  
        $servers = get-content C:\servers.txt  
        $servers = 1..70 | %{"Server-c$_"}  
 
    #Define the info we will pass into invoke-parallel.  This example passes in an object 
        $info = New-Object -TypeName psobject -Property @{ 
            detail1 = "something" 
            detail2 = "somethingelse" 
        } 
 
    #A basic example that tests connection against each server in $servers, all 70 at once, times out after 30 seconds, and that passes in $info to work with if needed 
    invoke-parallel -InputObject $servers -parameter $info -throttle 70 -runspaceTimeout 30 -ScriptBlock {  
 
        # We are using a scriptblock, so each item in $servers is $_, and the parameter $info is now $parameter 
 
        # If we need to call an external command (e.g. from a script or module) you must add it to the session 
            # Import-Module SomeModule 
 
        # This runspace does not share state with your session.  You must define variables in here, or pass them in with the Parameter parameter. 
            # $someVariable = 1 
 
        #Run whatever you would like! 
 
            #Test connection.  Add in a custom property that shows 
            test-connection -ComputerName $_ -count 1 | select Address, IPV4Address, ResponseTime, @{l="Parameter";e={"We passed in '$(  $parameter.detail1  )'"}} 
    }
Examples
Prior to each of the examples, define $servers as a list of computers
PowerShell
Edit|Remove
#define list of servers.  Two simple examples provided 
    $servers = get-content C:\servers.txt 
    $servers = 1..70 | %{"Server-c$_"} 
 
Example 1
PowerShell
Edit|Remove
#Example query pulling a few details from remote computers, including IE version and free space on the C drive.  Results displayed on screen 
    invoke-parallel -InputObject $servers -throttle 20 -runspaceTimeout 30 -ScriptBlock { 
         
        #Clear out variables 
            $ieVersion = $null 
            $freeSpace = $null 
 
        if($ping = Test-Connection -ComputerName $_ -BufferSize 16 -quiet -count 2){ 
         
            #Get iexplore version 
                Try { 
                    $ieVersion = Get-ItemProperty "\\$_\c$\Program Files\Internet Explorer\iexplore.exe" -ErrorAction stop | select -ExpandProperty versioninfo | select -ExpandProperty productversion 
                } 
                Catch{  
                    $ieVersion = "IE version query failed"} 
             
            #Get free space on system drive 
                Try { 
                    $freeSpace = Get-WmiObject Win32_LogicalDisk -ComputerName $_ -Filter "DeviceID='C:'" -ErrorAction stop | Select-Object -ExpandProperty freespace 
                    $freeSpace = [Math]::Round($freeSpace / 1GB , 2) 
                } 
                Catch{  
                    $freeSpace = "WMI query failed"}} 
 
        #Create and display object 
        $temp = "" | Select ComputerName, "IE Version""Free Space (GB)", Ping 
        $temp.ComputerName = $_ 
        $temp."IE Version" = $ieVersion 
        $temp."Free Space (GB)" = $freeSpace 
        $temp.ping = $ping 
        $temp 
 
    }
The output from the code above displays everything at the command line, but we can't work with it.  Alternatively, we could say $results = Invoke-Parallel...
Example 2
 
PowerShell
Edit|Remove
#Same query as above.  This time, save output for each server to a text file in a log path we specify with 'parameter'#Example query pulling a few details from remote computers, including IE version and free space on the C drive.#Save results for each computer here$logpath = "C:\temp" 
 
    invoke-parallel -InputObject $servers-parameter $logpath-throttle 20 -runspaceTimeout 30 -ScriptBlock { 
         
        #Clear out variables$ieVersion = $null$freeSpace = $nullif($ping = Test-Connection -ComputerName $_-BufferSize 16 -quiet -count 2){ 
         
            #Get iexplore versionTry { 
                    $ieVersion = Get-ItemProperty"\\$_\c$\Program Files\Internet Explorer\iexplore.exe"-ErrorAction stop |select-ExpandProperty versioninfo |select-ExpandProperty productversion 
                } 
                Catch{  
                    $ieVersion = "IE version query failed" 
                } 
             
            #Get free space on system driveTry { 
                    $freeSpace = Get-WmiObject Win32_LogicalDisk -ComputerName $_-Filter"DeviceID='C:'"-ErrorAction stop |Select-Object-ExpandProperty freespace 
                    $freeSpace = [Math]::Round($freeSpace/ 1GB , 2) 
                } 
                Catch{  
                    $freeSpace = "WMI query failed" 
                } 
        } 
 
        #Create and display object$temp = ""|Select ComputerName, "IE Version""Free Space (GB)", Ping 
        $temp.ComputerName = $_$temp."IE Version" = $ieVersion$temp."Free Space (GB)" = $freeSpace$temp.ping = $ping#export the object to a file$temp|Export-Clixml-path $parameter\$_.txt 
 
    } 
 
    #Read in the results - this example assumes there are no other .txt files in this directory!#I'm sleeping 1 second before hand to let the file system catch up.Start-sleep-seconds 1 
    $results = gci$logpath-filter*.txt | %{ import-clixml$_.fullname }
The code above saves data to individual text files in a specified log path.  It reads this data back in and displays it at the command line.

Example 3

PowerShell
Edit|Remove
 
#Same query again.  This time, define it as a script block, measure performance difference! 
#Example query pulling a few details from remote computers, including IE version and free space on the C drive.  Results displayed on screen 
     
    #define the scriptblock 
    $scriptBlock = { 
         
        #Clear out variables 
            $ieVersion = $null 
            $freeSpace = $null 
 
        if($ping = Test-Connection -ComputerName $_ -BufferSize 16 -quiet -count 2){ 
         
            #Get iexplore version 
                Try { 
                    $ieVersion = Get-ItemProperty "\\$_\c$\Program Files\Internet Explorer\iexplore.exe" -ErrorAction stop | select -ExpandProperty versioninfo | select -ExpandProperty productversion 
                } 
                Catch{  
                    $ieVersion = "IE version query failed" 
                } 
             
            #Get free space on system drive 
                Try { 
                    $freeSpace = Get-WmiObject Win32_LogicalDisk -ComputerName $_ -Filter "DeviceID='C:'" -ErrorAction stop | Select-Object -ExpandProperty freespace 
                    $freeSpace = [Math]::Round($freeSpace / 1GB , 2) 
                } 
                Catch{  
                    $freeSpace = "WMI query failed" 
                } 
        } 
 
        #Create and display object 
        $temp = "" | Select ComputerName, "IE Version""Free Space (GB)", Ping 
        $temp.ComputerName = $_ 
        $temp."IE Version" = $ieVersion 
        $temp."Free Space (GB)" = $freeSpace 
        $temp.ping = $ping 
        $temp 
 
    } 
 
    #test command speed with invoke-parallel - Result: 13.3 seconds 
    Measure-Command { invoke-parallel -InputObject $servers -throttle 20 -runspaceTimeout 30 -ScriptBlock $scriptBlock } | select -ExpandProperty totalseconds 
    
    #test command speed without invoke-parallel - Result: 117.6 seconds 
    Measure-Command { $servers | ForEach-Object $scriptblock } | select -ExpandProperty totalseconds
This final example illustrates the speed difference.  The time saved by using this function increases with the count of servers and execution time of your code.  We saved 100 seconds here.  What if each query took longer than a few seconds and we had a few thousand systems to run against?

 

Notes
Changes