# Powershell Errors function Capture-ErrorDetails { param ( [Parameter(Mandatory = $true)] [System.Management.Automation.ErrorRecord]$ErrorRecord ) try { # Skip errors with a specific code if ($ErrorRecord.Exception.Message -match "0x80092004") { return } # Timestamp and severity $Timestamp = Get-Date -Format 'dd/MM/yyyy HH:mm:ss.ff' $Severity = 'ERROR' # Capture file info (if available) $ErrorInFile = if ($ErrorRecord.InvocationInfo.PSCommandPath) { Split-Path -Path $ErrorRecord.InvocationInfo.PSCommandPath -Leaf } else { 'N/A' } # Capture other error details $LineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber $Line = $ErrorRecord.InvocationInfo.Line.Trim() $Category = $ErrorRecord.CategoryInfo.Category $ExceptionType = $ErrorRecord.Exception.GetType().FullName $ExceptionMessage = $ErrorRecord.Exception.Message # Format the error output $FormattedOutput = @" [$Timestamp] [$Severity] [InFile: $ErrorInFile] [LineNumber: $LineNumber] [Line: $Line] [ExceptionMessage: $ExceptionMessage] [Category: $Category] [ExceptionType: $ExceptionType] "@ return $FormattedOutput } catch { # If error info is incomplete or another error occurs while capturing details Write-Host "Error capturing details: $($_.Exception.Message)" return $null } } # Process the global error list $ErrorOutput = @($Global:Error) | ForEach-Object -Process { Capture-ErrorDetails -ErrorRecord $_ } # Display or log the captured error details $ErrorOutput | ForEach-Object { Write-Host $_ } # The three methods you mentioned are used to handle and throw errors in PowerShell, but they behave slightly differently. Here's a breakdown of each: # 1. throw $_ # What it does: This throws the current error stored in $_ within the catch block. It rethrows the exact same error that was caught by the catch, so it can propagate up to higher-level error handlers. # When to use: Use this when you want to rethrow the same non-terminating error that occurred, without converting it to a terminating error. It just passes the error up to any outer error-handling blocks or stops execution if there are no other handlers. # Effect: This will rethrow the current error and halt execution unless there's an outer try block to handle it. catch { Write-Host "Error: $($_.Exception.Message)" throw $_ # Rethrows the same error that was caught } # 2. throw # What it does: Without any arguments, throw generates a new terminating error. If you use throw without an expression or object, it raises a generic error. # When to use: Use this when you want to raise a new terminating error or explicitly stop execution with a custom error message or object. try { # Some code } catch { Write-Host "Error: $($_.Exception.Message)" throw # Raises a generic terminating error } # 3. $PSCmdlet.ThrowTerminatingError($_) # What it does: This is a special method in advanced functions or cmdlets (those using [CmdletBinding()]) that allows you to throw a terminating error. It explicitly marks the error as terminating and provides additional details from the error record. It's the recommended way in advanced functions to throw terminating errors because it works better in pipeline and cmdlet scenarios. # When to use: Use this when you want to throw a terminating error in an advanced function. It provides more control, allowing the error to be handled in cmdlet pipelines and reports detailed error information. # Effect: This will mark the error as terminating, and if your function is part of a pipeline, it will immediately stop pipeline execution. try { # Some code } catch { Write-Host "Error: $($_.Exception.Message)" $PSCmdlet.ThrowTerminatingError($_) # Throws the error as a terminating error } # Key Differences: # throw $_: Rethrows the caught error in its original form, allowing for more flexible error handling but may not stop pipeline execution unless the error was originally terminating. # throw: Can raise a new error (or rethrow the current one if an object is passed). If nothing is passed, it raises a generic error, stopping execution. # $PSCmdlet.ThrowTerminatingError($_): Explicitly throws a terminating error in advanced cmdlets, designed for use in functions marked with [CmdletBinding()] and in pipeline scenarios, where it immediately halts processing. # If you are writing advanced functions and need to guarantee that an error stops execution or the pipeline, $PSCmdlet.ThrowTerminatingError($_) is the best approach. $Error $Error | Group-Object | Sort-Object -Property Count -Descending | Format-Table -Property Count, Name -AutoSize $Error[0] | Format-List * $Error[0] | Format-Table * $Error[0] | Format-List * -Force $Error[0].Exception $Error[0].Exception | Format-List * -Force $Error[0].Exception.InnerException | Format-List * -Force $Error[0].ScriptStackTrace #for locations in PowerShell functions/scripts $Error[0].Exception.StackTrace #for locations in compiled cmdlets/dlls # Get exception name $Error.Exception.GetType().FullName (Get-Error).Exception.Type try { Start-Something -Path $path -ErrorAction Stop } catch [System.IO.DirectoryNotFoundException], [System.IO.FileNotFoundException] { Write-Output "The path or file was not found: [$path]" } catch [System.IO.IOException] { Write-Output "IO error with the file: [$path]" } try { Start-Something -Path $path } catch [System.IO.FileNotFoundException] { Write-Output "Could not find $path" } catch [System.IO.IOException] { Write-Output "IO error with the file: $path" } $ErrorOutput = @($Global:Error) | ForEach-Object -Process { # Attempt to access properties, and only format if they exist try { $Timestamp = Get-Date -Format 'dd-MM-yyyy HH:mm:ss.ffff' $Severity = 'ERROR' $ErrorInFile = if ($_.InvocationInfo.PSCommandPath) { Split-Path -Path $_.InvocationInfo.PSCommandPath -Leaf } $LineNumber = $_.InvocationInfo.ScriptLineNumber $Line = $_.InvocationInfo.Line.Trim() $CommandName = $_.InvocationInfo.MyCommand.Name $Category = $_.CategoryInfo.Category $ExceptionType = $_.Exception.GetType().FullName $ExceptionMessage = $_.Exception.Message $StackTrace = $_.Exception.StackTrace $InnerExceptionMessage = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } $FullyQualifiedErrorId = $_.FullyQualifiedErrorId $FormattedOutput = '[{0}] [{1}] [{2}] [{3}] [{4}] [{5}] [{6}] [{7}] [{8}] [{9}] [{10}]' -f $Timestamp, $Severity, $ErrorInFile, $LineNumber, $Line, $CommandName, $Category, $ExceptionType, $ExceptionMessage, $StackTrace, $InnerExceptionMessage } catch { # Ignore errors if properties don't exist $null } $FormattedOutput } $FormattedOutput = $ErrorOutput -join "`n" Write-Host $FormattedOutput # https://gist.github.com/techthoughts2/0945276362aeebb4926a11b848844926 function Reset-Errors { $Global:Error.Clear() $psISE.Options.ErrorForegroundColor = '#FFFF0000' $Global:ErrorView = 'NormalView' } Reset-Errors #generate an error function Show-Error { Get-Item c:\doesnotexist.txt } Show-Error #all errors are stored in: $Error #lets make it less overwhelming (and prioritized and actionable) $Error | Group-Object | Sort-Object -Property Count -Descending | Format-Table -Property Count, Name -AutoSize #what about speific error details? $Error[0] | Format-List * #PS is dumb somtimes and this doesn't provide the date we are looking for. #use the force, luke! $Error[0] | Format-List * -Force #when the top level information inst' clear, go deeper $Error[0].Exception $Error[0].Exception | Format-List * -Force $Error[0].Exception.InnerException | Format-List * -Force #leverage the stack traces $Error[0].ScriptStackTrace #for locations in PowerShell functions/scripts $Error[0].Exception.StackTrace #for locations in compiled cmdlets/dlls #don't forget to clean up behind yourself as you deal with errors $Error.Remove($Error[0]) #remove a specific error $Error.RemoveAt(0) #remove by index $Error.RemoveRange(0, 10) #remove by index + count $Error.Clear() #clear the error collection #------------------------------------------- #consider ussing ThrowTerminating error 1 / 0 Write-Host 'Will this run?' -ForegroundColor Cyan function Test-1 { [CmdletBinding()] param() try { 1 / 0; Write-Host 'Will this run?' -ForegroundColor Cyan } catch { $PSCmdlet.ThrowTerminatingError($_) } } function Test-2 { [CmdletBinding()] param() try { 1 / 0; Write-Host 'Will this run?' -ForegroundColor Cyan } catch { throw } } #compare Test-1 (clean error identifying the source line where it happened in the calling script Test-1 #to Test-2 (error showing internals that don't identify the source line at all) Test-2 #------------------------------------------------------------------------------- #crafting a custom ErrorRecord for the purposes of properly mocking failures Mock Invoke-RestMethod { [System.Exception]$exception = "The remote server returned an error: (400) Bad Request." [System.String]$errorId = 'BadRequest' [Management.Automation.ErrorCategory]$errorCategory = [Management.Automation.ErrorCategory]::InvalidOperation [System.Object]$target = 'Whatevs' $errorRecord = New-Object Management.Automation.ErrorRecord ($exception, $errorID, $errorCategory, $target) [System.Management.Automation.ErrorDetails]$errorDetails = '{"message":"Database could not be reached"}' $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } #------------------------------------------------------------------------------- $formatstring = "{0} : {1}`n{2}`n" + " + CategoryInfo : {3}`n" + " + FullyQualifiedErrorId : {4}`n" $fields = $_.InvocationInfo.MyCommand.Name, $_.ErrorDetails.Message, $_.InvocationInfo.PositionMessage, $_.CategoryInfo.ToString(), $_.FullyQualifiedErrorId $formatstring -f $fields #-------------------------------------------------------------------------------