-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathStop-ThrowError.ps1
133 lines (115 loc) · 5.98 KB
/
Stop-ThrowError.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<#
.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.ps1 '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.ps1 'Bad URL' -Format URL -InputString $u}
(Fails for non-uri values of $u.)
#>
#Requires -Version 3
[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))