Try Catch Finally with PowerShell


By:   |   Updated: 2008-10-04   |   Comments   |   Related: More > 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 -----------------------------------------

# See the Get-Usage function contents in the begin{} section for usage/comment details
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



Last Updated: 2008-10-04


get scripts

next tip button



About the author
MSSQLTips author Chad Boyd Chad Boyd is an Architect, Administrator and Developer with technologies such as SQL Server, .NET, and Windows Server.

View all my tips
Related Resources





Comments For This Article





download





Recommended Reading

Setting the PowerShell Execution Policy

How to find a specific text string in a SQL Server Stored Procedure, Function, View or Trigger

Using PowerShell to Work with Directories and Files

Bulk Copy Data from Oracle to SQL Server

Execute SQL Server Stored Procedures from PowerShell








get free sql tips
agree to terms


Learn more about SQL Server tools