[ooooooooo ] Baby

Rocking the Progress Bar in PowerShell

Windows PowerShell is our friend. When the job is too small to justify doing the File->New->Project dance, but too big to sweat through by mouse alone, the answer is a script written in PowerShell. Not only does it enable us to tap in to the familiarity and baked-in goodness of .NET, but there is also a rich library of pre-built cmdlets that keep you from wasting your valuable blog-reading time re-inventing the wheel. Not the least of these is the Write-Progress cmdlet.

Hurry Up And Wait

In a previous incarnation as a c programmer (in a UNIX environment (in the 21st Century)), I wrote a function that drew a rudimentary progress bar at the command line and kept the user aprised of happenings within the code that called it. While it did strengthen my character art skills in a maturing platform, it would have been nice to be able instead to focus that time on reviewing the business logic executed by the task that I was measuring.

When we are scripting rather than writing a compiled application, it is likely because we just need something to get done, and it is no time for such distracting programmatic escapades. The PowerShell guys at Microsoft know this, and because they love us like we are their own children, they have bestowed upon us System.Management.Automation.Cmdlet.Write-Progress:

Write-Progress `
    -activity "Getting Things Done" `
    -status "Doing Stuff" `
    -percentComplete $calculatedProgress;

In a single line of code, this cmdlet will display an area across the top of the PowerShell command window in which a text-based progress bar (made from ‘o’ characters, oddly enough) is shown. It also includes options to add descriptive status text to further inform the executor of what’s going on:

PowerShell Progress Bar (Single)

Here is a full working script to get your PowerShell progress motor warmed up:

##
##  Calls the Get-SomethingDone function in a loop
##
function Go
{
    param
    (
        # The number of times to do something important (defaults to 1)
        [int] $NumberOfTimes = 1
    )

    # Set up our loop
    for ($i = 0; $i -lt $NumberOfTimes; $i++)
    {
        # TCOB
        Get-SomethingDone;

        # Show the progress bar
        Set-Progress "Doing Something Important" $i $NumberOfTimes
    }
}

##
##  A helper function that calls Write-Progress
##
function Set-Progress
{
    param
    (
        [string] $activity = "Unknown Activity",
        [int] $step = 0,
        [int] $steps = 1
    )

    # Calculate progress and format status string
    [int] $progress = ($step / $steps) * 100;
    [string] $status = [String]::Format("Working... ({0}%)", $progress);

    # Show the progress bar <-- HERE'S THE IMPORTANT PART!!!
    Write-Progress `
        -activity $activity `
        -status $status `
        -percentComplete ($progress);
}

##
##  Gets something important done
##
function Get-SomethingDone
{
    #do something important here...
    Start-Sleep 1;
}

# and... go!
. Go 100;

Upgrade: Multiple Progress Bars

In today’s mocha latte on-the-go lifestyle, our scripting adventures sometimes lead us into multi-tasking territory, and our users want to see a finer-grained view of just how fast they have to wait. Again, the good people in Redmond know this (much in the way that Santa Claus knows whether you’ve been commenting your code), and have graciously provided with the -Id parameter.

Using this parameter, we can set up multiple simultaneous progress bars, and update them independently. First, let’s update our helper function to take a new parameter to indicate the progress bar the caller has in mind:

param
(
    [int] $id = 1,  # <-- Like this
    [string] $activity = "Unknown Activity",
    [int] $step = 0,
    [int] $steps = 1
)

Then, we add the -Id parameter to the Write-Progress call:

# Like this
Write-Progress `
    -Id $id `
    -activity $activity `
    -status $status `
    -percentComplete $progress;

Now we can nest a new loop inside our original loop and measure progess at two different levels:

# Our original loop
for ($i = 0; $i -lt $NumberOfTimes; $i++)
{
    # A new inner loop
    [int] $innerNumberOfTimes = 3;
    for ($j = 0; $j -lt $innerNumberOfTimes; $j++)
    {
        # TCOB
        Get-SomethingDone;

        # Show the progress bar
        Set-Progress 2 "Task Progress" ($j + 1) $innerNumberOfTimes;
    }

    # Show the progress bar
    Set-Progress 1 "Overall Progress" ($i + 1) $NumberOfTimes;
}

And by gum, we get a second progress bar:

PowerShell Progress Bar (Multiple)

[ooooooooooooooooooooooooooo]

Now please pay closer attention to the crippling effect that your script is having on the production database–PowerShell already did the junior programmer busy work for you.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s