diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs
index 520bbfec1d..afabbace6c 100644
--- a/src/System.Management.Automation/engine/parser/Compiler.cs
+++ b/src/System.Management.Automation/engine/parser/Compiler.cs
@@ -4540,9 +4540,8 @@ namespace System.Management.Automation.Language
else
{
cases.Add(Expression.SwitchCase(catchBody,
- Enumerable.Range(handlerTypeIndex,
- handlerTypeIndex + c.CatchTypes.Count).Select(
- ExpressionCache.Constant)));
+ Enumerable.Range(handlerTypeIndex, c.CatchTypes.Count).Select(
+ ExpressionCache.Constant)));
handlerTypeIndex += c.CatchTypes.Count;
}
}
diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
index de55433050..bf8f86f33a 100644
--- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
+++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs
@@ -1254,66 +1254,173 @@ namespace System.Management.Automation
{
internal class CatchAll { }
- internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte, Type[] types, ExecutionContext context)
+ ///
+ /// Represent a handler search result
+ ///
+ private class HandlerSearchResult
{
- Exception exceptionToPass = rte;
- Exception inner = rte.InnerException;
- int handler = -1;
- if (inner != null)
+ internal HandlerSearchResult()
{
- handler = FindMatchingHandlerByType(inner.GetType(), types);
- exceptionToPass = inner;
+ Handler = -1;
+ Rank = int.MaxValue;
+ ExceptionToPass = null;
+ ErrorRecordToPass = null;
}
- // If no handler was found, or if the handler we found was the catch all handler,
- // then look again, this time using the outer exception. If we found the catch all,
- // there may be a handler that catches outer but not inner. Furthermore, rethrow
- // should throw the original exception from a catchall, not the inner.
- if (handler == -1 || types[handler].Equals(typeof(CatchAll)))
- {
- handler = FindMatchingHandlerByType(rte.GetType(), types);
- exceptionToPass = rte;
- }
-
- // If we still didn't find a specific handler, we'll try unwrapping a few other of our exceptions:
- // ActionPreferenceStopException - to cover -ea stop
- // try { gci nosuchfile -ea stop } catch [System.Management.Automation.ItemNotFoundException] { 'caught' }
- // CmdletInvocationException - to cover cmdlets like Invoke-Expression
- //
- if ((handler == -1 || types[handler].Equals(typeof(CatchAll))))
- {
- var apse = rte as ActionPreferenceStopException;
- if (apse != null)
- {
- exceptionToPass = apse.ErrorRecord.Exception;
- if (exceptionToPass is RuntimeException)
- {
- return FindMatchingHandler(tuple, (RuntimeException)exceptionToPass, types, context);
- }
-
- if (exceptionToPass != null)
- {
- handler = FindMatchingHandlerByType(exceptionToPass.GetType(), types);
- }
- }
- else if (rte is CmdletInvocationException && inner != null)
- {
- exceptionToPass = inner.InnerException;
- if (exceptionToPass != null)
- {
- handler = FindMatchingHandlerByType(exceptionToPass.GetType(), types);
- }
- }
- }
-
- if (handler != -1)
- {
- var errorRecord = new ErrorRecord(rte.ErrorRecord, exceptionToPass);
- tuple.SetAutomaticVariable(AutomaticVariable.Underbar, errorRecord, context);
- }
- return handler;
+ internal int Handler;
+ internal int Rank;
+ internal Exception ExceptionToPass;
+ internal ErrorRecord ErrorRecordToPass;
}
+ ///
+ /// Rank the exception types based on how specific they are.
+ /// Smaller ranking number indicates more specific exception type.
+ ///
+ ///
+ /// The ranking number for each type represent how many other
+ /// types from the array derive from it.
+ /// For example, 0 means no other types in the array derive from
+ /// the corresponding type, while 3 means there are 3 other types
+ /// in the array actually derive from the corresponding type.
+ /// 'CatchAll' is considered to be derived by all exception types.
+ ///
+ private static int[] RankExceptionTypes(Type[] types)
+ {
+ int[] ranks = new int[types.Length];
+ int length = types.Length;
+
+ // If 'CatchAll' is specified, it must be the last catch block.
+ // Handle it specially. This can save a few iterations in the
+ // 'for' loop below, and also avoid some type comparisons.
+ if (types[length - 1].Equals(typeof(CatchAll)))
+ {
+ ranks[length - 1] = length - 1;
+ length = length - 1;
+ }
+
+ // For each type check if it's a sub-class of any types after it.
+ // The ordering of the type array guarantees the more specific type comes first.
+ for (int i = 0; i < length - 1; i++)
+ {
+ for (int j = i + 1; j < length; j++)
+ {
+ if (types[i].IsSubclassOf(types[j]))
+ ranks[j]++;
+ }
+ }
+
+ return ranks;
+ }
+
+ ///
+ /// Search for handler by the exception type and process the found result.
+ ///
+ private static void FindAndProcessHandler(Type[] types, int[] ranks,
+ HandlerSearchResult current,
+ Exception exception,
+ ErrorRecord errorRecord)
+ {
+ Diagnostics.Assert(current != null, "Caller makes sure 'current' is not null.");
+ int handler = FindMatchingHandlerByType(exception.GetType(), types);
+
+ // If no handler was found, return without changing the current result.
+ if (handler == -1) { return; }
+
+ // New handler was found.
+ // - If new-rank is less than current-rank -- meaning the new handler is more specific,
+ // then we update the current result with it.
+ // - If new-rank is more than current-rank -- meaning the new handler is less specific,
+ // then we do NOT change the current result.
+ // - If new-rank is equal to current-rank, we do NOT change the current result UNLESS the
+ // current handler is catch-all. (This is to keep the original behavior -- prefer to use
+ // the later found exception as the exception-to-pass-in if all exceptions result in the
+ // catch-all handler.
+ int rank = ranks[handler];
+ if (rank < current.Rank ||
+ (rank == current.Rank && types[current.Handler].Equals(typeof(CatchAll)))
+ )
+ {
+ current.Handler = handler;
+ current.Rank = rank;
+ current.ExceptionToPass = exception;
+ current.ErrorRecordToPass = errorRecord;
+ }
+ }
+
+ ///
+ /// Find the matching handler for the caught exception
+ ///
+ internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte, Type[] types, ExecutionContext context)
+ {
+ bool continueToSearch = false;
+ int[] ranks = RankExceptionTypes(types);
+ var current = new HandlerSearchResult();
+
+ do {
+ // Always assume no need to repeat the search for another interation
+ continueToSearch = false;
+ // The 'ErrorRecord' of the current RuntimeException would be passed to $_
+ ErrorRecord errorRecordToPass = rte.ErrorRecord;
+
+ Exception inner = rte.InnerException;
+ if (inner != null)
+ {
+ FindAndProcessHandler(types, ranks, current, inner, errorRecordToPass);
+ }
+
+ // If no handler was found (rank = int.MaxValue), or if the handler we found was not
+ // the most specific one, then look again, this time using the outer exception.
+ // If we found a handler, but not one of the most specific ones (rank != 0), there may
+ // be a more specific handler that catches outer but not inner exception.
+ if (current.Rank > 0)
+ {
+ FindAndProcessHandler(types, ranks, current, rte, errorRecordToPass);
+ }
+
+ // If we still didn't find one of the most specific handlers (rank != 0), we'll try unwrapping a few other of our exceptions:
+ // ActionPreferenceStopException - to cover '-ea stop'
+ // try { gci nosuchfile -ea stop } catch [System.Management.Automation.ItemNotFoundException] { 'caught' }
+ // CmdletInvocationException - to cover cmdlets like Invoke-Expression
+ if (current.Rank > 0)
+ {
+ var apse = rte as ActionPreferenceStopException;
+ if (apse != null)
+ {
+ var exceptionToPass = apse.ErrorRecord.Exception;
+
+ // If it's again a RuntimeException, we repeat the search using it
+ rte = exceptionToPass as RuntimeException;
+ if (rte != null)
+ {
+ continueToSearch = true;
+ }
+ else if (exceptionToPass != null)
+ {
+ FindAndProcessHandler(types, ranks, current, exceptionToPass, errorRecordToPass);
+ }
+ }
+ else if (rte is CmdletInvocationException && inner != null)
+ {
+ if (inner.InnerException != null)
+ {
+ FindAndProcessHandler(types, ranks, current, inner.InnerException, errorRecordToPass);
+ }
+ }
+ }
+ } while (continueToSearch);
+
+ if (current.Handler != -1)
+ {
+ var errorRecord = new ErrorRecord(current.ErrorRecordToPass, current.ExceptionToPass);
+ tuple.SetAutomaticVariable(AutomaticVariable.Underbar, errorRecord, context);
+ }
+ return current.Handler;
+ }
+
+ ///
+ /// Find the matching handler by the exception type
+ ///
private static int FindMatchingHandlerByType(Type exceptionType, Type[] types)
{
int i;
diff --git a/test/powershell/Language/Scripting/Trap.Tests.ps1 b/test/powershell/Language/Scripting/Trap.Tests.ps1
new file mode 100644
index 0000000000..6d72b89642
--- /dev/null
+++ b/test/powershell/Language/Scripting/Trap.Tests.ps1
@@ -0,0 +1,28 @@
+
+Describe "Test trap" -Tags "CI" {
+ Context "Trap with flow control" {
+ It "Line after exception should NOT be continued when it's from a nested script block" {
+ $a = . {trap {"trapped"; continue;}; . {"hello"; throw "exception"; "world"}}
+ $a.Length | Should Be 2
+ $a -join "," | Should Be "hello,trapped"
+ }
+
+ It "Line after exception should NOT be continued and both inner and outter traps should be triggered" {
+ $a = . {trap {"outer trap"; continue;}; . {trap {"inner trap"; break;}; "hello"; throw "exception"; "world"}}
+ $a.Length | Should Be 3
+ $a -join "," | Should Be "hello,inner trap,outer trap"
+ }
+
+ It "Line after exception should be invoked after continue" {
+ $a = . {trap {"outer trap"; continue;} "hello"; throw "exception"; "world"}
+ $a.Length | Should Be 3
+ $a -join "," | Should Be "hello,outer trap,world"
+ }
+
+ It "Line after exception should NOT be invoked and inner trap should not be triggered" {
+ $a = . {trap {"outer trap"; continue;}; . {trap [system.Argumentexception] {"inner trap"; continue;}; "hello"; throw "exception"; "world"}}
+ $a.Length | Should Be 2
+ $a -join "," | Should Be "hello,outer trap"
+ }
+ }
+}
diff --git a/test/powershell/Language/Scripting/TryCatch.Tests.ps1 b/test/powershell/Language/Scripting/TryCatch.Tests.ps1
new file mode 100644
index 0000000000..6ddbb4b1dc
--- /dev/null
+++ b/test/powershell/Language/Scripting/TryCatch.Tests.ps1
@@ -0,0 +1,656 @@
+
+#############################################################
+#
+# Test simple parsing, ensure newlines allowed everywhere
+#
+#############################################################
+
+Describe "Test try/catch" -Tags "CI" {
+
+ BeforeAll {
+ function AssertArraysEqual ($result, $expected)
+ {
+ $result.Count | Should Be $expected.Count
+ for ($i = 0; $i -lt $result.Count; $i++) {
+ $result[$i] | Should Be $expected[$i]
+ }
+ }
+ }
+
+ It "Test simple parsing, ensure newlines allowed everywhere" {
+ try
+ {
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ }
+ catch
+ [int]
+ {
+ }
+
+ try
+ {
+ }
+ catch
+ [int]
+ ,
+ [char]
+ {
+ }
+
+ try
+ {
+ }
+ finally
+ {
+ }
+
+ try
+ {
+ }
+ catch
+ {
+ }
+ finally
+ {
+ }
+
+ try
+ {
+ }
+ catch
+ [int]
+ {
+ }
+ finally
+ {
+ }
+
+ try
+ {
+ }
+ catch
+ [int]
+ ,
+ [char]
+ {
+ }
+ finally
+ {
+ }
+
+ $true | Should Be $true # we only verify that there is no parsing error. This line contains a dummy Should to make pester happy.
+ }
+
+ Context "Basic exception handling" {
+ It "Simple throw and catch" {
+ $a = . { try { 1; throw "exception"; "test failed" } catch { 2 } }
+ AssertArraysEqual $a (1, 2)
+ }
+
+ It "Simple try finally" {
+ $a = . { try { 1 } finally { 2 } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Simple try, throw, catch, and finally" {
+ $a = . { try { 1; throw "exception"; "test failed" } catch { 2 } finally { 3 } }
+ AssertArraysEqual $a (1..3)
+ }
+ }
+
+ Context "Mix traps with try/catch" {
+ It "Trap shouldn't catch exception" {
+ $a = . { trap { "test failed" } try { 1; throw "exception"; "test failed" } catch { 2 } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Trap should catch exception" {
+ $a = . { try { 1; throw "exception"; trap { 2; return }; "test failed" } catch { "test failed" } }
+ AssertArraysEqual $a (1,2)
+ }
+ }
+
+ Context "Catch by type" {
+ It "Catch by type #1" {
+ $a = . { try { 1; $a = 0; 1/$a; "test failed" } catch [DivideByZeroException] { 2 } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Catch by type #2" {
+ $a = . { try { 1; $a = 0; 1/$a; "test failed" } catch [DivideByZeroException] { 2 } catch [Exception] { "test failed" } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Catch by type #3" {
+ $a = . { try { 1; $a = 0; 1/$a; "test failed" } catch [DivideByZeroException] { 2 } catch { "test failed" } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Catch by type #4" {
+ $a = . { try { 1; $a = 0; 1/$a; "test failed" } catch [DivideByZeroException],[ArgumentNullException] { 2 } }
+ AssertArraysEqual $a (1,2)
+ }
+
+ It "Catch by type #5" {
+ $a = . { try { 1; throw ([ArgumentNullException]::new("bad")) } catch [DivideByZeroException],[ArgumentNullException] { 2 } }
+ AssertArraysEqual $a (1,2)
+ }
+ }
+
+ Context "Control flow in try [exit not tested and throw tested elsewhere]" {
+ It "break in try" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ } catch {
+ "test failed"
+ } finally {
+ "finally: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally: 1", "finally: 2")
+ }
+
+ It "continue in try" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ } catch {
+ "test failed"
+ } finally {
+ "finally: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally: 1", "finally: 2", 3, "finally: 3")
+ }
+
+ # Disabled - Compiled script has differing (but better) behavior
+ It "return in try" -Pending {
+ $a = . {
+ function foo($i) {
+ try {
+ if ($i -eq 2) {
+ return "return: $i"
+ }
+ $i
+ } catch {
+ "test failed"
+ } finally {
+ "finally: $i"
+ }
+ }
+ foo 1
+ foo 2
+ }
+
+ AssertArraysEqual $a (1, "finally: 1", "finally: 2", "return: 2")
+ }
+
+ It "continue in nested try within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try { #1
+ try { #2
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ } catch {
+ "test failed: catch#2"
+ } finally {
+ "finally#2: $i"
+ }
+ } catch {
+ "test failed: catch#1"
+ } finally {
+ "finally#1: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally#2: 1", "finally#1: 1", "finally#2: 2", "finally#1: 2", 3, "finally#2: 3", "finally#1: 3")
+ }
+
+ It "break in nested try within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try { #1
+ try { #2
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ } catch {
+ "test failed: catch#2"
+ } finally {
+ "finally#2: $i"
+ }
+ } catch {
+ "test failed: catch#1"
+ } finally {
+ "finally#1: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally#2: 1", "finally#1: 1", "finally#2: 2", "finally#1: 2")
+ }
+ }
+
+ Context "Control flow in catch [exit not tested and throw tested elsewhere]" {
+ It "break in catch without loop" {
+ $a = . {
+ try {
+ throw 1
+ } catch {
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ }
+ } finally {
+ "finally"
+ }
+ }
+ AssertArraysEqual $a (1, "finally")
+ }
+
+ It "break in catch within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try {
+ throw 1
+ } catch {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ } finally {
+ "finally $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally 1", "finally 2")
+ }
+
+ It "continue in catch without loop" {
+ $a = . {
+ try {
+ throw 1
+ } catch {
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ }
+ } finally {
+ "finally"
+ }
+ }
+ AssertArraysEqual $a (1, 3, "finally")
+ }
+
+ It "continue in catch within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try {
+ throw 1
+ } catch {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ } finally {
+ "finally $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally 1", "finally 2", 3, "finally 3")
+ }
+
+ It "continue in nested catch within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try { #1
+ try { #2
+ throw 1
+ } catch {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ } finally {
+ "finally#2: $i"
+ }
+ } catch {
+ "test failed: catch#1"
+ } finally {
+ "finally#1: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally#2: 1", "finally#1: 1", "finally#2: 2", "finally#1: 2", 3, "finally#2: 3", "finally#1: 3")
+ }
+
+ It "break in nested catch within foreach loop" {
+ $a = . {
+ foreach ($i in (1..3)) {
+ try { #1
+ try { #2
+ throw 1
+ } catch {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ } finally {
+ "finally#2: $i"
+ }
+ } catch {
+ "test failed: catch#1"
+ } finally {
+ "finally#1: $i"
+ }
+ }
+ }
+ AssertArraysEqual $a (1, "finally#2: 1", "finally#1: 1", "finally#2: 2", "finally#1: 2")
+ }
+
+ # Disabled - Compiled script has differing (but better) behavior
+ It "return in catch without loop" -Pending {
+ $a = . {
+ function foo {
+ try {
+ throw 1
+ } catch {
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ return "returned"
+ }
+ $i
+ }
+ } finally {
+ "finally"
+ }
+ }
+ foo
+ }
+
+ AssertArraysEqual $a (1, "finally", "returned") "return in catch"
+ }
+
+ # Disabled - Compiled script has differing (but better) behavior
+ It "return in catch within foreach loop" -Pending {
+ $a = . {
+ function foo {
+ foreach ($i in (1..3)) {
+ try {
+ throw 1
+ } catch {
+ if ($i -eq 2) {
+ return "returned"
+ }
+ $i
+ } finally {
+ "finally $i"
+ }
+ }
+ }
+ foo
+ }
+
+ AssertArraysEqual $a (1, "finally 1", "finally 2", "returned")
+ }
+ }
+
+ Context "Control flow in finally, normal execution" {
+ It "break in finally normal execution" {
+ $a = . {
+ try {
+ "try"
+ } catch {
+ } finally {
+ "finally"
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ }
+ }
+ }
+
+ AssertArraysEqual $a ("try", "finally", 1)
+ }
+
+ It "continue in finally normal execution" {
+ $a = . {
+ try {
+ "try"
+ } catch {
+ } finally {
+ "finally"
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ }
+ }
+ }
+
+ AssertArraysEqual $a ("try", "finally", 1, 3)
+ }
+ }
+
+ Context "Control flow in finally, abnormal execution" {
+ It "break in finally normal execution" {
+ $a = . {
+ try {
+ "try"
+ throw 1
+ } catch {
+ "catch"
+ } finally {
+ "finally"
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ break
+ }
+ $i
+ }
+ }
+ }
+
+ AssertArraysEqual $a ("try", "catch", "finally", 1)
+ }
+
+ It "continue in finally normal execution" {
+ $a = . {
+ try {
+ "try"
+ throw 1
+ } catch {
+ "catch"
+ } finally {
+ "finally"
+ foreach ($i in (1..3)) {
+ if ($i -eq 2) {
+ continue
+ }
+ $i
+ }
+ }
+ }
+
+ AssertArraysEqual $a ("try", "catch", "finally", 1, 3)
+ }
+ }
+
+ Context "Exception object" {
+ It "ErrorRecord object is set correctly" {
+ $a = . {
+ try {
+ throw 42
+ } catch {
+ $_
+ }
+ }
+
+ [int]$a.ToString() | Should Be 42
+ }
+ }
+
+ It "Nested try/catch" {
+ $a = . {
+ try {
+ "outer try"
+ try {
+ "inner try"
+ $a = 0
+ 1 / $a
+ }
+ catch [OutOfMemoryException] {
+ "test failed"
+ }
+ finally {
+ "inner finally"
+ }
+ }
+ catch [DivideByZeroException] {
+ "caught"
+ }
+ finally {
+ "outer finally"
+ }
+ }
+
+ AssertArraysEqual $a ("outer try", "inner try", "inner finally", "caught", "outer finally")
+ }
+
+ Context "Rethrow" {
+ It "rethrow flow up" {
+ $a = . {
+ try {
+ try {
+ $a = 0
+ 1 / $a
+ } catch {
+ "inner catch"
+ $ex_inner = $_
+ throw
+ }
+ } catch {
+ "outer catch"
+ $ex_outer = $_
+ }
+ }
+
+ AssertArraysEqual $a ("inner catch", "outer catch")
+ ($ex_inner.Exception -eq $ex_outer.Exception) | Should Be $true
+ }
+
+ It "throw; outside catch threw wrong object" {
+ $a = . {
+ function foo {
+ trap [system.management.automation.runtimeexception] {
+ return "test passed"
+ }
+ trap {
+ return "test failed"
+ }
+ throw
+ }
+ try {
+ $a = 0
+ 1 / $a
+ } catch {
+ foo
+ }
+ }
+
+ ($a -eq "test passed") | Should Be $true
+ }
+ }
+
+ Context "Additional try/catch tests by exception types" {
+
+ It "Catch ActionPreferenceStopException" {
+ $exception = $null
+ $a = try {
+ Get-ChildItem TESTDRIVE:\NotExist -ErrorAction Stop
+ } catch [System.Management.Automation.ActionPreferenceStopException] {
+ $exception = $_.Exception.GetType().FullName
+ "ActionPreferenceStopException Caught"
+ }
+ $a | Should Be "ActionPreferenceStopException Caught"
+ ## Many legacy scripts from PSv2 catch 'ActionPreferenceStopException' and then check '$_.Exception' to do the real handling
+ $exception | Should Be "System.Management.Automation.ItemNotFoundException"
+ }
+
+ It "Catch CmdletInvocationException" {
+ $exception = $null
+ $a = try {
+ Invoke-Expression "Get-Command -Name"
+ } catch [System.Management.Automation.CmdletInvocationException] {
+ $exception = $_.Exception.GetType().FullName
+ "CmdletInvocationException Caught"
+ }
+ $a | Should Be "CmdletInvocationException Caught"
+ $exception | Should Be "System.Management.Automation.ParameterBindingException"
+ }
+
+ It "Choose 'ItemNotFoundException' over 'Exception' when searching handler" {
+ $a = try {
+ Get-ChildItem TESTDRIVE:\NotExist -ErrorAction Stop
+ } catch [System.Management.Automation.ItemNotFoundException] {
+ "ItemNotFoundException caught"
+ } catch [System.Exception] {
+ "System.Exception caught"
+ }
+ $a | Should Be "ItemNotFoundException caught"
+ }
+
+ It "Choose 'ItemNotFoundException' over 'RuntimeException' when searching handler" {
+ $a = try {
+ Get-ChildItem TESTDRIVE:\NotExist -ErrorAction Stop
+ } catch [System.Management.Automation.ItemNotFoundException] {
+ "ItemNotFoundException caught"
+ } catch [System.Management.Automation.RuntimeException] {
+ "RuntimeException caught"
+ } catch [System.Exception] {
+ "System.Exception caught"
+ }
+ $a | Should Be "ItemNotFoundException caught"
+ }
+
+ It "Choose 'ItemNotFoundException' over 'RuntimeException' and 'Exception' when throw ItemNotFoundException directly" {
+ $a = try {
+ throw [System.Management.Automation.ItemNotFoundException]::new()
+ } catch [System.Management.Automation.ItemNotFoundException] {
+ "ItemNotFoundException caught"
+ } catch [System.Management.Automation.RuntimeException] {
+ "RuntimeException caught"
+ } catch [System.Exception] {
+ "System.Exception caught"
+ }
+ $a | Should Be "ItemNotFoundException caught"
+ }
+ }
+}
diff --git a/test/powershell/Language/map.json b/test/powershell/Language/map.json
index 96bfdb32e1..ec1bcaefce 100644
--- a/test/powershell/Language/map.json
+++ b/test/powershell/Language/map.json
@@ -61,4 +61,6 @@
"monad/tests/monad/DRT/utscripts/Language/I18n.Test_fallback.psd1":"Scripting/I18n.Test_fallback.psd1",
"monad/tests/monad/DRT/utscripts/Language/scriptHelp.ps1":"Scripting/ScriptHelp.Tests.ps1",
"monad/tests/monad/DRT/utscripts/Language/scriptHelp.xml":"Scripting/ScriptHelp.Tests.xml",
+ "monad/tests/monad/DRT/utscripts/Language/trycatch.ps1":"Scripting/TryCatch.Tests.ps1",
+ "monad/tests/monad/DRT/utscripts/Language/trap.ps1":"Scripting/Trap.Tests.ps1"
}