public/Stop-ThrowError.ps1
|
<#
.SYNOPSIS Throws a better error than "throw". .DESCRIPTION The PowerShell "throw" keyword doesn't do a good job of providing actionable detail or context: Unable to remove root node. At C:\Scripts\PS5\Remove-Xml.ps1:34 char:37 + ... if($node.ParentNode -eq $null) {throw 'Unable to remove root node.'} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (Unable to remove root node.:String) [], RuntimeException + FullyQualifiedErrorId : Unable to remove root node. It only shows where the "throw" was used in the called script! Using $PSCmdlet.ThrowTerminatingError() does a much better job: C:\Scripts\PS5\Remove-Xml.ps1 : Unable to remove root node Parameter name: SelectXmlInfo At C:\Scripts\Test-Error.ps1:2 char:23 + '<a/>' |Select-Xml / |Remove-Xml.ps1 + ~~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (<a />:SelectXmlInfo) [Remove-Xml.ps1], ArgumentException + FullyQualifiedErrorId : RootRequired,Remove-Xml.ps1 Now you can see where the trouble is in the calling script! However, contructing an exception, then using that to construct an error with the right ID & category & target object, then using that to call ThrowTerminatingError() is pretty inconvenient. This script combines that process into a few simple parameters. .FUNCTIONALITY PowerShell .LINK https://docs.microsoft.com/dotnet/api/system.management.automation.cmdlet.throwterminatingerror .LINK https://docs.microsoft.com/dotnet/api/system.management.automation.errorrecord.-ctor .LINK Get-Variable .LINK New-Object .EXAMPLE Stop-ThrowError 'Unable to remove root node' -Argument SelectXmlInfo C:\Scripts\PS5\Remove-Xml.ps1 : Unable to remove root node Parameter name: SelectXmlInfo At C:\Scripts\Test-Error.ps1:2 char:23 + '<a/>' |Select-Xml / |Remove-Xml.ps1 + ~~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (<a />:SelectXmlInfo) [Remove-Xml.ps1], ArgumentException + FullyQualifiedErrorId : SelectXmlInfo,Remove-Xml.ps1 .EXAMPLE if(Test-Uri.ps1 $u) {[uri]$u} else {Stop-ThrowError 'Bad URL' -Format URL -InputString $u} (Fails for non-uri values of $u.) #> [CmdletBinding()][OutputType([void])] Param( # The type of a Exception class to instantiate as part of the error. [Parameter(ParameterSetName='CatchBlock',Position=0)] [Parameter(ParameterSetName='Detailed',Mandatory=$true,Position=0)][Type] $ExceptionType, # The constructor parameters for the exception class specified by ExceptionTypeName. [Parameter(ParameterSetName='CatchBlock',Position=1)] [Parameter(ParameterSetName='Detailed',Mandatory=$true,Position=1)][object[]] $ExceptionArguments, # The error's category, as an enumeration value. [Parameter(ParameterSetName='Detailed',Mandatory=$true,Position=2)][Management.Automation.ErrorCategory] $ErrorCategory, # The object in context when the error happened. [Parameter(ParameterSetName='Detailed',Mandatory=$true,Position=3)][object] $TargetObject, <# An string unique to the script that identifies the error. By default this will use the line number it is called from. #> [Parameter(ParameterSetName='Detailed',Position=4)][string] $ErrorId = "L$(Get-PSCallStack |Select-Object -First 1 |Select-Object -ExpandProperty ScriptLineNumber)", [Parameter(Position=0,ParameterSetName='Format',Mandatory=$true)] [Parameter(Position=0,ParameterSetName='InvalidArgument',Mandatory=$true)] [Parameter(Position=0,ParameterSetName='InvalidOperation',Mandatory=$true)] [Parameter(Position=0,ParameterSetName='ObjectNotFound',Mandatory=$true)] [Parameter(Position=0,ParameterSetName='ItemNotFound',Mandatory=$true)] [Parameter(Position=0,ParameterSetName='NotImplemented',Mandatory=$true)] [string] $Message, # The data format the string failed to parse as. [Parameter(ParameterSetName='Format',Mandatory=$true)][string] $Format, # The string that failed to parse. [Parameter(ParameterSetName='Format',Mandatory=$true)][string] $InputString, # The parameter name that had a bad value. [Parameter(ParameterSetName='InvalidArgument',Mandatory=$true)][Alias('InvalidArgument')][string] $Argument, # An object containing the state that failed to process. [Parameter(ParameterSetName='InvalidOperation',Mandatory=$true)][Alias('InvalidOperation')] $OperationContext, # An object containing the search detail that failed. [Parameter(ParameterSetName='ItemNotFound',Mandatory=$true)][Alias('ObjectNotFound')] $SearchContext, # Indicates that the exception represents incomplete functionality. [Parameter(ParameterSetName='NotImplemented',Mandatory=$true)][switch] $NotImplemented ) [object[]] $params = switch($PSCmdlet.ParameterSetName) { CatchBlock {(Get-Variable PSItem -ValueOnly -Scope 1),(New-Object $ExceptionType.FullName $ExceptionArguments)} Detailed {(New-Object $ExceptionType.FullName $ExceptionArguments),$ErrorId,$ErrorCategory,$TargetObject} Format {(New-Object FormatException $Message),$Format,'ParserError',$InputString} InvalidArgument { $ScriptParams = Get-Variable PSBoundParameters -ValueOnly -Scope 1 -ErrorAction Ignore $paramValue = if($ScriptParams -and $ScriptParams.ContainsKey($Argument)) {$ScriptParams[$Argument]} (New-Object ArgumentException $Message,$Argument),$Argument,'InvalidArgument',$paramValue } InvalidOperation { (New-Object InvalidOperationException $Message),($OperationContext.GetType().Name), 'InvalidOperation',$OperationContext } ItemNotFound { (New-Object Management.Automation.ItemNotFoundException $Message),($SearchContext.GetType().Name), 'ObjectNotFound',$SearchContext } NotImplemented { (New-Object NotImplementedException $Message),'NotImplementedException','NotImplemented',$null } } [Management.Automation.PSCmdlet] $caller = Get-Variable PSCmdlet -ValueOnly -Scope 1 -ErrorAction Ignore if(!$caller) {$caller = $PSCmdlet} $caller.ThrowTerminatingError((New-Object Management.Automation.ErrorRecord $params)) |