[PowerShell] Write-Progress can slow down your scripts – Performance Tip
- Posted by Cláudio Silva
- On December 19, 2016
- 2 Comments
- performance, PowerShell, Write-Progress
As SQL Server Data Architect who works everyday with PowerShell, I’m used to writing my own scripts and every time I need to go through a list of items I like to have a progress bar which tells me where I am and how far I am from the end of the execution.
We have some scenarios where the script execution time is not linear. For example if we need to connect to multiple instances the time to connect could vary for each instance. Or, imagine you want to backup a bunch of databases with quite different sizes. I’d like to know if I’m on the 3rd of 10th database or if I’m still on the first one because the backup stills executing.
The Write-Progress cmdlet gives to us this feedback.
But…
you heard that “…Write-Progress cmdlet slows down the script execution…”. Is that true?
Yes it is. Every block of code, however small it is will take time to execute, nevertheless we need to have a balance between the execution time and the feedback that we want to give/receive.
That is why today I bring a performance tip for when you use the Write-Progress PowerShell cmdlet.
What does Write-Progress cmdlet do?
From the msdn Write-Progress help page
The Write-Progress cmdlet displays a progress bar in a Windows PowerShell command window that depicts the status of a running command or script. You can select the indicators that the bar reflects and the text that appears above and below the progress bar.
Example – Typical usage of Write-Progress cmdlet
$count = 100000
for ($i=0; $i -le $count; $i++)
{
Write-Progress -Activity Testing -Status "One by one" -PercentComplete (($i/$count)*100)
}
In this example, we iterate 100000 times and each time, we update our progress bar.
Measure script execution time
If you want to measure your script time execution you can use the Measure-Command cmdlet.
The syntax is:
Measure-Command {<code>}
For our script the command will be:
Measure-Command {
$count = 100000
for ($i=0; $i -le $count; $i++)
{
Write-Progress -Activity Testing -Status "One by one" -PercentComplete (($i/$count)*100)
}
}
We will know exactly how much time the script took to run. For this example, we want the TotalMilliseconds value but if you need you can go down on ticks precision.
Ok, so lets see our results
The original script takes about 15715 ms (this is the average of 5 executions on my laptop).
At first this does not seems to be a big problem, right? Lets see how it performs using a slightly different approach.
Introducing the improvements
$count = 100000
$interval = $count * 0.1
for ($i=0; $i -le $count; $i++)
{
if ($i % $interval -eq 0)
{
Write-Progress -Activity Testing -Status something -PercentComplete (($i/$count)*100)
}
}
We have introduced a new variable $interval on line 2 which keeps the value of 10% of the $count variable. Then we introduce the if condition (if ($i % $interval -eq 0)) on line 5, where we say that our progress bar will only be updated every 10%.
Using this approach the script takes about 644 ms (again, the average of 5 executions on my laptop). Uh uh! We just increased the performance of our script 24 times!
Look to the following gifs so you can see the difference!

Slow execution – One by one

Fast execution – 10% evolution
Why do we have such a big difference?
The short answer is, with the second approach we update the progress bar less often.
Lets do some math.
Our first script update the progress bar every iteration which means a total of 100000 updates. On the other hand, our second script only updates the progress bar 10 times (10% * 10 times = 100%). This translate to 1000 times less updates for this example!
Conclusion
We saw a 24 times performance gain (from 15715 ms to 644 ms). This must encourage you to use the Write-Progress cmdlet every time you want to give feedback to your user.
You just need to remember to not update the progress bar too often.
Notes
If you want to know who to measure the average of X execution times as I did, you can use the following code:
((1..5) | Foreach-Object {
$(Measure-Command {
$count = 100000
for ($i=0; $i -le $count; $i++)
{
Write-Progress -Activity Testing -Status "One by one" -PercentComplete (($i/$count)*100)
}
}
)
} | Measure-Object -Property TotalMilliseconds).Average
The “(1..5)” says that we will execute a loop of 5 iterations. Each iteration will execute the Measure-Command with the code that we have define and pipe the results to Measure-Object cmdlet where we will get the Average of TotalMilliseconds value for our 5 executions.


2 Comments