@beatcracker if everything works as it should after the non
terminating exception the execution should proceed with the next
instruction which is write-output "caught exception" and soon after
the command "exit" which should exit the script.
– Davide Talesco
I agree, I wasn't clear on this. What I meant is that it always worked that way - one had to wrap everything inside the catch block into another try/catch or it would just silently exit the catch scriptblock on non-terminating errors and continue to execute whatever code comes next.
Not a real answer below, just a braindump to in hope, that someone more skilled can make a sense out of it.
While I can't properly understand why it doing this, I think I localized it to this code in
System.Management.Automation assembly (
public class ScriptBlock):
internal void InvokeWithPipe(bool useLocalScope, bool writeErrors, object dollarUnder, object input, object scriptThis, Pipe outputPipe, ref ArrayList resultList, params object[] args)
{
ExecutionContext contextFromTLS = this.GetContextFromTLS();
if (contextFromTLS.CurrentPipelineStopping)
{
throw new PipelineStoppedException();
}
ParseTreeNode codeToInvoke = this.GetCodeToInvoke();
if (codeToInvoke != null)
{
InvocationInfo invocationInfo = new InvocationInfo(null, codeToInvoke.NodeToken, contextFromTLS);
contextFromTLS.Debugger.PushMethodCall(invocationInfo, this);
bool flag = false;
ScriptInvocationContext oldScriptContext = null;
Pipe shellFunctionErrorOutputPipe = null;
CommandOrigin scopeOrigin = contextFromTLS.EngineSessionState.currentScope.ScopeOrigin;
Exception exception = null;
SessionStateInternal engineSessionState = contextFromTLS.EngineSessionState;
ActivationRecord oldActivationRecord = null;
try
{
ScriptInvocationContext scriptContext = new ScriptInvocationContext(useLocalScope, scriptThis, dollarUnder, input, args);
this.EnterScope(contextFromTLS, scriptContext, out oldScriptContext, out oldActivationRecord);
shellFunctionErrorOutputPipe = contextFromTLS.ShellFunctionErrorOutputPipe;
if (!writeErrors)
{
contextFromTLS.ShellFunctionErrorOutputPipe = null;
}
contextFromTLS.EngineSessionState.currentScope.ScopeOrigin = CommandOrigin.Internal;
if (!string.IsNullOrEmpty(this.File))
{
contextFromTLS.Debugger.PushRunning(this.File, this, false);
flag = true;
}
codeToInvoke.Execute(null, outputPipe, ref resultList, contextFromTLS);
}
catch (ReturnException exception2)
{
if (!this._isScriptBlockForExceptionHandler)
{
ParseTreeNode.AppendResult(contextFromTLS, exception2.Argument, null, ref resultList);
}
else
{
exception = exception2;
}
}
finally
{
if (flag)
{
contextFromTLS.Debugger.PopRunning();
}
contextFromTLS.ShellFunctionErrorOutputPipe = shellFunctionErrorOutputPipe;
contextFromTLS.EngineSessionState.currentScope.ScopeOrigin = scopeOrigin;
try
{
this.LeaveScope(contextFromTLS, oldScriptContext, engineSessionState, oldActivationRecord);
}
finally
{
contextFromTLS.Debugger.PopMethodCall();
}
}
if (exception != null)
{
throw exception;
}
}
}
Scriptblocks for catch are created with _isScriptBlockForExceptionHandler set to true by internal sealed class ExceptionHandlerNode's method Invoke which calls CreateExceptionHandler from aforementioned public class ScriptBlock:
internal static ScriptBlock CreateExceptionHandler(ParseTreeNode body, Token token, int pipelineSlots, int variableSlots)
{
return new ScriptBlock(token, null, null, null, null, body, null, false, null, null, null, pipelineSlots, variableSlots) { _isScriptBlockForExceptionHandler = true };
}
Notice, that when _isScriptBlockForExceptionHandler is set to true, exception is not thrown if it happens, when catch block executes in InvokeWithPipe method above:
catch (ReturnException exception2)
{
if (!this._isScriptBlockForExceptionHandler)
{
ParseTreeNode.AppendResult(contextFromTLS, exception2.Argument, null, ref resultList);
}
else
{
exception = exception2;
}
}
What AppendResult method does is not clear to me, but I've found this:
AppendResult method is called to basically call the getters of the
public properties from the object returned to retrieve the values that
will be written to the output console.
ParameterArgumentValidationErroris a non terminating exception, then everything works as it should. You have to convert it to the terminating exception, e.g.:test-function -param "fasdfsd" -ErrorAction Stopto achieve desired result.-ErrorActionparameter doesn't cover parameter errors, so you'd have to set$ErrorActionPreference = 'Stop'here.$ErrorActionPreference = 'Stop'is the only way to go.