Automatically stop powershell script on bat errors - powershell

Generally one can use $ErrorActionPreference to stop the execution of a PS script on error as in the example below that uses an invalid subversion command. The script exits after the first command.
$ErrorActionPreference='Stop'
svn foo
svn foo
Trying the same thing with the maven command (mvn.bat) executes both commands.
$ErrorActionPreference='Stop'
mvn foo
mvn foo
Initially I suspected that mvn.bat does not set an error code and PS just doesn't see the error. However, $? is set properly as demonstrated by the following, when PS exits after the first error:
mvn foo
if (-not $?) {exit 1}
mvn foo
So here's my question: Given that both svn.exe and mvn.bat set an error code on failure, why does the PS script not stop after the mvn error. I find it a lot more convenient to set "$ErrorActionPreference=Stop" globally rather than doing "if (-not $?) {exit 1}" after each command to terminate on error.

Not all command line programs provide errors in the same way. Some set an exit code. Some don't. Some use the error stream, some don't. I've seen some command line programs actually output everything to error, and always output non-zero return codes.
So there's not a real safe guess one could ever make as to it having run successfully, and therefore it's next to impossible to codify that behavior into PowerShell.
$errorActionPreference will actually stop a script whenever a .exe writes to the error stream, but many .exes will write regular output to the console and never populate the error stream.
But it will not reliably work. Neither, for that matter, will $?. If an exe returns 0 and doesn't write to error $? will be true.
While it might be a pain in the butt to deal with each of these individual .exes and their errors in PowerShell, it's a great example of why PowerShell's highly structured Output/Error/Verbose/Warning/Debug/Progress streams and consistent error behavior in Cmdlets beats out plain old .EXE tools.
Hope this Helps

$ErrorActionPreference controls PowerShell's behavior with regard to commandlets and PowerShell functions -- it is not sensitive to exit codes of "legacy" commands. I'm still looking for a work-around for this design decision myself -- for this issue, refer to this thread: How to stop a PowerShell script on the first error?. You'll probably conclude that this custom "exec" function is the best solution: http://jameskovacs.com/2010/02/25/the-exec-problem/
With regard to the stop-on-stderr behavior, I can't reproduce this behavior in PowerShell 3:
Invoke-Command
{
$ErrorActionPreference='Stop'
cmd /c "echo hi >&2" # Output to stderr (this does not cause termination)
.\DoesNotExist.exe # Try to run a program that doesn't exist (this throws)
echo bye # This never executes
}
Update: The stop-on-stderr behavior appears to take effect when using PS remoting.

Related

How to return non zero exit code from a Powershell module function without closing the powershell console?

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

Equivalent of bash "set -o errexit" for windows cmd.exe batch file?

What's the Windows batch file equivalent of set -o errexit in a bash script?
I have a long batch file filled with different programs to run on Windows command line... basically its an unrolled make file with every compiler command that needs to be run to build an exe in a long sequence of commands.
The problem with this method is that I want it to exit the batch command on the first non-zero return code generate by a command in the script.
As far as I know, Windows batch files have a problem where they don't automatically exit on the first error without adding a lot of repetitive boilerplate code between each command to check for a non-zero return code and to exit the script.
What I'm wondering about, is there an option similar to bash's set -o errexit for Windows cmd.exe? or perhaps a technique that works to eliminate too much boilerplate error checking code... like you set it up once and then it automatically exits if a command returns a non-zero return code without adding a bunch of junk to your script to do this for you.
(I would accept PowerShell option as well instead of cmd.exe, except PowerShell isn't very nice with old-unix-style command flags like: -dontbreak -y ... breaking those commands without adding junk to your command line like quotes or escape characters... not really something I want to mess around with either...)
CMD/Batch
As Ken mentioned in the comments, CMD does not have an equivalent to the bash option -e (or the equivalent -o errexit). You'd have to check the exit status of each command, which is stored in the variable %errorlevel% (equivalent to $? in bash). Something like
if %errorlevel% neq 0 then exit /b %errorlevel%
PowerShell
PowerShell already automatically terminates script execution on errors in most cases. However, there are two error classes in PowerShell: terminating and non-terminating. The latter just displays an error without terminating script execution. The behavior can be controlled via the variable $ErrorActionPreference:
$ErrorActionPreference = 'Stop': terminate on all errors (terminating and non-terminating)
$ErrorActionPreference = 'Continue' (default): terminate on terminating errors, continue on non-terminating errors
$ErrorActionPreference = 'SilentlyContinue': don't terminate on any error
PowerShell also allows more fine-grained error handling via try/catch statements:
try {
# run command here
} catch [System.SomeException] {
# handle exception of a specific type
} catch [System.OtherException] {
# handle exception of a different type
} catch {
# handle all other exceptions
} finally {
# cleanup statements that are run regardless of whether or not
# an exception was thrown
}

How do I get errors to propagate in the TeamCity PowerShell runner

I have a TeamCity 7 Build Configuration which is pretty much only an invocation of a .ps1 script using various TeamCity Parameters.
I was hoping that might be a simple matter of setting:
Script
File
Script File
%system.teamcity.build.workingDir%/Script.ps1
Script execution mode
Execute .ps1 script with "-File" argument
Script arguments
%system.teamcity.build.workingDir% -OptionB %BuildConfigArgument% %BuildConfigArg2%
And then I would expect:
if I mess up my arguments and the script won't start, the Build fails
if my Script.ps1 script throws, the Build fails
If the script exits with a non-0 Error Level I want the Build to Fail (maybe this is not idiomatic PS error management - should a .ps1 only report success by the absence of exceptions?)
The question: It just doesn't work. How is it supposed to work? Is there something I'm doing drastically wrong that I can fix by choosing different options?
As doc'd in the friendly TeamCity manual:
Setting Error Output to Error and adding build failure condition
In case syntax errors and exceptions are present, PowerShell writes them to stderr. To make TeamCity fail the build, set Error Output option to Error and add a build failure condition that will fail the build on any error output.
The keys to making this work is to change two defaults:
At the top level in the Build Failure Conditions, switch on an error message is logged by build runner:
In the [PowerShell] Build Step, Show advanced options and set Error output: Error
In 9.1 the following works (I wouldn't be surprised if it works for earlier versions too):
create a PowerShell Build Step with the default options
change the dropdown to say Script: Source code
Add a trap { Write-Error "Exception $_" ; exit 98 } at the top of the script
(Optional but more correct IMO for the kind of scripting that's appropriate for within TeamCity build scripts)
Show advanced options and switch on Options: Add -NoProfile argument
(Optional, but for me this should be the default as it renders more clearly as suggested by #Jamal Mavadat)
Show advanced options and switch on Error output: Error
(ASIDE #JetBrains: if the label was "Format stderr output as" it would be less misleading)
This covers the following cases:
Parse errors [bubble up as exceptions and stop execution immediately]
Exceptions [thrown directly or indirectly in your PS code show and trigger an exit code for TC to stop the build]
An explicit exit n in the script propagates out to the build (and fails it if non-zero)
There is an known bug in TeamCity that causes the behavior that the original poster noticed.
It is easy to work around, however.
At the end of your PowerShell script, add output indicating that the end of the script has been reached:
Echo "Packaging complete (end of script reached)"
Then, set up a new Build Failure Condition on your build to fail if the text you are echoing is NOT present in the output.
You're over-thinking things. Try this:
Script
File
Script File
Script.ps1
You don't need to give this a path - by default, it's relative to the checkout directory.
Script execution mode
Put script into PowerShell stdin with "-Command -" arguments
This is exactly what I use to run a bunch of powershell scripts within Teamcity.
Update
I missed the bit in the original post about having failures in the powershell script fail the build. Apologies!
I've solved that part of the puzzle in two different ways.
For regular powershell scripts
Wrap the main code block in a try...catch; if an exception occurs, return a non-zero integer value. For successful execution, return 0.
This convention of returning zero for success dates back a very long way in history - it's used by Unix/Linux systems, DOS, CP/M and more.
For PSake build scripts
Use a wrapper powershell script to invoke psake and directly set the result of the teamcity build by writing a response message to stdout.
At the start of the script, define a status message that represents failure:
$global:buildResult = "#teamcity[buildStatus status='FAILURE' text='It died.']
Within the psake script, update $global:buildResult to indicate success in a dedicated task that's run last of all.
$global:buildResult = "#teamcity[buildStatus status='SUCCESS' text='It lives.']
At the end of the wrapper script, output the status message
write-host $global:buildResult
If anything in the build script fails, that last task won't run, and the default message (indicating failure) will be output.
Either way, Teamcity will pick up on the message and set the build status appropriately.
An alternative to the accepted answer that works for me On TeamCity 9 (I don't like changing the 'Build Failure Conditions' option as it affects all build steps):-
I wanted PowerShell errors to fail the build step, but with a pretty message.
So I wanted to throw an error message AND return an error code.... try / catch / finally to the rescue.
EDIT for clarity: This script is supposed to fail. It is demonstrating that it is possible to throw an exception AND to return an exit code.
So you get both, the exit code for TeamCity to deal with, and, in my case, a nice clear debug message that shows where the issue was.
My demo script:
Try {
Write-Host "Demoing the finally bit"
# Make sure if anything goes wrong in the script we get an exception
$ErrorActionPreference = "Stop"
# This will fail and throw an exception (unless you add the file)
Get-Content IDontExist.txt
}
Catch
{
# Throwing like this gives a pretty error message in the build log
throw $_
# These give less pretty/helpful error messages
# Write-Host $_
# Write-Host $_.Exception.Message
}
Finally
{
# 69 because it's more funny than 1
exit(69)
}
If newer TeamCity versions are within your reach then it is worth checking out some of PowerShell build runner improvements.
In particular, changing Error Output from default warning to error may interest you.
Just praise the lawd you don't need to work with Jenkins.. Can't use the PowerShell step for similar issues. Instead we need to use a "cmd" step that calls the following magic wrapper that will fail the step on exception and exit codes:
%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -Command "& { $ErrorActionPreference = 'Stop'; & 'path\to\script.ps1 -arg1 -arg2; EXIT $LASTEXITCODE }"
This has been superseded by options afforded by 9.x, but I'll leave it here as it definitely was bullet proof at the time and I couldn't find any other solution I liked better.
You could just do something that works. The following has been tested with
errors in the script bit
missing files
script exiting with non-0 ERRORLEVEL
In the TeamCity Powershell runner options, set it as follows:
Script File
Source code
Script source
$ErrorActionPreference='Stop'
powershell -NoProfile "Invoke-Command -ScriptBlock { `$errorActionPreference='Stop'; %system.teamcity.build.workingDir%/Script.ps1 %system.teamcity.build.workingDir% --OptionB %BuildConfigArgument% %BuildConfigArg2%; exit `$LastExitCode }"
(unwrapped version: powershell -NoProfile "Invoke-Command -ScriptBlock { $errorActionPreference='Stop'; %system.teamcity.build.workingDir%/Script.ps1 %system.teamcity.build.workingDir% --OptionB %BuildConfigArgument% %BuildConfigArg2%; exit$LastExitCode }"
Script execution mode
Put script into PowerShell stdin with "-Command -" arguments
Additional command line parameters
-NoProfile
I'm hoping against hope this isn't the best answer!
This checks the last error code and exits if is not 0.
If ($LASTEXITCODE -ne 0) {
Write-Host "The result was of the previous script was " $LASTEXITCODE
Exit $LASTEXITCODE
}
Powershell v2 atleast had an issue with the -File option messing up error codes. There is some details about how it messes up and stuff.
The answer is either switch to -Command option and/or wrap it with a bat file that checks the LastErrorCode and returns 1+ if its supposed to fail.
http://zduck.com/2012/powershell-batch-files-exit-codes/
Use something like this:
echo "##teamcity[buildStatus status='FAILURE' text='Something went wrong while executing the script...']";
For reference, take a look on Reporting Build Status from Teamcity

Can I make the PowerShell $error object behave consistently in both script and interactive mode?

I'm writing a PowerShell script that uses $error to detect and respond to errors. My problem is that I get different behavior for the $error object depending on how I run the script. If I run it interactively (specifically, from PowerShell ISE), then errors get added to the collection, but if I run the same script from the command line, the same errors occur, but are not added to the collection.
Here's the script (pared down to illustrate the issue):
# export_exception_test.ps1
# show me how many errors before we start
"Count = " + $error.Count
try {
# treat non-terminating errors as terminating errors.
$ErrorActionPreference = "Stop"
# echo the setting to output
$ErrorActionPreference
# use sqlcmd to save the output of stored procedures to text files.
# The first and third call will fail because the output folder does not exist.
# The second call will succeed.
'first call to sqlcmd'
sqlcmd -S myserver\myinstance -E -s `"`t`" -Q "EXEC mydatabase.dbo.FI_Codes" -b -o "B:\Exports_new\FI_Codes.txt"
'second call to sqlcmd'
sqlcmd -S myserver\myinstance -E -s `"`t`" -Q "EXEC mydatabase.dbo.FI_Codes" -b -o "B:\Exports_newt\FI_Codes.txt"
'third call to sqlcmd'
sqlcmd -S myserver\myinstance -E -s `"`t`" -Q "EXEC mydatabase.dbo.FI_Codes" -b -o "B:\Exports_new\FI_Codes.txt"
# and a whole bunch more of these...
# The error count should be two more than when we started.
"Count = " + $error.Count
# And this should be the most recent error message
"Message = " + $error[0].Message
}
catch [Exception]
{
'exception was caught!!!'
"Count in catch clause = " + $error.Count
$_.Exception.Message
# the ultimate goal is to return a non-successful return code when the exports fail
exit 1
}
finally {
# set this back to what it was
$ErrorActionPreference = "Continue"
# primitive trace output
'finally.'
}
When I run this from PowerShell ISE, it behaves as expected. SQLCMD raises a non-terminating error, which is caught and handled. Here's the output:
PS U:\> C:\Users\etmatt\Documents\PowerShellScripts\export_exception_test.ps1
Count = 0
Stop
first call to sqlcmd
exception was caught!!!
Count in catch clause = 1
Sqlcmd: Error: Error occurred while opening or operating on file B:\Exports_new\FI_Codes.txt (Reason: The system cannot find the path specified).
finally.
But when I run it from the command line, as I would if I were to set up a scheduled task, nothing gets added to $error, and no exceptions are raised. Here's the output:
C:\Users\etmatt>powershell.exe -file "C:\Users\etmatt\Documents\PowerShellScripts\export_exception_test.ps1"
Count = 0
Stop
first call to sqlcmd
Sqlcmd: Error: Error occurred while opening or operating on file B:\Exports_new\FI_Codes.txt (Reason: The system cannot find the path specified).
second call to sqlcmd
third call to sqlcmd
Sqlcmd: Error: Error occurred while opening or operating on file B:\Exports_new\FI_Codes.txt (Reason: The system cannot find the path specified).
Count = 0
Message =
finally.
C:\Users\etmatt>
I also get the same results if I run the script file from the powershell command line rather than cmd.exe (which makes sense). For example:
PS C:\> ."C:\Users\etmatt\Documents\PowerShellScripts\export_exception_test.ps1"
I suspect this has something to do with the execution context or maybe something about parser modes that I'm still not quite grokking, or maybe even my profile (although so far I've been running everything from the same PC under the same account). I've seen a lot of examples online that use this basic approach of try/catch with $ErrorActionPreference = "Stop", so it seems like I should be able to make this work.
So my question is essentially: can I make this script work like I think it should? And if not, what am I misunderstanding? How do I catch these sorts of errors? If it helps, I don't need super detailed exception handling for this one, I just need to know when something goes wrong so that my task monitor can alert me. A non-zero return code from powershell.exe would be sufficient.
I'm looking into the use $?, and $LASTEXITCODE, but I'm betting $? will have the same issue as $error, and both only apply to the last statement executed, which is less than ideal since my real script is quite a bit longer than this example.
EDIT:
OK, I've since learned that Windows executables (such as sqlcmd), never add anything to the $error collection, even if they return a non-zero exit code. $error is only used by cmdlets, if I understand correctly. I got my script working through the repeated use of $LASTEXITCODE, although I could have used $? as well, because it recognizes Windows executable exit codes and gets set to false for non-zero values.
To sum up, the behavior of the script when run from the command line is the correct, expected behavior. Still don't know why I got different results from the PowerShell ISE, though.
For me I am calling batch command using cmd /C <command> and I have the similar problem. For non zero return code $? is set correctly but there is no $error object (how would powershell know? so this is ok) and the code behaves similarly in ISE as well as command line execution.
My problem is that in such cases when $ErrorActionPreference = "Stop" it is not throwing exception. Probably because it doesn't have $error object to throw. So now I need to check $? after every call to other script or program which is not neat. They should come up with good solution for such situation (probably by throwing in-built exception saying call to external script/program failed)

powershell $LastExitCode -1073741502 when calling osql or sqlcmd

I've written a powershell script to call sqlcmd.exe to execute a sql script against a remote sql server. The powershell script checks $LASTEXITCODE. If $LASTEXITCODE is non zero, I throw "Script failed. Return code is $LASTEXITCODE."
The script is used multiple times to execute different sql scripts and is part of a chain of powershell scripts that run during deployment.
The script runs fine most of the time but randomly fails with a return code of -1073741502.
This has only started happening after upgrading to SQL2008 and I cannot reproduce it by running either the single powershell script manually or the sql cmd script manually.
This is the powershell command:
& 'sqlcmd.exe' -S $databaseServer -r -b -E -i '$scriptFullPath'
if($LASTEXITCODE -ne 0)
{
throw "Script failed. Return code is $LASTEXITCODE."
}
The seemingly random nature of the failure is causing a lot of pain. I can't determine if the error is SQL2008, SQLCMD (Although I get the same behaviour with osql.exe) or somehow coupled to powershell.
The actual sql that sqlcmd is executing appears to be unrelated to the problem since a sql script will execute ok for a while and then fail.
The same failure has been seen on many different Workstations and Servers (Win7, Win2003 and Win2008)
Any guidance on how to track this down would be much appreciated.
Don't use sqlcmd and command lines if you are using Powershell!
There are very good built-in tools in V2 that give you a lot more robust interaction with SQL Server, and don't require you to parse the text of return codes to check for errors - they actually return proper error codes.
Here's a technet article on Invoke-SQLCmd
Bear in mind you need to load the snapins first:
Add-PSSnapin SqlServerProviderSnapin100
Add-PSSnapin SqlServerCmdletSnapin100
The root-cause is actually explained elsewhere on here
Exit Code -1073741502 is 0xC0000142 in hex (status_dll_init_failed).
Microsoft's KB2701373 addresses the leaky handles made by Console.Write in Microsoft.powershell.consolehost.dll.
Side-Note: Some "Fixes" on the Internet involve doing something differently, then restarting PowerShell. Yet the actual restart of PowerShell is what addresses the problem (temporarily)