3

My powershell module has a function and I want it to return a non zero exit code. However, being a module function it is loaded into the context of the powershell console when I run Import-Module. So, when the function executes exit 1 - poof, goes the console window!

At least, this is how I explain it closing the powershell window when it exits.

So, how can a ps module function exit with a non zero exit code without killing the console where the module was imported?

P.S.

I did notice several questions on SO about this subject, but none seems to examine this particular case.

EDIT 1

I would like to provide some context. I have a PS module with a lot of functions. Some of them are used as is in Azure DevOps yaml build scripts. The latter knows to recognize non zero exit code and abort the pipeline, so it is not necessary to throw from a function to abort the flow.

However, if I want to call that function from the console, e.g. to test something quickly and it exits with non zero code, the whole console window is closed. This is extremely annoying.

Sometimes there is a workaround. So, instead of this code:

dotnet build ...
$ExitCode = $LastExitCode
DoSomethingInAnyCase()
if ($ExitCode)
{
    exit $ExitCode
}

We can have the following version:

try
{
    dotnet build ...
}
finally
{
    DoSomethingInAnyCase()
}

Both versions would correctly return the right exit code, but because the second one does not have the explicit exit statement, it does not close the console.

mark
  • 59,016
  • 79
  • 296
  • 580
  • What problem are you trying to solve (by needing a non-zero exit code)? – Bill_Stewart Nov 21 '19 at 16:29
  • I want to indicate a failure without explicit throw statement. – mark Nov 21 '19 at 18:04
  • Why? What's wrong with throwing an exception? – Bill_Stewart Nov 21 '19 at 18:50
  • Too much garbage on the screen as a result of it. I do use it often, but I want to know about exit codes, hence the question. – mark Nov 21 '19 at 19:04
  • I'm trying to understand your goal. Modules implement functions that execute in the current scope. Functions don't change exit codes because those are only set by processes or scripts. Although I guess you could write `& $Env:ComSpec /c exit n`? – Bill_Stewart Nov 21 '19 at 19:09
  • Please, see **EDIT 1** – mark Nov 21 '19 at 22:52
  • As noted, you can work around this by running `cmd.exe /c exit` with an exit code. This will spawn `cmd.exe` which will immediately terminate with whatever exit code you need. – Bill_Stewart Nov 21 '19 at 23:29
  • I will check it. – mark Nov 21 '19 at 23:49
  • It does set exit code, but at the same time does not exit the function - of course, it exits the cmd command. So, one has to manually take care of leaving the function if exit code is not zero. Not very intuitive. Is it the best we can have? – mark Nov 22 '19 at 05:53
  • @Bill_Stewart - still `cmd /c exit 1` is a valuable input. It would help when setting the exit code is really the last statement. Can you arrange this as an answer, so I could credit you? – mark Nov 22 '19 at 12:54
  • Added as answer. – Bill_Stewart Nov 25 '19 at 01:10

2 Answers2

6

You'll have to set $global:LASTEXITCODE in order to set the exit code, but note that PowerShell functions aren't really meant to set exit codes, only scripts, and the latter only for reporting exit codes to the outside world, via the PowerShell process' own exit code, when PowerShell is called via its (CLI powershell.exe for Windows PowerShell, pwsh for PowerShell Core) from a build tool, scheduled task, or another shell, for instance.

Also note that setting $global:LASTEXITCODE directly:

  • does not make $?, the automatic success-status variable, reflect $false in the caller's context, the way that exit <nonzero-value> does from a script and the way that calling an external program that reports a nonzero exit code does.

  • is not enough to make the PowerShell process as a whole report this exit code.

In short: All this gains you is that the caller can inspect $LASTEXITCODE after your function was called, as you would after calling an external program.


Generally, exit codes are an inter-process concept and do not fit well into PowerShell's in-process world.

For more information about exit codes in PowerShell, see this post.


PowerShell's analog to exit codes is $?, the automatic, Boolean success-status variable ($true or $false), which reflects a PowerShell command's success immediately afterwards.
(If that command is an external-program call, an exit code of 0 sets $? to $true, and any nonzero one sets it to $false).

As it turns out, setting that was what you really meant to ask.

As of PowerShell Core 7.0.0-preview.5, you cannot set $? directly.

For now, these are the only ways to cause $? to reflect $false in the caller's scope, and, conversely, you cannot always ensure that it is $true:

  • From a script: Exit the script with exit <nonzero-integer>

  • From a cmdlet or function (script as well):

    • Throw a script-terminating error (Throw) or a statement-terminating error ($PSCmdlet.ThrowTerminatingError()) - the latter being only available in advanced functions and scripts.

    • Write an error to PowerShell's error stream with $PSCmdlet.WriteError() - the latter being only available in advanced functions and scripts.

      • Note that this unexpectedly currently does not apply to the Write-Error cmdlet - see this GitHub issue

Note that both techniques invariably involve emitting an error.

Since it sounds like that's precisely what you're trying to avoid, you'll have to wait until the ability to set $? directly is implemented.

The decision to implement this ability has been made, but it's unclear when it will be implemented.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Workaround: Run cmd.exe and set whatever exit code you want before exiting the function. Example:

function Test-Function {
  $cmd = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::System)) "cmd.exe"
  & $cmd /c exit 3
  # $LASTEXITCODE will be set to 3
}
Bill_Stewart
  • 22,916
  • 4
  • 51
  • 62