Reputation: 2227
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
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
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
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