Showing posts with label Errors. Show all posts
Showing posts with label Errors. Show all posts

Wednesday, July 23, 2008

Handling Errors in PowerShell

Powershell gives you a few ways to handle errors in your scripts.  The most powerful tool in your arsenal is the exception trap, I'm sure, but it's a bit complicated and I can't claim to be a master of PowerShell exceptions, but I'll go over that too.  We'll start off with something you'll use a lot more though, even though you may not know it.


PowerShell has a number of Preference Variables that you can use to determine the way it behaves.  If you have the v2 CTP version installed then you can run 'help about_Preference_Variables' to see the list, but for the rest of us use the link above.  $ErrorActionPreference sets the way PowerShell will respond when hitting a non-terminating error.  This won't affect errors that terminate a script.  

The allowable values for $ErrorActionPreference are 'Continue' (default), 'SilentlyContinue', 'Inquire', and 'Stop'.

Which errors are terminating and which aren't?  Well there's no definitive list, but take this example to show you how it works:

PS HKLM:\> dir


   Hive: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE

SKC  VC Name                           Property
---  -- ----                           --------
  7   4 COMPONENTS                     {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime, LastScavengeCookie}
  4   0 HARDWARE                       {}
  1   0 SAM                            {}
Get-ChildItem : Requested registry access is not allowed.
At line:1 char:3
+ dir <<<<
 17   0 SOFTWARE                       {}
  9   0 SYSTEM                         {}

______________________________________________________

PS HKLM:\> $ErrorActionPreference = 'SilentlyContinue'
PS HKLM:\> dir


   Hive: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE

SKC  VC Name                           Property
---  -- ----                           --------
  7   4 COMPONENTS                     {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime, LastScavengeCookie}
  4   0 HARDWARE                       {}
  1   0 SAM                            {}
 17   0 SOFTWARE                       {}
  9   0 SYSTEM                         {}

______________________________________________________

PS HKLM:\> $ErrorActionPreference = 'Inquire'
PS HKLM:\> dir


   Hive: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE

SKC  VC Name                           Property
---  -- ----                           --------
  7   4 COMPONENTS                     {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime, LastScavengeCookie}
  4   0 HARDWARE                       {}
  1   0 SAM                            {}

Confirm
Requested registry access is not allowed.
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): y
Get-ChildItem : Requested registry access is not allowed.
At line:1 char:3
+ dir <<<<
 17   0 SOFTWARE                       {}
  9   0 SYSTEM                         {}


As you can see, the default is to print an error and continue, while 'SilentlyContinue' will suppress error messages, which is similar to the old 'On Error Resume Next' in VBScript.

One nice side-effect of having this Preference Variable is that you can change the behavior for just one code block or function within your script.  Once the variable inside of the block goes out of scope, $ErrorActionPreference reverts to the original value.

PS HKLM:\> function suppress_errors ([string]$path) {
>> $ErrorActionPreference = 'SilentlyContinue'
>>  echo "`$ErrorActionPreference = $ErrorActionPreference"
>>}
>>
PS HKLM:\> echo "`$ErrorActionPreference = $ErrorActionPreference"
$ErrorActionPreference = Continue
PS HKLM:\> suppress_errors
$ErrorActionPreference = SilentlyContinue
PS HKLM:\> echo "`$ErrorActionPreference = $ErrorActionPreference"
$ErrorActionPreference = Continue


NOTE: You can also use the -ErrorAction (or -ea) switch that is ubiquitous across cmdlets with the same values if you want to change PowerShell's behavior for a particular command.

Sunday, July 6, 2008

Importing Scripts as Libraries, Part Deux

In this post I showed how using the dotsource operator lets us run a script and import all of its named objects from the script scope into the global scope. The effect is as if instead of calling a script you just typed its contents at the prompt.

This set off a lightbulb in my head, and gave me the solution to something I'd been pondering for a while: how to import scripts as libraries (modules, etc, whatever we're calling them these days). You see, if I come up with a really neat function I don't want to have to cut and paste it into every script that uses it, and if I come up with a lot of content, I'd like to be able to share them with people.

This is what I came up with, and it incorporates a few new tricks I picked up along the way to trying to solve this problem. The desired result: I can type in 'import-script scriptname.ps1' and it is automatically imported without having to know the full path to the file.

Some highlights:
  • Test-Path is a nice cmdlet for checking the existence of a path on any PSDrive, so that means any Enviornment variable, registry key, file object, etc.
  • Exceptions work, but are really hard to get straight. Check out this link for some details.
  • The (, (command)) syntax is how I'm forcing the variable to be an array. I might get rid of it later, I think now that I'm using boolean comparison instead of looking for the length of the array to check if any files were found.
  • In most examples people use Write-Error instead of echo to print error messages, but I wanted to have the error show up clearly without all of the red words and extra garbage.
  • Note that I'm searching everything under the folder pointed to by the PSPATH environment variable. I point mine to C:\PSLibs by default.

function Import-Script ([string]$script) {
  $local:ReportErrorShowSource = 0

  # Clean up the Exception messages a bit
  trap [Exception] {
    $errmsg = "`n`tError importing '$script': "
    $errmsg += ($_.Exception.Message + "`n")
echo $errmsg
break
  }

  # Check PSPATH
  if (-not (Test-Path "Env:pspath")) {
    throw "PSPath environment variable not set."
  }elseif (-not (Test-Path $env:pspath)) {
    throw "PSPATH environment variable points to invalid path." 
  }

  $files = (, (dir $env:pspath $script -Recurse))

  # Make sure we find a single matching file
  if ($files.Length -gt 1) {
    throw ([string]$files.Length + " files of name '$script' found in PSPATH.")
  }elseif (-not $files) {
    throw "No files named '$script' found in PSPATH."
  }

  # Do the needful
  . $files[0].FullName
}