1

I am trying to understand why the below code prints "I shouldn't be printed!!"

The code in the try block throws an error and as its errorAction variable is set to stop Ppowershell will execute the corresponding catch block.

In the catch test-function invocation will fail (as test-function param attribute validateSet does not include "fasdfsd") and throws the apparently non terminating exception: ParameterArgumentValidationError

I now expect that the execution resumes at the line after test-function call, prints "caught exception" and exit but it jumps out of the catch block and continues execution printing "I shouldn't be printed!!".

What am I missing?

Thanks, Davide

function test-function{
[CmdletBinding()]
PARAM(
    [Parameter(Mandatory)]
    [ValidateSet('OnlyAcceptThisStringAndNothingElse')]
    [ValidateNotNullOrEmpty()]
    [string]$param
)
    write-output "Executed"

}

try{
    ## throws an error to enter the catch block
    get-childitem nonexisting -ea stop
}
catch{
    ## test-function does not accept this param and throws an error
    test-function -param "fasdfsd"
    ## this should be executed anyway but it isn't
    write-output "caught exception"
    ## the script should quit here
    exit
}
## this Should not have been executed but it is
write-output "I shouldn't be printed!!"
5
  • 2
    Well, if ParameterArgumentValidationError is 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 Stop to achieve desired result. Commented Nov 29, 2015 at 17:04
  • 1
    IIRC setting the error action with the -ErrorAction parameter doesn't cover parameter errors, so you'd have to set $ErrorActionPreference = 'Stop' here. Commented Nov 29, 2015 at 18:50
  • @AnsgarWiechers, yep, just checked and you're right, so $ErrorActionPreference = 'Stop' is the only way to go. Commented Nov 29, 2015 at 20:34
  • @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. Instead it write to output "I shouldn't be printed" Commented Nov 29, 2015 at 23:02
  • @DavideTalesco Yes, it's not logical, but it always worked like this, see my "answer" for details. Commented Nov 30, 2015 at 0:02

1 Answer 1

0

@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.

Sign up to request clarification or add additional context in comments.

5 Comments

It makes sense but still there is something missing... if you replace test-function -param "fasdfsd" with lets say get-item "nonexistingitem" (which throws a non terminating error) the execution now continues as expected (prints caught exception and exit). it means these 2 exceptions despite they look both non terminating they affect the execution flow differently...
@DavideTalesco "Curiouser and curiouser!" I've done some more tests throwing raw exceptions with New-Object -TypeName 'SomeException' and it really looks like it depends on the type of exception generated inside the catch block. Some exceptions cause "skipping", some not. The more I think about it, the more it seems like a bug that should be reported to the Connect.
I have opened a bug report ... let's see. if you think can help please post your test code too
@DavideTalesco I've voted for this bug on Connect, but I can't post anything there - something is wrong with MS auth system. I'll try later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.