MyScript.ps1:
exit 1
MyThrow.ps1:
throw "test"
Execution in PowerShell:
& ".\MyScript.ps1"
Write-Host $LastExitCode # Outputs 1
Clear-Variable LastExitCode
& ".\MyThrow.ps1"
Write-Host $LastExitCode # Outputs nothing
How do I set a proper exit code when throwing an exception?
You don't. When you throw an exception you expect someone to handle it. That someone would be the one to terminate execution and set an exit code. For instance:
try {
& ".\MyThrow.ps1"
} catch {
exit 1
}
If there is nothing to catch your exception you shouldn't be throwing it in the first place, but exit right away (with a proper exit code).
Becareful:
With Powershell 4 and less:
When an exception is thrown, exit code remains at 0 (unfortunately)
With Powershell 5 and up:
When an exception is thrown, exit code defaults to 1
Actually you can set the code also with throw, just put it before throw.
Contents of the script: Throw.ps1
exit 222
throw "test"
Output:
PS C:\> .\Throw.ps1
PS C:\> $LASTEXITCODE
222
If you run it like this:
powershell .\Throw.ps1
The output will be as follows:
PS C:\> powershell .\Throw.ps1
PS C:\> $LASTEXITCODE
1
But here is the thing, powershell exit codes should be either 0 or 1,
anything else, finally will give you result of 1.
Another interesting thing to mention, if to try $? after you run the script,
if true or false, the result depends of what you want to put in there.
exit 0 --> true, exit 1 --> false
Here it is an example:
Content of the script: Throw_1.ps1
exit 1
throw "test"
Output:
PS C:\> .\Throw_1.ps1
PS C:\> $?
False
Content of the script: Throw_0.ps1
exit 0
throw "test"
Output:
PS C:\> .\Throw_0.ps1
PS C:\> $?
True
As you can see it is just what you need or want to achieve.
Running the mythrow.ps1 inside powershell will set $? to false, and will add to the $error object array. Running it with another powershell process will set $lastexitcode to 1.
PS C:\> powershell mythrow.ps1
PS C:\> $lastexitcode
1
Related
Please, observe:
The method
PS C:\> (Get-Command Invoke-SilentlyAndReturnExitCode).ScriptBlock
param([scriptblock]$Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
& $Command > $null 2>&1
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
PS C:\>
The command to silence
PS C:\> $ErrorActionPreference = "Stop"
PS C:\> $Command = { cmd /c dir xo-xo-xo }
PS C:\> & $Command > $null 2>&1
cmd : File Not Found
At line:1 char:14
+ $Command = { cmd /c dir xo-xo-xo }
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (File Not Found:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PS C:\>
As you can see, it fails with an exception. But we can silence it easily, right?
PS C:\> $ErrorActionPreference = 'SilentlyContinue'
PS C:\> & $Command > $null 2>&1
PS C:\> $LASTEXITCODE
1
PS C:\>
All is good. Now my function does the same, so let us try it:
PS C:\> $ErrorActionPreference = "Stop"
PS C:\> Invoke-SilentlyAndReturnExitCode $Command
-1
PS C:\>
Yikes! It returns -1, not 1.
The problem appears to be that setting $ErrorActionPreference inside the function does not actually propagate to the command scope. Indeed, let me add some output:
PS C:\> (Get-Command Invoke-SilentlyAndReturnExitCode).ScriptBlock
param([scriptblock]$Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
Write-Host $ErrorActionPreference
& $Command > $null 2>&1
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
PS C:\> $Command = { Write-Host $ErrorActionPreference ; cmd /c dir xo-xo-xo }
PS C:\> Invoke-SilentlyAndReturnExitCode $Command
Continue
Stop
-1
PS C:\>
So, the problem is really around $ErrorActionPreference - why does it not propagate? Powershell uses dynamic scoping, so the command definition should not capture its value, but use the one from the function. So, what is going on? How to fix it?
tl;dr
Because your Invoke-SilentlyAndReturnExitCode function is defined in a module, you must recreate your script block in the scope of that module for it to see the module-local $ErrorActionPreference value of Continue:
# Use an in-memory module to demonstrate the behavior.
$null = New-Module {
Function Invoke-SilentlyAndReturnExitCode {
param([scriptblock] $Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
Write-Host $ErrorActionPreference # local value
# *Recreate the script block in the scope of this module*,
# which makes it see the module's variables.
$Command = [scriptblock]::Create($Command.ToString())
# Invoke the recreated script block, suppressing all output.
& $Command *>$null
# Output the exit code.
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
}
}
$ErrorActionPreference = 'Stop'
$Command = { Out-Host -InputObject $ErrorActionPreference; cmd /c dir xo-xo-xo }
Invoke-SilentlyAndReturnExitCode $Command
On Windows, the above now prints the following, as expected:
Continue
Continue
1
That is, the recreated $Command script block saw the function-local $ErrorActionPreference value, and the catch block was not triggered.
Caveat:
This will only work if the $Command script block contains no references to variables in the originating scope other than variables in the global scope.
The alternative to avoid this limitation is to define the function outside of a module (assuming you're also calling it from code that lives outside modules).
Background Information
The behavior implies that your Invoke-SilentlyAndReturnExitCode function is defined in a module, and each module has its own domain of scopes (hierarchy of scopes).
Your $Command script block, because it was defined outside that module, is bound to the default scope domain, and even when executed from inside a module, it continues see the variables from the scope domain in which it was defined.
Therefore, $Command still sees the Stop $ErrorActionPreference value, even though for module-originated code inside the function it would be Continue, due to setting a local copy of $ErrorActionPreference inside the module function.
Perhaps surprisingly, it is still the $ErrorActionPreference in effect inside $Command that controls the behavior, not the function-local value.
With a redirection such as 2>$null for *>$null in effect while Stop is the effective $ErrorActionPreference value, the mere presence of stderr output from an external program - whether it indicates a true error of not - triggers a terminating error and therefore the catch branch.
This particular behavior - where the explicit intent to suppress stderr output triggers an error - should be considered a bug, and has been reported in this GitHub issue.
The general behavior, however - a script block executing in the scope in which it was defined - while non-obvious, is by design.
Note: The remainder of this answer is its original form, which contains general background information that, however, does not cover the module aspect discussed above.
*> $null can be used to silence all output from a command - no need for suppressing the success output stream (>, implied 1>) and the error output stream (2>) separately.
Generally, $ErrorActionPreference has no effect on error output from external programs (such as git), because stderr output from external programs bypasses PowerShell's error stream by default.
There is on exception, however: setting $ErrorActionPreference to 'Stop' actually makes redirections such as 2>&1 and *>$null throw a terminating error if an external program such as git produces any stderr output.
This unexpected behavior is discussed in this GitHub issue.
Otherwise, a call to an external program never triggers a terminating error that a try / catch statement would handle. Success or failure can only be inferred from the automatic $LASTEXITCODE variable.
Therefore, write your function as follows if you define (and call) it outside a module:
function Invoke-SilentlyAndReturnExitCode {
param([scriptblock]$Command, $Folder)
# Set a local copy of $ErrorActionPreference,
# which will go out of scope on exiting this function.
# For *> $null to effectively suppress stderr output from
# external programs *without triggering a terminating error*
# any value other than 'Stop' will do.
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try {
# Invoke the script block and suppress all of its output.
# Note that if the script block calls an *external program*, the
# catch handler will never get triggered - unless the external program
# cannot be found.
& $Command *> $null
$LASTEXITCODE
}
catch {
# Output the exit code used by POSIX-like shells such
# as Bash to signal that an executable could not be found.
127
} finally {
Pop-Location
}
}
Why does a PowerShell script not end when there is a non-zero exit code when using the call operator and $ErrorActionPerference = "Stop"?
Using the following example, I get the result managed to get here with exit code 1:
$ErrorActionPreference = "Stop"
& cmd.exe /c "exit 1"
Write-Host "managed to get here with exit code $LASTEXITCODE"
The Microsoft documentation for the call operator does not discuss what should happen when using call operator, it only states the following:
Runs a command, script, or script block. The call operator, also known as the "invocation operator," lets you run commands that are stored in variables and represented by strings. Because the call operator does not parse the command, it cannot interpret command parameters.
Additionally, if this is expected behaviour, is there any other way to have the call operator cause an error rather than let it continue?
The return code is not a PowerShell error - it's seen the same way as any other variable.
You need to then act on the variable and throw an error using PowerShell for you script to see it as a terminating error:
$ErrorActionPreference = "Stop"
& cmd.exe /c "exit 1"
if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" }
In almost all my PowerShell scripts, I prefer to "fail fast," so I almost always have a small function that looks something like this:
function Invoke-NativeCommand() {
# A handy way to run a command, and automatically throw an error if the
# exit code is non-zero.
if ($args.Count -eq 0) {
throw "Must supply some arguments."
}
$command = $args[0]
$commandArgs = #()
if ($args.Count -gt 1) {
$commandArgs = $args[1..($args.Count - 1)]
}
& $command $commandArgs
$result = $LASTEXITCODE
if ($result -ne 0) {
throw "$command $commandArgs exited with code $result."
}
}
So for your example I'd do this:
Invoke-NativeCommand cmd.exe /c "exit 1"
... and this would give me a nice PowerShell error that looks like:
cmd /c exit 1 exited with code 1.
At line:16 char:9
+ throw "$command $commandArgs exited with code $result."
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (cmd /c exit 1 exited with code 1.:String) [], RuntimeException
+ FullyQualifiedErrorId : cmd /c exit 1 exited with code 1.
I am writing an automation script. I had a function which takes either a command or an executable. I had to wait until the command or executable has completed running and return if failed or passed. I also want to write the output to file. I am trying with the Start-Job cmdlet.
My current code:
$job = Start-Job -scriptblock {
Param($incmd)
$ret = Invoke-Expression $incmd -ErrorVariable e
if ($e) {
throw $e
} else {
return $ret
}
} -ArumentList $outcmd
Wait-Job $job.id
"write the output to file using receive-job and return if passed or failed"
This works perfectly fine for commands but for executables irrespective of errorcode the value of $e is null. This falsely shows as passed even though the errorcode is 0.
I tried with errorcode using $LASTEXISTCODE and $?. But $? is true for executables and $LASTEXISTCODE is either null or garbage value for commands. I am out of ideas and struck here.
When in doubt, read the documentation:
$?
Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.
[…]
$LASTEXITCODE
Contains the exit code of the last Windows-based program that was run.
Basically, you need to check both. $? indicates whether the last PowerShell command/cmdlet was run successfully, whereas $LASTEXITCODE contains the exit code of the external program that was last executed.
if (-not $? -or $LASTEXITCODE -ne 0) {
throw '... whatever ...'
} else {
return $ret
}
However, Invoke-Expression is not a very good approach to executing commands. Depending on what you actually want to execute there are probably better ways to do it, with better methods for error handling.
I found that
$(Invoke-Expression hostname) -eq 'mycomputername'
Whether it is matched or not, the exitcode must be 0
this behavior is different from linux ,i.e, if not match error code exit 1
Is there any short command in PowerShell that can return error exit code if doesn't match the string?
In an script you can change exit code using exit keyword.
A normal termination will set the exitcode to 0
An uncaught THROW will set the exitcode to 1
The EXIT statement will stop the process and set the exitcode to whatever is specified.
In your case I'ld do something like this
if ( $(hostname) -eq 'mycomputername')
{
exit 0
}
else
{
exit 1
}
Are you looking for something like this?
C:\>powershell -command "& { if($(Invoke-Expression hostname) -eq 'wrongname'){ exit 0 } else { exit 1 } } "
C:\>echo %errorlevel%
1
C:\>powershell -command "& { if($(Invoke-Expression hostname) -eq 'rightname'){ exit 0 } else { exit 1 } } "
C:\>echo %errorlevel%
0
If you're wanting it more succint,
powershell -command "& { if($(Invoke-Expression hostname) -ne 'wrongname'){ exit 1 } }"
Minor update, you can simplify this these days:
powershell -command "if($(Invoke-Expression hostname) -ne 'wrongname'){ exit 1 }"
echo Error=%ERRORLEVEL%
I'm running PS scripts from VBScript and would like to throw exception in VBScript if there were errors in PS. This is the VBScript code I use to run PowerShell:
Option Explicit
Dim oShell, appCmd
Set oShell = CreateObject("WScript.Shell")
appCmd = "powershell set-executionpolicy unrestricted -force; c:\3.ps1"
oShell.Run appCmd, 4, true
This is my PS script:
throw "I'm exception"
I've tried general VBScript catch:
if( Err.number <> 0 ) then
Err.raise()
end if
but it doesn't seem to work. I would love to use only PS and get rid of VBScript altogether, but it's another application that is running VBs and it only supports VBs. Any ideas?
I could write exception in file in PS and then from VBs check if file exist and throw exception, but I hope there's a better way.
Thanks
In the Powershell script use
exit $MyErrorLevel
Where $MyErrorLevel is your own code you will detect with VBS.
In VBS the WShell object Run method will return the exit code from PowerShell.
Example VBS script:
Option Explicit
Dim oShell, appCmd
Dim intReturnCode
Set oShell = CreateObject("WScript.Shell")
appCmd = "powershell.exe -File c:\Test.ps1"
intReturnCode = oShell.Run(appCmd, 4, true)
MsgBox intReturnCode
Example PS script (C:\Test.ps1):
try {
1 / $null
} catch {
exit 1
}
exit 0