mnaoumov
mnaoumov

Reputation: 2227

How to get a proper error line number with $ErrorActionPreference = "Stop"

Consider the following script

#requires -version 2.0

[CmdletBinding()]
param
(
)

$script:ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
function PSScriptRoot { $MyInvocation.ScriptName | Split-Path }

function ThrowFunction($i)
{
    "ThrowFunction $i"
    $someNonExistingVariable
}

@(1, 2, 3) | ForEach-Object -Process { ThrowFunction $_ }

When we run it we get

C:\dev> .\MyScript.ps1
ThrowFunction 1
ForEach-Object : The variable '$someNonExistingVariable' cannot be retrieved because it has not      been set.
At C:\dev\MyScript.ps1:18 char:28
+ @(1, 2, 3) | ForEach-Object <<<<  -Process { ThrowFunction $_ }
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:Token) [ForEach-Object],     RuntimeException
    + FullyQualifiedErrorId :      VariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand

As you see it reports the problem in line #18

But the actual problem is in the line #15

I found that if we change Line 8:

$script:ErrorActionPreference = "Continue"

We get

C:\dev> .\MyScript.ps1
ThrowFunction 1
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At C:\dev\MyScript.ps1:15 char:29
+     $someNonExistingVariable <<<<
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:Token) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

ThrowFunction 2
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At C:\dev\MyScript.ps1:15 char:29
+     $someNonExistingVariable <<<<
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:Token) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

ThrowFunction 3
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At C:\dev\MyScript.ps1:15 char:29
+     $someNonExistingVariable <<<<
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:Token) [],     RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

And you see that now line 15 is reported as expected.

Now the question is how to get the proper line and have “Stop” behavior.

I tried many approaches and none of them worked for me.

I tried

trap { throw $_ }

trap { $_.InvocationInfo }

trap { Get-PSCallStack }

but none of them gets the proper line

Then I tried to switch

$script:ErrorActionPreference = "Continue"

and found that as soon as I add any trap, the wrong line is being reported again.

So I am still looking for a working solution...

Upvotes: 3

Views: 1152

Answers (3)

Mathieu Buisson
Mathieu Buisson

Reputation: 1413

You can you Try {} Catch {} instead :

#requires -version 2.0

[CmdletBinding()]
param
(
)

$script:ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
function PSScriptRoot { $MyInvocation.ScriptName | Split-Path }

function ThrowFunction($i)
{
    "ThrowFunction $i"
    Try {
        $someNonExistingVariable
    }
    Catch { # The variable $_ represents the error that is caught
    Write-Output $_
    }
}

@(1, 2, 3) | ForEach-Object -Process { ThrowFunction $_ }

And the resulting output gives the correct line number :

ThrowFunction 1
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At line:16 char:9
+         $someNonExistingVariable
+         ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:String) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

ThrowFunction 2
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At line:16 char:9
+         $someNonExistingVariable
+         ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:String) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

ThrowFunction 3
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At line:16 char:9
+         $someNonExistingVariable
+         ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:String) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

Upvotes: 0

mnaoumov
mnaoumov

Reputation: 2227

Thanks to @Keith Hill, I found a solution

The magic line is

trap { throw $Error[0] }

This script

#requires -version 2.0

[CmdletBinding()]
param
(
)

$script:ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
function PSScriptRoot { $MyInvocation.ScriptName | Split-Path }

trap { throw $Error[0] }

function ThrowFunction($i)
{
    "ThrowFunction $i"
    $someNonExistingVariable
}

@(1, 2, 3) | ForEach-Object -Process { ThrowFunction $_ }

returns

C:\Dev> .\MyScript.ps1
ThrowFunction 1
The variable '$someNonExistingVariable' cannot be retrieved because it has not been set.
At C:\Dev\MyScript.ps1:17 char:29
+     $someNonExistingVariable <<<<
    + CategoryInfo          : InvalidOperation: (someNonExistingVariable:Token) [], RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

Great!

Upvotes: 5

Keith Hill
Keith Hill

Reputation: 201882

Even though you've set strictmode to Latest, the $someNonExistingVariable does not generate a terminating error. It just writes to the Error stream. By setting ErrorActionPreference to Stop, you now have Foreach-Object converting the non-terminating error into a terminating error. That is why the error indicates line 18 - the Foreach-Object cmdlet.

If you look in the $Error collection, assuming you Clear() it first, you would see two errors. The latest is in $Error[0] - this is the terminating error thrown by Foreach-Object. $Error[1] is the non-terminating error written by the attempt to access an undefined variable. It has the right line number - 15.

You can access the ScriptStackTrace like so:

PS C:\> $error[0].errorRecord.ScriptStackTrace
at ThrowFunction, C:\Users\hillr\ErrorLine.ps1: line 15
at <ScriptBlock>, C:\Users\hillr\ErrorLine.ps1: line 18
at <ScriptBlock>, C:\Users\hillr\ErrorLine.ps1: line 18

Upvotes: 4

Related Questions