January 29th, 2011

Add a Progress Bar to Your PowerShell Script

 

Summary: Microsoft Scripting Guy Ed Wilson shows you how to add a progress bar to your script.

 

Microsoft Scripting Guy Ed Wilson here. I was sitting in the kitchen reflecting on how much nicer things are nowadays than they used to be even a few years ago. For example, the Scripting Wife and I have a computer in our kitchen. The computer itself is about the size of a small paperback book, and it is hooked up to a nice flat panel monitor. With a wireless keyboard, mouse, and network connection, it is a rather compact installation. Our first kitchen computer was a mini-tower, hooked up to a 15 inch monitor, and it took us several days to cut holes in dry wall, drill holes through wall studs to run category five wire from the upstairs rack to the downstairs kitchen. It was worth the effort because having a computer in the kitchen is super handy around our household. Comparing the amount of work involved, the old installation took several days, and the new installation took only a few minutes.

That same economy of effort also works for scripting progress bars. It is common to display some kind of progress when a script takes a long time to complete. When a user launches the script and nothing happens, one begins to wonder if the script launched correctly. We probably have all had the occasion to start three or four copies of a program that is slow in launching because we clicked and nothing happened. The same principle applies to scripts: if the script will take a long time to run, provide feedback to the user (or even to yourself if you run the script six months from now and you forget that it takes a long time to run.)

Five years ago, I wrote a VBScript script such as the one that follows. It collects a list of all running services, and back then it took a long time to run.

CollectRunningServicesDisplayProgress.vbs

strComputer = “.”

wmiNS = “\root\cimv2”

wmiQuery = “Select name from win32_service where state = ‘running'”

Set stdout = WScript.StdOut

stdout.write “Please wait”

strout = wmiQuery & VbCrLf

Set objWMIService = GetObject(“winmgmts:\\” & strComputer & wmiNS)

Set colItems = objWMIService.ExecQuery(wmiQuery)

For Each objItem in colItems

stdout.Write “.”

strout = strout & objItem.name & VbCrLf

Next

stdout.WriteBlankLines(1)

stdout.write(strOut)

Using the Write-Progress Windows PowerShell cmdlet, I can trim the previous VBScript from 14 lines to six lines of code. One thing that is cool about WMI queries is that the result from Get-WmiObject is a collection. That collection has a count property. Armed with the count property, I can display a progress bar that indicates a percentage of completion of the command. The ProgressDemoWMI.ps1 script is shown here.

ProgressDemoWMI.ps1

$wmiQuery = “Select name from win32_service where state = ‘running'”

$colItems = Get-WmiObject -Query $wmiQuery

For($i = 1; $i -le $colItems.count; $i++)

{ Write-Progress -Activity “Gathering Services” -status “Found Service $i” `

-percentComplete ($i / $colItems.count*100)}

$colItems | Select name

When using the Write-Progress cmdlet, two parameters are required. The first is the activity parameter. The string supplied for this parameter appears on the first line in the progress dialog. The second required parameter is the status parameter. It appears under the Activity line. The dialog that appears in the following image is shown when the ProgressDemoWMI.ps1 script runs from within the Windows PowerShell ISE.

Image of dialog box that appears when PowerShell script is run in the PowerShell ISE

When the ProgressDemoWMI.ps1 script runs inside the Windows PowerShell console, a green bar appears at the top of the console, and yellow o’s track their way across the console window. This is shown in the following image.

Image of progress bar in PowerShell console

Displaying a percentage of completion is a great progress indicator if you know how many work items exist. Unfortunately, many times you have no idea how many items are in the collection. This is especially true when commands are piped, because there is no way to determine how many items need to be streamed as items are coming over the pipeline.

One solution is to store items in a variable, and then iterate over the items collected in the variable. This is illustrated in the FilesProgressDemo.ps1 script.

FilesProgressDemo.ps1

$files = Get-ChildItem -Path c:\fso

For($i = 1; $i -le $files.count; $i++)

{ Write-Progress -Activity “Collecting files” -status “Finding file $i” `

-percentComplete ($i / $files.count*100)}

$files | Select name

As you can tell, the FilesProgressDemo.ps1 script is nearly identical to the earlier script—the difference is collecting the files instead of services.

That is all there is to displaying a progress indicator.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Author

0 comments

Discussion are closed.