Nested Progress Bars in PowerShell

I've been working on some scripts lately and just learned about nested progress bars, which are really cool!  In fact, progress bars are a tool that I'm going to use far more often in my scripts, for a few reasons.  First though, let's talk about script output.  In my opinion, there are three basic types of output that a script generates: information as the result of the script, run-time errors, and information about the progress of the script.  We're going to ignore the run-time errors and just talk about the output that is generated by a successful script execution: information about what the script is doing right now, and information that the script has retrieved as a result of its actions.

In general, it's super helpful to be able to store the results of a script in a variable for future manipulation/archival/whatever.  I do this by using syntax like $a = ./myScript.ps1.  That will take whatever output the script generates and store it in the variable $a, which I can then manipulate later.  Hopefully, $a is full of something that I actually care about, such as a list of VMs, or Event Log entries, or whatever, rather than a bunch of lines like "Working on VM1" and "Querying Server X Event Logs".

"But what about write-host?" I imagine that you are asking.  It's true, you can use write-host to output those run-time status updates to the terminal, to show the user of the script what's actually going on without capturing those lines in your output variable, which is the trick that I used for quite a while.  Ain't write-host great?

No.  No, it's not.  Ok, that's not entirely fair; it gets the job done, but it's also messy.  You get this rapidly scrolling window that can be difficult to read and can potentially obscure any error output that occurs, but it does the basic job of letting you know that the script isn't frozen and can even let you know what the script is doing, without polluting your actual output.  But, there's a better way to do that!  Write-Progress.

Write-Progress will do a couple of things for you.  It'll give you a dedicated space at the top of the screen that gets updated based on what your script is doing, rather than just throwing lines at the terminal.  It'll give you a progress bar, so that you can give the user some idea of where in the overall process the script is.  It'll even cook you dinner and take you out to the movies!  Well, maybe not that last one, but it's still pretty cool.  Check out this example script I put together:


That guy will generate a couple of progress bars: a parent and a child.  I love this technique for complex scripts.  For example, I've been working on a script that needs to perform an operation on each datastore on each ESXi host.  So, I've got a parent progress bar showing how far through the list of ESXi hosts I've gone, with a child progress bar showing how where it is on the list of datastores on the current host.  I even update the -status parameter of each bar so that it lists the name of the host and the current datastore on which it's working.  That script is complex (and hopefully forthcoming), so I figured that I'd put together this simpler example to show the technique.

You'll notice that there are two instances of Write-Progress in the script.  The first one is given an -id of 1, so that I can reference it as the -parentID on the child progress bar.  This instructs PowerShell to display my secondary progress bar inset, below the parent (which could be important if you have even more progress bars with intricate relationships).

Another thing to pay attention to is how the -percentComplete parameter is used.  That's what sets the actual bar in the progress bar.  If you leave that parameter off, you'll still get the nicely updating banner at the top of the screen, but for long tasks I like to actually give the bar as well.  You'll notice that I used two different variables to track my progress through each of the loops in the script, $i and $j.  I like to start them at 1 so that the progress bar will basically go from 1-100 instead of going from 0-99, but that's just personal preference.  You'll also notice that I used a formula for that value: ($i++ / $items.count * 100).  That will give me the current value of $i (and then increment it), divide that number by the total number of items in my loop, then multiply that by 100.  That's because -percentComplete is expecting numbers between 0 and 100 for its progress.  I like to increment my variable right there in the write-progress command because I'm lazy and I otherwise forget to do so, but you are free to increment it wherever makes sense for your script.  Speaking of being lazy, I  used the same list of objects for both my main loop and my nested loop; I would guess that you're going to be using different lists of objects in practice (for example, a list of computers for the main loop, then collect the event logs and parse through them looking for a particular set of entries in the nested loop).

Well, that's it for today; I hope you find progress bars to be as helpful as I have!

Comments

Popular posts from this blog

Clone a Standard vSwitch from one ESXi Host to Another

PowerShell Sorting by Multiple Columns

Deleting Orphaned (AKA Zombie) VMDK Files