7

I expect that the return from the function statement (begin, process, end) should also terminate all its statements, but this does not happen.

I have a test function like this:

function test($a) {
    begin { 
        Write-Host 'begin ' -NoNewline
        if ($a -like '*begin*') { return; } 
    }
    process {
        Write-Host 'process ' -NoNewline
        if ($a -like '*process*') { return; } 
    }
    end {
        Write-Host 'end' -NoNewline
        if ($a -like '*end*') { return; } 
    }
}

And when I run it this way:

'begin', 'process', 'end' | ForEach-Object {
    Write-Host "Try exit in $_ :`t" -NoNewline
    test $_
    Write-Host
}

Then I get a similar result:

Try exit in begin :     begin process end
Try exit in process :   begin process end
Try exit in end :       begin process end

But I would like the output from the function to also terminate its other statements and the output would be like this:

Try exit in begin :     begin
Try exit in process :   begin process
Try exit in end :       begin process end

How can I get it?

I tryed to use break, continue and exit instead of return but it terminated all script..

2

1 Answer 1

13

Your function is an advanced function, and therefore uses begin, process and end blocks.

In a simple (non-advanced) function, in the absence of such blocks, return would indeed instantly exit the function, but from inside these blocks, return just exits that block.

(As an aside: break and continue only apply to loop-like constructs, and exit is meant to exit scripts as a whole, not individual functions).


If you want to exit your advanced function as a whole you have two basic choices:

  • Throw an error, using a throw statement, which creates a script-terminating error (fatal by default).

    • To emulate the statement-terminating errors that binary cmdlets emit, you can use the $PSCmdlet.ThrowTerminatingError() method (available to advanced functions such as yours) - by default, execution resumes in the caller's scope afterwards (after printing an error message).

    • If throw is used but aborting execution overall should be prevented, a caller must enclose the call in a try / catch statement (or, less commonly these days, use a trap statement).

  • Use a custom [bool] variable that ignores attempts to (re)enter the process / the other blocks.

    • Note that in order to exit the per-pipeline-input-object process block efficiently, it would be helpful to be able to instruct upstream commands that no more input should be provided. However, this isn't an option as of PowerShell 7.4, but has been proposed as a future enhancement in GitHub issue #3821.

To illustrate the technique of using a [bool] variable:

function test($a) {
  begin { 
    $done = $false
    Write-Host 'begin ' -NoNewline
    if ($a -like '*begin*') { $done = $true; return } 
  }
  process {
    if ($done) { return }
    Write-Host 'process ' -NoNewline
    if ($a -like '*process*') { $done = $true; return } 
  }
  end {
    if ($done) { return }
    Write-Host 'end' -NoNewline
    # Retained from your original code, but note that this is redundant: 
    # The function will exit here either way.
    if ($a -like '*end*') { return } 
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Understanding that an Exit will not exit the script is vital. A throw will exit the script.
@JariTurkia, exit does exit the enclosing script, not just the function at hand.
Ah, ok. When nesting scripts, things get tricky.
@JariTurkia, yes; if the intent is to abort execution overall, throw must be used (the resulting error can be caught by a caller, however).

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.