Maybe I've misunderstood something, but my issue here is that I've not been seeing powershell script stack traces for inner exceptions.
In the end I ended up searching $Global:Error for an exception's Error Eecord object to retrieve the script stack trace.
<#
.SYNOPSIS
Expands all inner exceptions and provides Script Stack Traces where available by mapping Exceptions to ErrorRecords
.NOTES
Aggregate exceptions aren't full supported, and their child exceptions won't be traversed like regular inner exceptions
#>
function Get-InnerErrors ([Parameter(ValueFrompipeline)] $ErrorObject=$Global:Error[0])
{
# Get the first exception object from the input error
$ex = $null
if( $ErrorObject -is [System.Management.Automation.ErrorRecord] ){
$ex = $ErrorObject.Exception
}
elseif( $ErrorObject -is [System.Exception] ){
$ex = $ErrorObject
}
else
{
throw "Unexpected error type for Get-InnerErrors: $($ErrorObject.GetType()):`n $ErrorObject"
}
Write-Debug "Walking inner exceptions from exception"
for ($i = 0; $ex; $i++, ($ex = $ex.InnerException))
{
$ErrorRecord = $null
if( $ex -is [System.Management.Automation.IContainsErrorRecord] ){
Write-Debug "Inner Exception $i : Skipping search for ErrorRecord in `$Global:Error, exception type implements IContainsErrorRecord"
$ErrorRecord = ([System.Management.Automation.IContainsErrorRecord]$ex).ErrorRecord
}
else {
# Find ErrorRecord for exception by mapping exception to ErrorRecrods in $Global:Error
ForEach( $err in $Global:Error ){# Need to use Global scope when referring from a module
if( $err -is [System.Management.Automation.ErrorRecord] ){
if( $err.Exception -eq $ex ){
$ErrorRecord = $err
Write-Debug "Inner Exception $i : Found ErrorRecord in `$Global:Error"
break
}
}
elseif( $err -is [System.Management.Automation.IContainsErrorRecord] ) {
if( $err -eq $ex -or $err.ErrorRecord.Exception -eq $ex ){
$ErrorRecord = $err.ErrorRecord
Write-Debug "Inner Exception $i : Found ErrorRecord in `$Global:Error"
break
}
}
else {
Write-Warning "Unexpected type in `$Global:Error[]. Expected `$Global:Error to always be an ErrorRecrod OR exception implementing IContainsErrorRecord but found type: $($err.GetType())"
}
}
}
if( -not($ErrorRecord) ){
Write-Debug "Inner Exception $i : No ErrorRecord could be found for exception of type: $($ex.GetType())"
}
# Return details as custom object
[PSCustomObject] @{
ExceptionDepth = $i
Message = $ex.Message
ScriptStackTrace = $ErrorRecord.ScriptStackTrace # Note ErrorRecord will be null if exception was not from Powershell
ExceptionType = $ex.GetType().FullName
ExceptionStackTrace = $ex.StackTrace
}
}
}
Example Usage:
function Test-SqlConnection
{
$ConnectionString = "ThisConnectionStringWillFail"
try{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $ConnectionString;
$sqlConnection.Open();
}
catch
{
throw [System.Exception]::new("Sql connection failed with connection string: '$ConnectionString'", $_.Exception)
}
finally
{
if($sqlConnection){
$sqlConnection.Close();
}
}
}
try{
Test-SqlConnection
}
catch {
Get-InnerErrors $_
}
Example output:
ExceptionDepth : 0
Message : Sql connection failed with connection string: 'ThisConnectionStringWillFail'
ScriptStackTrace : at Test-SqlConnection, <No file>: line 11
at <ScriptBlock>, <No file>: line 23
ExceptionType : System.Exception
ExceptionStackTrace :
ExceptionDepth : 1
Message : Exception calling ".ctor" with "1" argument(s): "Format of the initialization string does not conform to specification starting at index 0."
ScriptStackTrace :
ExceptionType : System.Management.Automation.MethodInvocationException
ExceptionStackTrace : at System.Management.Automation.DotNetAdapter.AuxiliaryConstructorInvoke(MethodInformation methodInformation, Object[] arguments, Object[] originalArguments)
at System.Management.Automation.DotNetAdapter.ConstructorInvokeDotNet(Type type, ConstructorInfo[] constructors, Object[] arguments)
at Microsoft.PowerShell.Commands.NewObjectCommand.CallConstructor(Type type, ConstructorInfo[] constructors, Object[] args)
ExceptionDepth : 2
Message : Format of the initialization string does not conform to specification starting at index 0.
ScriptStackTrace :
ExceptionType : System.ArgumentException
ExceptionStackTrace : at System.Data.Common.DbConnectionOptions.GetKeyValuePair(String connectionString, Int32 currentPosition, StringBuilder buffer, Boolean useOdbcRules, String& keyname, String&
keyvalue)
at System.Data.Common.DbConnectionOptions.ParseInternal(Hashtable parsetable, String connectionString, Boolean buildChain, Hashtable synonyms, Boolean firstKey)
at System.Data.Common.DbConnectionOptions..ctor(String connectionString, Hashtable synonyms, Boolean useOdbcRules)
at System.Data.SqlClient.SqlConnectionString..ctor(String connectionString)
at System.Data.SqlClient.SqlConnectionFactory.CreateConnectionOptions(String connectionString, DbConnectionOptions previous)
at System.Data.ProviderBase.DbConnectionFactory.GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, DbConnectionOptions& userConnectionOptions)
at System.Data.SqlClient.SqlConnection.ConnectionString_Set(DbConnectionPoolKey key)
at System.Data.SqlClient.SqlConnection.set_ConnectionString(String value)
at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)