Thursday, 25 June 2015

Displaying Script Progress With a Function

In my experience, if you are writing scripts that will be used by other people they will almost invariably be intimidated by the console, even if they work in IT.  The fact is, the vast majority of people who work on Windows spend most of their time in the GUI and only a tiny fraction of time in any kind of command prompt/console.  Therefore, if you’re writing a script that other people are going to be running, and the script performs multiple actions, I find it very helpful to display in clear detail what stage the script is at and what it’s doing.  To this end, I wrote this function which I use in certain scripts which displays the script progress.  Let’s take a look.

Before we look at the code, let’s go over one of the fundamental rules of script writing and programming in general – don’t repeat yourself.  Let me repeat that because it’s so important – don’t repeat yourself!  What this means is that if you find yourself writing the same line of code more than once in a script, you need to write a function instead and call it.  So let’s say you want to produce an output like this:

image

You might be tempted to write something like this to get the output shown above:

001
002
003
004
005
006
007
008
009
010
011
012
013
014

Write-Host -ForegroundColor Green '**********************************'
Write-Host -ForegroundColor Green 'STAGE 1 - COPYING FILES'
Write-Host -ForegroundColor Green '**********************************'
#Code to copy files goes here

Write-Host -ForegroundColor Green "`n**********************************"
Write-Host -ForegroundColor Green 'STAGE 2 - QUERYING EVENT LOG'
Write-Host -ForegroundColor Green '**********************************'
#Code to query event logs goes here

Write-Host -ForegroundColor Green "`n**********************************"
Write-Host -ForegroundColor Green 'STAGE 3 - SETTING REGISTRY VALUES'
Write-Host -ForegroundColor Green '**********************************'
#Code to set reg values goes here

This approach works but notice how often you’re repeating the same thing.  Also, what happens if your script has 15 stages and you need to add a new stage between 3 and 4?  Now you have to find all the references to stage numbers and manually change them to be in order.  A better approach is to delegate this task to a function.  We need two things to make this happen:

  1. A way to keep track of the stage number
  2. A way to keep track of the stage description, eg. “Copying Files”

Let’s take a look at the function to do this, along with the code needed to product the output previously discussed:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019

function Write-Stage([string]$description,[string]$fgColor = 'Green', [int]$length = 35)
{
     
"`n`n"
      Write-Host -ForegroundColor $fgColor $("*" * $length
)
     
Write-Host -ForegroundColor $fgColor "STAGE $stage - $description"

     
Write-Host -ForegroundColor $fgColor $("*" * $length
)
     
$script:stage++
} 

$script:stage = 1

Write-Stage -description "COPYING FILES"
# Code to copy files goes here

Write-Stage -description "QUERYING EVENT LOG"
# Code to query event log goes here

Write-Stage -description "SETTING REGISTRY VALUES"
# Code to set reg values goes here

Let’s take a look at what is going on here.  Our function accepts three parameters:

  1. Stage description ($description)
  2. Text foreground colour ($fgColor, defaults to white)
  3. Length of the string of asterisks above and below the stage description ($length, defaults to 35)

Line 004 simply repeats the asterisk the number of times specified by the $length variable.  Line 005 echoes the script stage number and description and then line 006 echoes the asterisk the same number of times again.

So, each time we want to display our progress we simply call the function with the appropriate description and it appears in all its glory without endless repetition of asterisks all over your script.

Now you might be wondering – how does the script know what stage number to display?  After all, we never pass it as a parameter to the function.  The answer to that lies in the variable defined on line 010.  Notice that it is not simply defined as $stage but rather as $script:stage.  This gives the variable script-wide scope which means that we can modify it inside the Write-Stage function and it will be available outside of the function.  So we start off by setting it to 1 and then each time the function is called, it is incremented by 1.  With this approach, if you ever need to add extra stages it’s not a big deal because the stage number is not hard-coded anywhere like it was in the first code example I showed you.

You can also modify the appearance of the progress display by passing different values for the length and foreground colour parameters.  So you could do something like this:

001
002
003
004
005
006
007
008

Write-Stage -description "COPYING FILES"
# Code to copy files goes here

Write-Stage -description "QUERYING EVENT LOG" -fgColor Magenta
# Code to query event log goes here

Write-Stage -description "SETTING REGISTRY VALUES"
# Code to set reg values goes here

And get output like this:

image

And that’s it!  Now you have a concise and efficient way to display the progress of a script and hopefully reduce the intimidation factor a non-command-line guru might feel when running one of your masterpieces.