Sunday, June 14, 2020

PowerShell: Getting the Current Filename and Line Number

For decades error handling has been simplified when two fundamental pieces of information are included in the error reporting: the line number where the error occurred and the name of the file in which the error occurred. This post demonstrates how to access this information in PowerShell using the $MyInvocation automatic variable. $MyInvocation is defined as follows in About Automatic Variables:


$MyInvocation exposes multiple useful properties but two of them provide the filename and the line number of the code invoking the current function. $MyInvocation's ScriptLineNumber property does not return the current line number but instead returns the line number of the invoking code. $MyInvocation's ScriptName property does not return the current script name but instead returns the script name of the invoking code. This sounds contradictory but it is extremely useful:

To understand this consider the following methods defined in FileAndLines.ps1:

# Returns the line number of the code which invoked this function.
function Get-LineNumber 
{
    return $MyInvocation.ScriptLineNumber
}

# Returns the script name (filename) of the code which invoked this function.
function Get-ScriptName 
{
    return $MyInvocation.ScriptName | Split-Path -Leaf
}

# Creates an error message of the form:
# <filename> (<linenumber>: message
# <filename>: the name of the script invoking this function
# <linenumber>: the line number at which this method was invoked
# $ErrorMessage: parameter passed to function containing the error message/text
function Get-FormattedError()
{
    Param(
        [Parameter(Mandatory=$true)]
        [string] $errorMessage
    )    

    return `
        ($MyInvocation.ScriptName | Split-Path -Leaf) + 
        '(' + ($MyInvocation.ScriptLineNumber).ToString() + '): ' + 
        $errorMessage
}

The above code from FileAndLines.ps1 including line numbers is as follows:


An example of code that invokes this function is found in Outer.ps1 (see below):



The function, Get-LineNumber, is invoked at line 3 so this function returns a 3 for its return value. The function Get ScriptName is invoked from file Outer.ps1 so this function returns the text, Outer.ps1. The function Get-FormattedError returns both invoking filename and line number pre-pended to an error message.

The Get-FormattedError function deliberately invoked $MyInvocation's directly as opposed to invoke the functions Get-ScriptName and GetLineNumber. Obviously Get-ScriptName invoked from Get-FormattedError would return FileAndLines.ps1 because the invoking function, Get-FormattedError, is also in the file, FileAndLines.ps1. The Get-LineNumber function invoked from Get-FormattedError would simply return the line number where it was invoked within Get-FormattedError.

An example of how not to use $MyInvocation is as follows:


The return value of Get-IncorrectFormattedError is not particularly useful as demonstrated by:



The value return from  Get-IncorrectFormattedError with respect to line number is 40 which is not very useful in debugging as the method was invoked from line number 6. Similarly Get-IncorrectFormattedError returns FileAndLines.ps1 for the calling script which is not useful in debugging as it was invoked from Outer.ps1.