Summary: Microsoft Scripting Guy, Ed Wilson, shares what he noticed while grading the 2014 Winter PowerShell Scripting Games entries.
Microsoft Scripting Guy, Ed Wilson, is here. This morning things are a bit chilly around here. I am sipping a nice Assam Blatt tea that I got when I was in Hamburg on our last trip. I added a bit of local honey, a lemon, and a cinnamon stick to the pot. And it is quite refreshing and warming.
This is the second post in a series in which I talk about things I noticed whilst grading submissions for the 2014 Winter Scripting Games. You might want to read 2014 Winter PowerShell Scripting Games Wrap Up #1. I talked about best practices for using aliases and for formatting of Windows PowerShell scripts.
The need for structured error handling
Note There are a couple of really good Hey, Scripting Guy! Blog posts about structured error handling. You may want to start with How Can I Use Try/Catch/Finally in Windows PowerShell? Then read a guest post by Bhargav Shukla called PowerShell Error Handling and Why You Should Care. The two posts together provide a nice foundation for this technique.
Handling errors in production scripts is a good thing. For example, consider the following pseudo-code, which one may need to do when consolidating directories and attempting to clean up storage:
Step 1: Create a folder named folderA.
Step 2: Copy the content of a folder named folderB to folderA.
Step 3: Delete folderB and all its content.
Now, suppose that creating the new folderA fails, as does the copy operation of folderB to folderA. But the last step (Delete FolderB) succeeds. Obviously, this is not the optimal solution. Therefore, some sort of error handling needs to be added. One way to do this is to add a couple of checks to the script. Here I revise the pseudo-code to include two error checks:
Step 1: Create a folder named folderA.
Step 2: Check that folderA created successfully.
Step 3: Copy the content of a folder named folderB to folderA.
Step 4: Check that the content of folderB copy successfully to folderA.
Step 5: Delete folderB and all its content.
By checking for the successful creation of folderA and for the successful copying of folderB to folderA, I can help to avoid disaster. If it is important, I might also want to check that folderB and its content is also successfully deleted.
Using a series of if type statements
One of the most rudimentary types of construction to be created is one I see quite a bit: using if to check on stuff and else to decide what to do. An example of this is shown here:
$folder = "c:\myfolder"
If(-not (Test-Path -Path $folder))
{
Write-host "$folder was not available"
$rtn = Read-host -Prompt "Do you want to create $folder? Y/N"
If ($rtn -match "y")
{ MD $folder }
ELSE {
Write-Host "exiting now because $folder does not exist and you don't want to create it"}}
ELSE {
Write-Host "Writing to $folder now" }
This script first test’s to see if the folder exists. If it does not, it prompts to create and then creates the folder. Otherwise, it states that it will exit, or that it will write to the folder. It works, but is a lot of work—and truthfully, is a bit cumbersome to read.
Using Try/Catch/Finally
A better approach, than a series of if/elseif/else statements is to use Try/Catch/Finally. One thing to keep in mind when using Try/Catch/Finally is that not having a folder in existence is not a terminating error. By default, Windows PowerShell will try to go to the next line of the script.
To make the Catch portion actually catch the non-terminating error, I need to change the $ErrorActionPreference variable from Continue (the default value) to Stop. This will halt the script execution on an error, and enable the Catch portion to catch.
I consider it a best practice to set the $ErrorActionPreference back to Continue when I am done with this portion of the script. In a more complete solution, I would actually query for the current value of $ErrorActionPreference, store it in a variable, and use that to set it back (assuming that it needs to change).
The reason I am catching a [system.Exception] instead of a “Directory not found” or a “File not found” error, is because for some reason, the error that reports back is “Access is denied”—even though the directory does not exist. So a [System.Exception] is any kind of exception, and it will catch “Access is denied,” “File not found,” or “There are too many sun flares today.”
In the image that follows, I run the Copy-Item line of script, and it generates an access denied error.
When I run the entire script, nothing appears. This is because the Catch block catches the exception; and in the Catch script block, I create the folder, and then copy the file to the folder. This is shown in the following image:
The complete Try/Catch/Finally sample is shown here:
# -----------------------------------------------------------------------------
$myfile = "C:\fso\music.csv"
$folder = "c:\myfolder"
Try {
$ErrorActionPreference = "Stop"
Copy-Item -Path $myfile -Destination $folder -Force }
CATCH [System.Exception]{
MD $folder | Out-Null
Copy-Item -Path $myfile -Destination $folder -Force }
FINALLY { $ErrorActionPreference = "Continue" }
2014 Winter Scripting Games Wrap-Up Week will continue tomorrow.
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