Hey, Scripting Guy! Question

Hey, Scripting Guy! I am writing to you offline because I am stuck in an airplane somewhere over the Atlantic Ocean. It seems like I have been flying for two weeks, and I am really getting tired of looking at the bald spot on the head in front of me. Do you know when we will land?

-- DS

SpacerHey, Scripting Guy! Answer

Hi DS,

I am not sure the pilots themselves really know when you will land. The funny thing is that late plane arrival excuses sound an awful lot like the excuses I used to give to my users when I was a network administrator:

User: My computer is slow.

SG: Must be network congestion.

Passenger: Why are we going to be late?

Pilot: Must be airport congestion.

You get the idea. Anyway, even though we could write your "When Will We Land?" script in VBScript (usingDateDiff and CDate), we have been playing around with Windows PowerShell quite a bit lately, and we came across a cmdlet called Write-Progress that might just be cool enough to incorporate into your script. Here is what such a script might look like:

$arriveTime = get-date -hour 16 -minute 42
$now = get-date
$arrival = ($arriveTime - $now).totalMinutes
$activity = "Arrival Time" 

For($i = 0 ; $i -le [int]$arrival ; $i ++)
      {
        write-progress  -activity "$($arrival-$i) minutes till $activity" `
                        -percentComplete  ($i/[int]$arrival*100) `
                        -status "watched minute $i ..."
       start-sleep -seconds 60
    } # end for

Our script begins by creating a DateTime object that is equal to our estimated arrival time. This command is very similar to using theCDate function from VBScript. By using the -hour parameter and the -minute parameters of the Get-Date cmdlet, we create aDateTime object that represents an arbitrary point in time in the future (and in the case of airplanes, a fictional time object as well). We can see this line of code here:

$arriveTime = get-date -hour 16 -minute 45

Next, we once again use the Get-Date cmdlet, only this time we do not use any parameters. When we do this, we are emulating the functionality of the Date function from VBScript. It simply returns the current system time as a DateTime object. We store this object in a variable we called $now. This line of code is seen here:

$now = get-date

Note: Remember that all variables in Windows PowerShell begin with a dollar sign, which means that in Europe and in other places in the world they are really inexpensive to use these days. I'll be here all week; try the veal.

Now we create another variable called $arrival. Instead of having to use the DateDiff function to subtract two different time objects, we simply subtract the two time objects. Because we are interested only in the total number of minutes, we put parentheses around the two time objects and tell it to give us the totalMinutes property. This is seen here:

$arrival = ($arriveTime - $now).totalMinutes

This next line of code is simple; we just need to store a string into a variable. The string could say anything, but because we are interested in when we will arrive, we thought it best to at least reflect that sentiment as seen here:

$activity = "Arrival Time"

It is now time for a good old-fashioned For/Next loop. However, in Windows PowerShell there is no Next in the For/Next loop. In VBScript, of course, there was a Next in the loop, which is why I call it a For/Next loop. I guess in reality, it is simply a For loop.

In Windows PowerShell the For loop has both smooth parentheses and curly brackets (or braces or crinkly things). The For loop begins with a parenthesis. Inside the parentheses are three sections. The first section tells the loop where to begin. The next section tells the loop how far to go, and the last section tells the loop how to get there. In our For loop (seen here without the script block code), we begin at 0 and go until we are less than or equal to the number of minutes stored in the $arrival variable. Lastly, we will get to that number by incrementing the value of the $i variable by one.

For($i = 0 ; $i -le [int]$arrival ; $i ++)
{
} # end for

Now we come to the Write-Progress cmdlet. There are a few parameters we are going to use: activitypercentComplete, andstatus. These parameter names are rather self explanatory. Activity tells you what the progress indicator is measuring. In our case, we subtract the value of $i from the number of minutes we stored when we computed how long we had until we arrived, and then we print out the string value we stored in the $activity variable. The dollar sign outside the parentheses indicates we want to perform a subexpression (and I used to think a subexpression was something like “down periscope.”) The subexpression is required here to force the expression inside the parentheses to be evaluated first. Otherwise we would get a rather strange looking activity report. This part of the Write-Progress command is seen here:

write-progress  -activity "$($arrival-$i) minutes till $activity"

Next, we want to figure out the percentage of completion of the activity we are timing. In other words, we want to know the percent of elapsed time for our trip. So, to do this, we are back in high school math class to figure out percentages. We take the $i variable that is used to count up to the number of minutes, the $arrival variable that stores the total number of minutes, multiply by 100, and divide the two numbers, and we arrive at a percent of elapsed time (don’t ask me, but it does seem to work right; I actually tested it on a recent flight from Lisbon). This part of the code is seen here:

-percentComplete  ($i/[int]$arrival*100)

Now, for la piece de resistance (or not): the status parameter will tell you how long the script has been marking time. Cool, huh? This line is seen here (the ellipsis is optional):

-status "watched minute $i ..."

To introduce an element of accuracy to the script, as we are counting minutes we need to ensure the loop takes about a minute, so we use the Start-Sleep cmdlet (which is PowerShell for wscript.sleep). The good thing about the Start-Sleep cmdlet is we have both the-seconds parameter and the -milliseconds parameter. We can therefore avoid the higher math requirements of multiplying everything by 1,000 to get seconds. Sweet! Here is that line of code:

start-sleep -seconds 60

The completed For loop is seen here.

For($i = 0 ; $i -le [int]$arrival ; $i ++)
      {
        write-progress  -activity "$($arrival-$i) minutes till $activity" `
                                      -percentComplete  ($i/[int]$arrival*100) `
                                      -status "watched minute $i ..."
       start-sleep -seconds 60
    } # end for

When the script is run, after you configure the appropriate time values in 24-hour time format, on the first line of code the script will print out a status bar such as this one:

Status bar graphic

 

Ed Wilson and Craig Liebendorfer, Scripting Guys