By: Chad Boyd | Updated: 2008-10-04 | Comments | Related: > PowerShell
I ran across Adam Weigert's posting/script that provides a PoSh script that allows try/catch/finally like behavior - very cool. I started using the script myself and found that as much as I loved it, I wanted to add some additional 'features' to what it provides. I was looking for pipeline support (including the ability to throw 'soft' errors in the pipeline that would allow the pipeline to continue processing), some optional verbose debugging/error related information (particularly handy when using the script in a large distributed environment and you need information in your error logs about where the error came from, what system the script failed on, etc.), alias configuration, and further support for allowing friendly dot-sourcing of the script.
So, here's a version adapted from what Adam wrote about, links to his original posting are included in the script as well (see the get-usage function).
Enjoy!
Chad Boyd ~~~ This posting is provided "AS IS" with no warranties, and confers no rights. Use of any included script samples are subject to the terms specified on the MSSQLTips.com Disclaimer and Copyright.
----------------------------------------- CODE BELOW -----------------------------------------
param
(
[ScriptBlock] $Try,
[ScriptBlock] $Catch = { Throw $_ },
[ScriptBlock] $Finally,
[switch] $SoftErrorInPipeline
)
begin {
function Get-Usage {
@"
NAME
Invoke-TryCatch
SYNOPSIS
Simulates Try,Catch,Finally handling for scripts
SYNTAX
Try {<try_block>} -Catch {<catch_block>} [-Finally {<finally_block>}] [-SoftErrorInPipeline]
DETAILED DESCRIPTION
Same functionality as try/catch/finally with .NET - specify the script blocks that make
up the try/catch/finally handling and call into the function using the sytax specified
above and below.
Supports input from the pipeline, dot-sourcing, and invoking directly - if a value is not
specified for the $Try block and we are in the pipeline, the input object will be either
cast directly to the $Try block (if the input object is a ScriptBlock object) or will be
invoked via Invoke-Expression (which supports string-type input to be tried).
Adapted from Adam Weigert's blog entry here:
http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx
PARAMETERS
-command <ScriptBlock>
Script block that makes up the Try section
Required? True
Position? 1
Default value <required>
Accept pipeline? False
Accept wildcards? False
-catch <ScriptBlock>
Script block that makes up the Catch section
Required? True
Position? 2
Default value <required>
Accept pipeline? False
Accept wildcards? False
-finally <ScriptBlock>
Script block that makes up the Finally section
Required? False
Position? 3
Default value {}
Accept pipeline? False
Accept wildcards? False
-SoftErrorInPipeline <switch>
If set and being used in the pipeline, errors caught from the $try block in the
catch will be raised via the write-error cmdlet vs. a direct Throw - this allows
the pipeline to continue processing vs. being stopped as it will be if a Throw
is used.
Required? False
Position? 4
Default value False
Accept pipeline? False
Accept wildcards? False
INPUT TYPE
ScriptBlock,ScriptBlock,[ScriptBlock],[Switch]
RETURN TYPE
Dependent on the script blocks passed
NOTES
Alias created called "Try"
Adapted from Adam Weigert's blog entry here:
http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx
-------------------------- EXAMPLE 1 --------------------------
Try {
echo " ::Do some work..."
echo " ::Try divide by zero: `$(0/0)"
} -Catch {
echo " ::Cannot handle the error (will rethrow): `$_"
#throw `$_
} -Finally {
echo " ::Cleanup resources..."
}
-------------------------- EXAMPLE 2 --------------------------
The following example will pull all ps1 files from the current directory,
pull the content of the file into the pipeline, and pass the content directly
to the try block (default Catch block will simply throw any error).
dir *.ps1 | get-content | try
"@
} # Get-Usage
if (($MyInvocation.InvocationName -ne '.' -and $MyInvocation.InvocationName
-ne '&') -and
($Args[0] -eq
"-?" -or $Args[0] -eq "/?" -or $Args[0] -eq "-help" -or $Args[0] -eq "/help"))
{
$showUsage
= $true;
&Get-Usage;
return;
}
if ($MyInvocation.InvocationName
-ne '.') { Write-Debug "Invoke-TryCatch::BEGIN"; }
$InPipeline = $false;
# Info for reporting error data...
if ($VerbosePreference -ne "SilentlyContinue") {
$myInfoScriptFullPath = $MyInvocation.ScriptName;
$myInfoScriptName = $myInfoScriptFullPath.Substring($myInfoScriptFullPath.LastIndexOf("\")
+ 1);
&{
# we simply ignore errors initializing myInfo
$local:ErrorActionPreference = "SilentlyContinue";
$script:myInfo = @{
"MachineName" = (get-content env:computername);
"MachineDomain" = (get-content env:userdomain);
"MachineDnsDomain" = (get-content env:userdnsdomain);
"UserName" = (get-content env:username);
"UserProfile" = (get-content env:userprofile);
"ScriptName" = $myInfoScriptName;
"ScriptFullPath" = $myInfoScriptFullPath;
"ScriptPath" = $($myInfoScriptFullPath.Substring(0, $myInfoScriptFullPath.Length
- $myInfoScriptName.Length));
}
}
}
# if ($VerbosePreference -ne "SilentlyContinue")
function
Show-Exception {
param (
[System.Management.Automation.ErrorRecord] $errobj
)
# Output some verbose error
information if requested...
if
($VerbosePreference -ne "SilentlyContinue") {
$local:ErrorActionPreference = "Continue"
$myInfo.PipelineProcessing = $InPipeline;
$msg = $myInfo | Sort-Object | Format-Table -autosize | Out-String;
$msg = "`n `n-------VERBOSE EXCEPTION INFORMATION------- `n `n$msg`n `n-------------------------------------------`n
";
# toss
it up...
Write-Error $msg;
}
# Rethrow the error, either continuing
the pipeline or not...
if (($SoftErrorInPipeline)
-and ($InPipeline)) {
# If we are in a pipeline and
$local:ErrorActionPreference = "Continue"
Write-Error $errobj;
} else {
#Throw the exception...
Throw $errobj;
}
}
function Invoke-TryCatch {
param
(
[ScriptBlock] $Try,
[ScriptBlock] $Catch = { Throw $_ },
[ScriptBlock] $Finally,
[switch] $SoftErrorInPipeline
)
begin {} #
Invoke-TryCatch::begin
process
{
# Store
meta-data depending on pipeline vs. invoked processing...
if (($_) -or ($_ -eq 0)) {
$InPipeline = $true;
if ($VerbosePreference -ne "SilentlyContinue") {
$local:ErrorActionPreference = "SilentlyContinue";
$myInfo.PipelineObject = $_;
$myInfo.PipelineObjectType = $_.GetType();
}
}
# if($VerbosePreference -ne "SilentlyContinue")
# If we don't have a $Try value, figure it out now
if (-not $Try) {
if ($_) {
# Try setting/casting the input object to a script block - if it works, we'll
use the pipeline
# object as the $try, otherwise we'll throw a parameter exception...
trap {
Write-Debug "Invoke-TryCatch::Could not convert pipeline object to ScriptBlock
[$_]."
continue;
}
# Get the script block...
if ("$($_.GetType())" -eq "System.Management.Automation.ScriptBlock") {
$Try = $_;
} else {
$Try = {Invoke-Expression $_};
}
} else {
# No pipeline input - so, if we are invoked throw an error, otherwise the $Try
will be nothing...
if ($MyInvocation.InvocationName -eq '&') {
# Invoked, throw an exception
Throw "The parameter -Try is required. [$($MyInvocation.InvocationName)] [$($MyInvocation.MyCommand.Name)]
[$_]";
}
}
}
# If we have a value, show it if debugging, otherwise show a warning
if ($Try) {
Write-Debug "Invoke-TryCatch::Have `$Try value of [$Try] [$_]";
} else {
# Not invoked, show a warning - don't throw an error, as that will stop pipeline
processing if we're in the pipe...
Write-Warning "Invoke-TryCatch::No value could be determined for the `$Try block,
will continue pipeline without processing this object"
}
# Process
the try/catch/finally...
&
{
# Explicitly set our preference for local use...
$local:ErrorActionPreference = "SilentlyContinue";
trap {
trap {
&
{
if ($Finally) {
trap {
Write-Debug "TryCatch::TRAP::Finally Block 2 Error [$_]";
Show-Exception $_;
}
Write-Debug "TryCatch::Finally Block 2 Start";
&$Finally
} # if ($Finally)
}
Write-Debug "TryCatch::TRAP::Catch Block Error [$_]";
Show-Exception $_;
}
Write-Debug "TryCatch::TRAP::Try Block Error [$_]";
if ($Catch) {
# Pipe along the current exception to the catch block to be processed
$_ | & {
Write-Debug "TryCatch::Catch Block Start";
# Store the original exception record...
if ($VerbosePreference -ne "SilentlyContinue") {
&
{
$local:ErrorActionPreference = "SilentlyContinue";
$myInfo.Error = $_;
$myInfo.ErrorInvocationName = $_.InvocationInfo.InvocationName;
$myInfo.ErrorLine = $_.InvocationInfo.Line;
$myInfo.ErrorCommand = $_.InvocationInfo.MyCommand;
$myInfo.ErrorOffsetInLine = $_.InvocationInfo.OffsetInLine;
$myInfo.ErrorPipeLineLength = $_.InvocationInfo.PipeLineLength;
$myInfo.PipeLinePosition = $_.InvocationInfo.PipeLinePosition;
$myInfo.ErrorPositionMessage = $_.InvocationInfo.ErrorPositionMessage;
$myInfo.ErrorScriptLineNumber = $_.InvocationInfo.ScriptLineNumber;
$myInfo.ErrorScriptName = $_.InvocationInfo.ScriptName;
$myInfo.ErrorCategoryInfo = $_.CategoryInfo.GetMessage();
}
} # if ($VerbosePreference -ne "SilentlyContinue")
&$Catch
}
} # if ($Catch)
}
if ($Try) {
Write-Debug "TryCatch::Try Block Start";
&$Try
} # if ($Try)
}
&
{
trap {
Write-Debug "TryCatch::TRAP::Finally Block 1 Error [$_]";
Show-Exception $_ ;
}
if ($Finally) {
Write-Debug "TryCatch::Finally Block 1 Start";
&$Finally
} # if ($Finally)
}
} # Invoke-TryCatch::process
end {} # Invoke-TryCatch::end
}
if (-not (Get-Alias -Name try -ErrorAction SilentlyContinue)) {
Set-Alias -Name try -Value Invoke-TryCatch -Description 'Simulates Try,Catch,Finally
handling for scripts';
}
} # Invoke-TryCatch::begin
process {
# No processing if we are dot-sourced or if
we were just asked for a little help...
if ($showUsage
-or $MyInvocation.InvocationName -eq '.') {
return;
}
# pass processing to the function via pipelining or invoke...
if (($_) -or ($_ -eq 0)) {
$InPipeline
= $true;
if ($SoftErrorInPipeline)
{
$_ |
Invoke-TryCatch $Try $Catch $Finally -SoftErrorInPipeline;
} else {
$_ | Invoke-TryCatch $Try $Catch $Finally;
}
} else { # if ($_)
if ($SoftErrorInPipeline) {
Invoke-TryCatch $Try $Catch $Finally -SoftErrorInPipeline;
} else {
Invoke-TryCatch $Try $Catch $Finally;
}
} # if (($_) -or ($_ -eq 0))
} # Invoke-TryCatch::process
end {
if ((-not $showUsage) -and ($MyInvocation.InvocationName -ne '.')) { Write-Debug
"Invoke-TryCatch::END"; }
} # Invoke-TryCatch::end
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2008-10-04