Powershell runspaces and color output - multithreading

Short issue description:
I need non-interspersed output from additional powershell runspaces that my script spawns. I need to be able to set the color of individual lines in the output.
Long issue description:
I wrote a script to deploy updates to a vendor application on multiple remote servers. Originally the script was meant to only deploy a single update at a time, but I have a new requirement that I now process multiple updates interactively. So I've re-written my script to use runspaces so that I can open sessions on all the servers, initialize them, then for every update the user inputs a runspace is created for each of the initialized sessions and the deployment work is done. I previously used jobs, but had to go to runspaces because you cannot pass a PSSession to a job.
I now have my script working, but the output is less than desirable. The job version had nice output because I could call Receive-Job and get all all my output grouped by thread. Also I could use write-host to log output which allows for colored responses (ie. Red text when updates fail to apply). I have been able to get my runspace version of the job to group output by thread, but only by using write-output. If I use write-host output occurs immediately, causing interspersed output which is unacceptable. Unfortunately write-output does not allow colored output. Setting $host.UI.RawUI.ForegroundColor also does not work because if the output does not happen at the moment in time when the color has been set, the effect does not happen. Since my output won't happen until the end, the $host settings are no longer in play.
Below is a quick demo script to illustrate my woes:
#Runspacing output example. Fix interleaving
cls
#region Setup throttle, pool, iterations
$iterations = 5
$throttleLimit = 2
write-host ('Throttled to ' + $throttleLimit + ' concurrent threads')
$iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$Pool = [runspacefactory]::CreateRunspacePool(1,$throttleLimit,$iss,$Host)
$Pool.Open()
#endregion
#This works because the console color is set at the time output occurs
$OrigfC = $host.UI.RawUI.ForegroundColor
$host.UI.RawUI.ForegroundColor = 'red'
write-host 'THIS IS RED TEXT!'
start-sleep 2
$host.UI.RawUI.ForegroundColor = $OrigfC
#define code to run off the main thread
$scriptBlock = {
$nl = ([Environment]::NewLine.Chars(0)+[Environment]::NewLine.Chars(1))
#This does not work because output won't occur until after color has been reset
$OrigfC = $host.UI.RawUI.ForegroundColor
$host.UI.RawUI.ForegroundColor = 'yellow'
write-output ($nl + ' TEST: ' + $args[0])
Write-Output (' Some write-output: ' + $args[0])
Start-Sleep 1
write-host (' Some write-host: ' + $args[0]) -ForegroundColor Cyan # notice write-host occurs immediately
Start-Sleep 1
$host.UI.RawUI.ForegroundColor = $OrigfC
}
#Start new runspaces
$threads = #()
$handles = #(for($x = 1; $x -le $iterations; $x++)
{
$powerShell = [PowerShell]::Create().AddScript($scriptBlock).AddParameters(#($x))
$powershell.RunspacePool = $Pool
$powerShell.BeginInvoke()
$threads += $powerShell
})
#Wait for threads to complete
$completedCount = 0
$completed = ($handles | where-object {$_.IsCompleted -eq $true}).count
while($handles.IsCompleted.Contains($false))
{
if($completedCount -ne ($handles | where-object {$_.IsCompleted -eq $true}).count)
{
$completedCount = ($handles | where-object {$_.IsCompleted -eq $true}).count
write-host ('Threads Completed: ' + $completedCount + ' of ' + $iterations)
}
write-host '.' -nonewline
Start-Sleep 1
}
write-host ('Threads Completed: ' + ($handles | where-object {$_.IsCompleted -eq $true}).count + ' of ' + $iterations)
#output from threads
for($z = 0; $z -lt $handles.Count; $z++)
{
$threads[$z].EndInvoke($handles[$z]) #causes output
$threads[$z].Dispose()
$handles[$z] = $null
}
$Pool.Dispose()
The output is below, unless specified the output is in gray:
My goal is to be able to get the lines that say "Some write-output: X" set to one color, and the "TEST: X" set to a different color. Ideas? Please note I'm running from the powershell prompt. You will get different results if you run in the ISE.
Throttled to 2 concurrent threads
THIS IS RED TEXT! #Outputs in Red
. Some write-host: 1 #Outputs in Cyan
Some write-host: 2 #Outputs in Cyan
.Threads Completed: 2 of 5 #Outputs in Yellow
. Some write-host: 3 #Outputs in Cyan
Some write-host: 4 #Outputs in Cyan
.Threads Completed: 4 of 5 #Outputs in Yellow
. Some write-host: 5 #Outputs in Cyan
.Threads Completed: 5 of 5
TEST: 1
Some write-output: 1
TEST: 2
Some write-output: 2
TEST: 3
Some write-output: 3
TEST: 4
Some write-output: 4
TEST: 5
Some write-output: 5
Edit: Adding another example to address Mjolinor's Answer. M, you are correct I can pass sessions to a job; I have oversimplified my example above. Please consider this example below where I am sending a function to the job. If this line ( if(1 -ne 1){MyFunc -ses $args[0]} ) is commented out below, it will run. If the line is not commented out the session (type System.Management.Automation.Runspaces.PSSession) gets converted to a type of Deserialized.System.Management.Automation.Runspaces.PSSession, even though the MyFunc call cannot be hit. I have not been able to figure this out so I started moving towards runspaces. Do you think there is a job oriented solution?
cls
$ses = New-PSSession -ComputerName XXXX
Write-Host ('Outside job: '+$ses.GetType())
$func = {
function MyFunc {
param([parameter(Mandatory=$true)][PSSession]$ses)
Write-Host ('Inside fcn: '+$ses.GetType())
}
}
$scriptBlock = {
Write-Host ('Inside job: '+$args[0].GetType())
if(1 -ne 1){MyFunc -ses $args[0]}
}
Start-Job -InitializationScript $func -ScriptBlock $scriptBlock -Args #($ses) | Out-Null
While (Get-Job -State "Running") { }
Get-Job | Receive-Job
Remove-Job *
Remove-PSSession -Session $ses

I don't quite understand the statement that you can't pass a PSSession to a job. You can run Invoke-Command with both -Session and -AsJob parameters, creating at job targeted to the session.
As far as the coloring conundrum, have you considered using the Verbose or Debug streams for the output you want to be a different color? Powershell should automatically make it a different color depending on the stream it came from.

This gets a bunch easier in Powershell 7, which wasn't yet available when the question was originally asked.
In powershell 7 you can use the foreach-object -parallel {} -asjob command and then fetch the results when all the jobs are completed.
In this example:
There are 5 different jobs with 5 sub-steps
Each step has a 0.5 second delay to demonstrate interleaving
Each job get a color specific to the job
Each step gets a color specific to the step
The output is color coded, and grouped by job
$script = {
foreach ( $Step in 1..5 ) {
write-host "Job $_" -ForegroundColor #{'1'='red';'2'='yellow';'3'='cyan';'4'='magenta';'5'='green'}[[string]$_] -NoNewLine
write-host " Step $Step" -ForegroundColor #{'1'='red';'2'='yellow';'3'='cyan';'4'='magenta';'5'='green'}[[string]$Step]
sleep 0.5
} # next step
} # end script
$Job = 1..5 | ForEach-Object -Parallel $Script -ThrottleLimit 5 -AsJob
$job | Wait-Job | Receive-Job
It should be noted:
The challenge with $job | Wait-Job | Receive-Job is that if any one job hangs, then this command will never return. There are a plethora of posts which describe how to deal with this limitation.
Foreach-object and Foreach-Object -Parallel are two entirely different animals. The former can make calls to predefined functions whereas the later will only have access to what was defined inside the script block.

Related

Not able to move to the next loop of vm list

I have a code that starts VM's in batches. I am able to start vm's in batches. I am facing an issue here where ('*adds*','*DB*','*') are vm's containing the following wildcard names and need to be started in the following order.The script only starts the first list of vms with 'adds' and continues to restart it in loop it does not move to the next. Any help guys.
$bubbleName="VmList"
do{
$vname=#('*adds*','*DB*','*')
$i=0
Write-Host "Starting VM with "$vname[$i]
$vmList = Get-AzVM -ResourceGroupName $bubbleName -Name $vname[$i]
$batch = #{
Skip = 0
First = 2
}
do{
do{
foreach($vm in ($vmList | Select-Object #batch)){
$params = #($vm.Name, $vm.ResourceGroupName)
$job = Start-Job -ScriptBlock {
param($ComputerName,$serviceName)
Start-AzVM -Name $ComputerName -ResourceGroupName $serviceName
} -ArgumentList $params
}
Wait-Job -Job $job
Get-Job | Receive-Job
Write-Host $batch
$batch.Skip += 2
}
until($batch.skip -ge $vmList.count)
}while($job.state -ne "Completed")
$i++
}while($vname[$i] -ne $null)
You changed this a bit from an earlier (now deleted) version. However, I continued working on the old one and I'll share my thoughts.
If I understand correctly you want to start VMs in orders
*adds*
*db*
* (everything else)
Honestly, I think you're going about your loops are a little convoluted. If it were me, I'd sort the a list of all VMs according to the above order. Then code a single traditional for loop to start 2 at a time and use Wait-Job in the loop.
I don't have an Azure environment to test with but here are my notes from earlier:
Step 1: get the list in the order you need it. This will prevent the funky stuff you're doing with -First & -Skip:
# Example data, substituting for VM objects
$Objects =
#(
[PSCustomObject]#{ Name = "adds1" }
[PSCustomObject]#{ Name = "DB1" }
[PSCustomObject]#{ Name = "adds2" }
[PSCustomObject]#{ Name = "DB2" }
[PSCustomObject]#{ Name = "something" }
[PSCustomObject]#{ Name = "another" }
[PSCustomObject]#{ Name = "last" }
)
$Objects =
$Objects.Where( { $_.Name -match 'adds'} ) +
$Objects.Where( { $_.Name -match 'db'} ) +
$Objects.Where( { $_.Name -notmatch '(adds|db)'} )
Obviously this is a simulation that you'd have to adjust to your VMs. Maybe Objects will actually be defined by Get-AzVM -ResourceGroupName $bubbleName -Name *. At any rate, you should be able to get the VMs in the desired order.
Step 2: a loop to start 2 at a time, waiting for them to start before moving on to the next 2:
$Step = 1
# Note: 1 means 2 at a time, $i and $i+$Step, in this case 1.
# So the first iteration will work on index 0 & 1
# You can modify $Step to different size chunks.
For($i = 0; $i -lt $Objects.Count; ++$i )
{
$ii = $i + $Step
$Jobs =
$Objects[$i..$ii] |
ForEach-Object{
# Run the Start-Job commands against the appropriate machines
}
# Note: The assignment of $Jobs solves another problem. You were only waiting
# for the 2nd of the 2 jobs that were started. Now Wait-Job -Job $Job
# will be an array of jobs, which is allowed by the cmdlet without piping..
# Wait-Job -Job $Jobs
# ...
# I also didn't understand why one of the loops was conditioned on While
# $job.State -ne 'Completed'. Wait job should be enough and the above code
# assumed that.
# Reassign $i so when the loop increments naturally it's 1 past $ii.
# Effectively this chunks 2 elements per iteration.
$i = $ii
}
Again, I don't have the right environment to test all this. But conceptually this should be enough to get you through. I'm sorry about the variable renaming, but given the disadvantage, I had to hack my way through purely on concept.

Multi-threaded powershell script on single processor system

I have access to a single-core single-processor VM with which to do logging for my team. I have the following code:
$sb = {
Param($_)
if($_.CONTROLLER -ne ".xx" ){
$posIP = "10." + $_.IP + $_.CONTROLLER
if (Test-Connection -ComputerName $posIP -Count 1 -Quiet) {
$mapPath = "\\" + $posIP + "\c$"
net use $mapPath $password /user:$userName | Out-Null
if(Test-Path $mapPath$dataFile) {
[xml]$periods = Get-Content $mapPath$dataFile
$endDate = $periods.IndataDbf.ingredient.PeriodDetail.PeriodEndDate | select -last 1
$output = "$($_.STORE);$endDate" }
else {
$outPut = $_.STORE + ';' + "$dataFile Not Found" }
net use $mapPath /de | Out-Null
}
else {
$outPut = $_.STORE + ';' + "Map FAILED" }
Write-Output $OutPut
}
}
Import-Csv $inFile | ForEach-Object {
while ((Get-Job -State Running).Count -ge 100) {
Start-Sleep -Seconds 5;
}
Write-Output $_.STORE
Start-Job -Scriptblock $sb -ArgumentList $_ | Write-Verbose
Get-Job -State Completed -HasMoreData 1 | Receive-Job | Out-File -Append -FilePath $outLog
}
Get-Job | Wait-Job | Receive-Job | Out-File -Append -FilePath $outLog
Which runs well, but takes the same amount of time as running the same code without Start-Job and just a loop. However, the previous logging command used BATCH files and automatically opened a couple dozen child command windows to process data, then return, and it runs in under half the time. The code used is the same, so I don't understand why adding more threads didn't make the script run faster. Can anyone tell me why a BATCH file program with a couple dozen child windows runs so much faster with arguably the same code? Any why does the Start-Job command not improve the speed at all? I would think it would try to execute multiple threads simultaneously.
Because there is a lot of overhead when using start-job and whenever you use pipeline.
If you use runspaces instead it maybe faster.Take a look at http://newsqlblog.com/2012/05/22/concurrency-in-powershell-multi-threading-with-runspaces/

Powershell Throttle Multi thread jobs via job completion

All the tuts I have found use a pre defined sleep time to throttle jobs.
I need the throttle to wait until a job is completed before starting a new one.
Only 4 jobs can be running at one time.
So The script will run up 4 and currently pauses for 10 seconds then runs up the rest.
What I want is for the script to only allow 4 jobs to be running at one time and as a job is completed a new one is kicked off.
Jobs are initialised via a list of servers names.
Is it possible to archive this?
$servers = Get-Content "C:\temp\flashfilestore\serverlist.txt"
$scriptBlock = { #DO STUFF }
$MaxThreads = 4
foreach($server in $servers) {
Start-Job -ScriptBlock $scriptBlock -argumentlist $server
While($(Get-Job -State 'Running').Count -ge $MaxThreads) {
sleep 10 #Need this to wait until a job is complete and kick off a new one.
}
}
Get-Job | Wait-Job | Receive-Job
You can test the following :
$servers = Get-Content "C:\temp\flashfilestore\serverlist.txt"
$scriptBlock = { #DO STUFF }
invoke-command -computerName $servers -scriptblock $scriptBlock -jobname 'YourJobSpecificName' -throttlelimit 4 -AsJob
This command uses the Invoke-Command cmdlet and its AsJob parameter to start a background job that runs a scriptblock on numerous computers. Because the command must not be run more than 4 times concurrently, the command uses the ThrottleLimit parameter of Invoke-Command to limit the number of concurrent commands to 4.
Be careful that the file contains the computer names in a domain.
In order to avoid inventing a wheel I would recommend to use one of the
existing tools.
One of them is the script
Invoke-Parallel.ps1.
It is written in PowerShell, you can see how it is implemented directly. It is
easy to get and it does not require any installation for using it.
Another one is the module SplitPipeline.
It may work faster because it is written in C#. It also covers some more use
cases, for example slow or infinite input, use of initialization and cleanup scripts.
In the latter case the code with 4 parallel pipelines will be
$servers | Split-Pipeline -Count 4 {process{ <# DO STUFF on $_ #> }}
I wrote a blog article which covers multithreading any given script via actual threads. You can find the full post here:
http://www.get-blog.com/?p=189
The basic setup is:
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.Open()
$Code = [ScriptBlock]::Create($(Get-Content $FileName))
$PowershellThread = [powershell]::Create().AddScript($Code)
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = "" | Select-Object Handle, Thread, object
$Job.Handle = $Handle
$Job.Thread = $PowershellThread
$Job.Object = $Object.ToString()
$Job.Thread.EndInvoke($Job.Handle)
$Job.Thread.Dispose()
Instead of sleep 10 you could also just wait on a job (-any job):
Get-Job | Wait-Job -Any | Out-Null
When there are no more jobs to kick off, start printing the output. You can also do this within the loop immediately after the above command. The script will receive jobs as they finish instead of waiting until the end.
Get-Job -State Completed | % {
Receive-Job $_ -AutoRemoveJob -Wait
}
So your script would look like this:
$servers = Get-Content "C:\temp\flashfilestore\serverlist.txt"
$scriptBlock = { #DO STUFF }
$MaxThreads = 4
foreach ($server in $servers) {
Start-Job -ScriptBlock $scriptBlock -argumentlist $server
While($(Get-Job -State Running).Count -ge $MaxThreads) {
Get-Job | Wait-Job -Any | Out-Null
}
Get-Job -State Completed | % {
Receive-Job $_ -AutoRemoveJob -Wait
}
}
While ($(Get-Job -State Running).Count -gt 0) {
Get-Job | Wait-Job -Any | Out-Null
}
Get-Job -State Completed | % {
Receive-Job $_ -AutoRemoveJob -Wait
}
Having said all that, I prefer runspaces (similar to Ryans post) or even workflows if you can use them. These are far less resource intensive than starting multiple powershell processes.
Your script looks good, try and add something like
Write-Host ("current count:" + ($(Get-Job -State 'Running').Count) + " on server:" + $server)
after your while loop to work out whether the job count is going down where you wouldn't expect it.
I noticed that every Start-Job command resulted in an additional conhost.exe process in the task manager. Knowing this, I was able to throttle using the following logic, where 5 is my desired number of concurrent threads (so I use 4 in my -gt statement since I am looking for a count greater than):
while((Get-Process conhost -ErrorAction SilentlyContinue).Count -gt 4){Start-Sleep -Seconds 1}

powershell how to implement worker threads

I have a little performance issue in my script, so i would like to implement some sort of worker theads. but so far i have not been able to find a solution..
what im hoping for is something like this:
start a pool of worker threads - these threads takes "commands" from a queue and process them
the main script will write "commands" to the queue as it runs
once complete the main will tell each thread to stop
main will wait for all workers to end before exiting.
does anybody have en idea on how to do this?
You can do this with Powershell workflows.
From Windows PowerShell: What is Windows PowerShell Workflow?
Workflows can also execute things in parallel, if you like. For
example, if you have a set of tasks that can run in any order, with no
interdependencies, then you can have them all run at more or less the
same time
Just do a search on "Powershell workflows" and you will find a good amount of documentation to get you started.
The basic approach to using a job is this:
$task1 = { ls c:\windows\system32 -r *.dll -ea 0 | where LastWriteTime -gt (Get-Date).AddDays(-21) }
$task2 = { ls E:\Symbols -r *.dll | where LastWriteTime -gt (Get-Date).AddDays(-21) }
$task3 = { Invoke-WebRequest -Uri http://blogs.msdn.com/b/mainfeed.aspx?Type=BlogsOnly | % Content }
$job1 = Start-Job $task1; $job2 = Start-Job $task2; $job3 = Start-Job $task3
Wait-Job $job1,$job2,$job3
$job1Data = Receive-Job $job1
$job2Data = Receive-Job $job2
$job3Data = Receive-Job $job3
If you need to have those background jobs waiting in a loop to do work as the main script dictates have a look at this SO answer to see how to use MSMQ to do this.
With some help from the pointers made by Keith hill - i got it working - thanks a bunch...
Here is a snipping of the code that did my prove of concept:
function New-Task([int]$Index,[scriptblock]$ScriptBlock) {
$ps = [Management.Automation.PowerShell]::Create()
$res = New-Object PSObject -Property #{
Index = $Index
Powershell = $ps
StartTime = Get-Date
Busy = $true
Data = $null
async = $null
}
[Void] $ps.AddScript($ScriptBlock)
[Void] $ps.AddParameter("TaskInfo",$Res)
$res.async = $ps.BeginInvoke()
$res
}
$ScriptBlock = {
param([Object]$TaskInfo)
$TaskInfo.Busy = $false
Start-Sleep -Seconds 1
$TaskInfo.Data = "test $($TaskInfo.Data)"
}
$a = New-Task -Index 1 -ScriptBlock $ScriptBlock
$a.Data = "i was here"
Start-Sleep -Seconds 5
$a
And here is the result proving that the data was communicated into the thread and back again:
Data : test i was here
Busy : False
Powershell : System.Management.Automation.PowerShell
Index : 1
StartTime : 11/25/2013 7:37:07 AM
async : System.Management.Automation.PowerShellAsyncResult
as you can see the $a.data now have "test" in front
So thanks a lot...

Can Powershell Run Commands in Parallel?

I have a powershell script to do some batch processing on a bunch of images and I'd like to do some parallel processing. Powershell seems to have some background processing options such as start-job, wait-job, etc, but the only good resource I found for doing parallel work was writing the text of a script out and running those (PowerShell Multithreading)
Ideally, I'd like something akin to parallel foreach in .net 4.
Something pretty seemless like:
foreach-parallel -threads 4 ($file in (Get-ChildItem $dir))
{
.. Do Work
}
Maybe I'd be better off just dropping down to c#...
You can execute parallel jobs in Powershell 2 using Background Jobs. Check out Start-Job and the other job cmdlets.
# Loop through the server list
Get-Content "ServerList.txt" | %{
# Define what each job does
$ScriptBlock = {
param($pipelinePassIn)
Test-Path "\\$pipelinePassIn\c`$\Something"
Start-Sleep 60
}
# Execute the jobs in parallel
Start-Job $ScriptBlock -ArgumentList $_
}
Get-Job
# Wait for it all to complete
While (Get-Job -State "Running")
{
Start-Sleep 10
}
# Getting the information back from the jobs
Get-Job | Receive-Job
The answer from Steve Townsend is correct in theory but not in practice as #likwid pointed out. My revised code takes into account the job-context barrier--nothing crosses that barrier by default! The automatic $_ variable can thus be used in the loop but cannot be used directly within the script block because it is inside a separate context created by the job.
To pass variables from the parent context to the child context, use the -ArgumentList parameter on Start-Job to send it and use param inside the script block to receive it.
cls
# Send in two root directory names, one that exists and one that does not.
# Should then get a "True" and a "False" result out the end.
"temp", "foo" | %{
$ScriptBlock = {
# accept the loop variable across the job-context barrier
param($name)
# Show the loop variable has made it through!
Write-Host "[processing '$name' inside the job]"
# Execute a command
Test-Path "\$name"
# Just wait for a bit...
Start-Sleep 5
}
# Show the loop variable here is correct
Write-Host "processing $_..."
# pass the loop variable across the job-context barrier
Start-Job $ScriptBlock -ArgumentList $_
}
# Wait for all to complete
While (Get-Job -State "Running") { Start-Sleep 2 }
# Display output from all jobs
Get-Job | Receive-Job
# Cleanup
Remove-Job *
(I generally like to provide a reference to the PowerShell documentation as supporting evidence but, alas, my search has been fruitless. If you happen to know where context separation is documented, post a comment here to let me know!)
There's so many answers to this these days:
jobs (or threadjobs in PS 6/7 or the module for PS 5)
start-process
workflows (PS 5 only)
powershell api with another runspace
invoke-command with multiple computers, which can all be localhost (have to be admin)
multiple session (runspace) tabs in the ISE, or remote powershell ISE tabs
Powershell 7 has a foreach-object -parallel as an alternative for #4
Using start-threadjob in powershell 5.1. I wish this worked like I expect, but it doesn't:
# test-netconnection has a miserably long timeout
echo yahoo.com facebook.com |
start-threadjob { test-netconnection $input } | receive-job -wait -auto
WARNING: Name resolution of yahoo.com microsoft.com facebook.com failed
It works this way. Not quite as nice and foreach-object -parallel in powershell 7 but it'll do.
echo yahoo.com facebook.com |
% { $_ | start-threadjob { test-netconnection $input } } |
receive-job -wait -auto | ft -a
ComputerName RemotePort RemoteAddress PingSucceeded PingReplyDetails (RTT) TcpTestS
ucceeded
------------ ---------- ------------- ------------- ---------------------- --------
facebook.com 0 31.13.71.36 True 17 ms False
yahoo.com 0 98.137.11.163 True 97 ms False
Here's workflows with literally a foreach -parallel:
workflow work {
foreach -parallel ($i in 1..3) {
sleep 5
"$i done"
}
}
work
3 done
1 done
2 done
Or a workflow with a parallel block:
function sleepfor($time) { sleep $time; "sleepfor $time done"}
workflow work {
parallel {
sleepfor 3
sleepfor 2
sleepfor 1
}
'hi'
}
work
sleepfor 1 done
sleepfor 2 done
sleepfor 3 done
hi
Here's an api with runspaces example:
$a = [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b = [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c = [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke() # run in background
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).streams.error # check for errors
($a,$b,$c).dispose() # clean
a done
b done
c done
In Powershell 7 you can use ForEach-Object -Parallel
$Message = "Output:"
Get-ChildItem $dir | ForEach-Object -Parallel {
"$using:Message $_"
} -ThrottleLimit 4
http://gallery.technet.microsoft.com/scriptcenter/Invoke-Async-Allows-you-to-83b0c9f0
i created an invoke-async which allows you do run multiple script blocks/cmdlets/functions at the same time. this is great for small jobs (subnet scan or wmi query against 100's of machines) because the overhead for creating a runspace vs the startup time of start-job is pretty drastic. It can be used like so.
with scriptblock,
$sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption}
$servers = Get-Content servers.txt
$rtn = Invoke-Async -Set $server -SetParam system -ScriptBlock $sb
just cmdlet/function
$servers = Get-Content servers.txt
$rtn = Invoke-Async -Set $servers -SetParam computername -Params #{count=1} -Cmdlet Test-Connection -ThreadCount 50
Backgrounds jobs are expensive to setup and are not reusable. PowerShell MVP Oisin Grehan
has a good example of PowerShell multi-threading.
(10/25/2010 site is down, but accessible via the Web Archive).
I'e used adapted Oisin script for use in a data loading routine here:
http://rsdd.codeplex.com/SourceControl/changeset/view/a6cd657ea2be#Invoke-RSDDThreaded.ps1
To complete previous answers, you can also use Wait-Job to wait for all jobs to complete:
For ($i=1; $i -le 3; $i++) {
$ScriptBlock = {
Param (
[string] [Parameter(Mandatory=$true)] $increment
)
Write-Host $increment
}
Start-Job $ScriptBlock -ArgumentList $i
}
Get-Job | Wait-Job | Receive-Job
If you're using latest cross platform powershell (which you should btw) https://github.com/powershell/powershell#get-powershell, you can add single & to run parallel scripts. (Use ; to run sequentially)
In my case I needed to run 2 npm scripts in parallel: npm run hotReload & npm run dev
You can also setup npm to use powershell for its scripts (by default it uses cmd on windows).
Run from project root folder: npm config set script-shell pwsh --userconfig ./.npmrc
and then use single npm script command: npm run start
"start":"npm run hotReload & npm run dev"
This has been answered thoroughly. Just want to post this method i have created based on Powershell-Jobs as a reference.
Jobs are passed on as a list of script-blocks. They can be parameterized.
Output of the jobs is color-coded and prefixed with a job-index (just like in a vs-build-process, as this will be used in a build)
Can be used to startup multiple servers at a time or running build steps in parallel or so..
function Start-Parallel {
param(
[ScriptBlock[]]
[Parameter(Position = 0)]
$ScriptBlock,
[Object[]]
[Alias("arguments")]
$parameters
)
$jobs = $ScriptBlock | ForEach-Object { Start-Job -ScriptBlock $_ -ArgumentList $parameters }
$colors = "Blue", "Red", "Cyan", "Green", "Magenta"
$colorCount = $colors.Length
try {
while (($jobs | Where-Object { $_.State -ieq "running" } | Measure-Object).Count -gt 0) {
$jobs | ForEach-Object { $i = 1 } {
$fgColor = $colors[($i - 1) % $colorCount]
$out = $_ | Receive-Job
$out = $out -split [System.Environment]::NewLine
$out | ForEach-Object {
Write-Host "$i> "-NoNewline -ForegroundColor $fgColor
Write-Host $_
}
$i++
}
}
} finally {
Write-Host "Stopping Parallel Jobs ..." -NoNewline
$jobs | Stop-Job
$jobs | Remove-Job -Force
Write-Host " done."
}
}
sample output:
There is a new built-in solution in PowerShell 7.0 Preview 3.
PowerShell ForEach-Object Parallel Feature
So you could do:
Get-ChildItem $dir | ForEach-Object -Parallel {
.. Do Work
$_ # this will be your file
}-ThrottleLimit 4

Resources