by thorhalvor

Powershell returns exitcode 0 instead of 1

I am currently setting up buildscripts in Teamcity for a website I am working on. I use Ctt.exe to transform my web.config files and this program is invoked through PowerShell. But my problem is that that Ctt.exe in some cases has an error or something else is wrong in my ps1-script but the Teamcity buildstep returns "Process exited with code 0" and the build Succeeds instead of failing.

Since this blogpost might be boring and badly written I will start with the solution:

$ErroActionPreference = ‘Stop’

My scenario is that I have a script, ConfigTransform.ps1, which is invoking the ctt.exe file. This script file takes one parameter which is the Environment (Test/Production). If an error occurs in the script I need the hole build to fail.

To force an error while testing I have set my script to invoke the file "DoesNotExist.exe" instead of the Ctt.exe.

My script (ConfigTransform.ps1)

param($Environment)
Write-Host Environment is $Environment
..DoesNotExist.Exe

 

In Teamcity there are many different alternatives for you to call the script.

Script=File and Executionmode = -FILE

Since I have a predefined powershell script with all the logic and I need to pass a parameter to it, – I first choose the PowerShell-runner with Script=File and Executionmode to –File. As you can see below the parameter is passed on but this process exits with code 0 and therefore the Build won’t fail.

[13:51:05][Step 1/5] Starting: (…)cmd.exe /c (…)powershell.exe -NonInteractive -File (…)
ConfigTransform.ps1 -Environment Test && exit /b %ERRORLEVEL%
[13:51:06][Step 1/5] Environment is Test
[13:51:06][Step 1/5] The term '..DoesNotExist.Exe' is not recognized as the name of a cmdlet, funct
[13:51:06][Step 1/5] ion, script file, or operable program. Check the spelling of the name, or if a
[13:51:06][Step 1/5] path was included, verify that the path is correct and try again.
[13:51:06][Step 1/5] At C:TeamCitybuildAgentwork7100e0a13f6ca7b6BuildToolsAndScriptsScriptsCo
[13:51:06][Step 1/5] nfigTransformConfigTransform.ps1:4 char:20
[13:51:06][Step 1/5] + ..DoesNotExist.Exe <<<<
[13:51:06][Step 1/5]     + CategoryInfo          : ObjectNotFound: (..DoesNotExist.Exe:String) [],
[13:51:06][Step 1/5]     CommandNotFoundException
[13:51:06][Step 1/5]     + FullyQualifiedErrorId : CommandNotFoundException
[13:51:06][Step 1/5]
[13:51:06][Step 1/5] Process exited with code 0

 

Script=File and Executionmode = -Command

Then I try setting the Executionmode to "-Command". The exit code is now correct and the Build is not failing but it is not possible to pass on script parameters.

[13:51:07][Step 1/5] Starting: (…)cmd.exe /c (…)powershell.exe -NonInteractive -Command (…)
ConfigTransform.ps1 && exit /b %ERRORLEVEL%
[13:51:07][Step 1/5] Environment is
[13:51:07][Step 2/5] The term '..DoesNotExist.Exe' is not recognized as the name of a cmdlet, funct
[13:51:07][Step 2/5] ion, script file, or operable program. Check the spelling of the name, or if a
[13:51:07][Step 2/5] path was included, verify that the path is correct and try again.
[13:51:07][Step 2/5] At line:1 char:20
[13:51:07][Step 2/5] + ..DoesNotExist.Exe <<<<
[13:51:07][Step 2/5]     + CategoryInfo          : ObjectNotFound: (..DoesNotExist.Exe:String) [],
[13:51:07][Step 2/5]     CommandNotFoundException
[13:51:07][Step 2/5]     + FullyQualifiedErrorId : CommandNotFoundException
[13:51:07][Step 2/5]
[13:51:07][Step 2/5] Process exited with code 1

 

Script=Source code and Executionmode = -Command

Since executionmode –File does not return correct errorcode I need to use "-Command" but also pass on my parameter. This can be done PowerShell-runner with Script=Source code. In the Script Source field I am now adding the full path to my script including my parameter.

The output is:

[13:51:05][Step 1/5] Starting: (…)cmd.exe /c (…)powershell.exe -NonInteractive -Command (…)
powershell3439435836265.ps1 && exit /b %ERRORLEVEL%
[13:51:06][Step 1/5] Environment is Test
[13:51:06][Step 1/5] The term '..DoesNotExist.Exe' is not recognized as the name of a cmdlet, funct
[13:51:06][Step 1/5] ion, script file, or operable program. Check the spelling of the name, or if a
[13:51:06][Step 1/5] path was included, verify that the path is correct and try again.
[13:51:06][Step 1/5] At C:TeamCitybuildAgentwork7100e0a13f6ca7b6BuildToolsAndScriptsScriptsCo
[13:51:06][Step 1/5] nfigTransformConfigTransform.ps1:4 char:20
[13:51:06][Step 1/5] + ..DoesNotExist.Exe <<<<
[13:51:06][Step 1/5] + CategoryInfo : ObjectNotFound: (..DoesNotExist.Exe:String) [],
[13:51:06][Step 1/5] CommandNotFoundException
[13:51:06][Step 1/5] + FullyQualifiedErrorId : CommandNotFoundException
[13:51:06][Step 1/5]
[13:51:06][Step 1/5] Process exited with code 0

My script is runtime wrapped in a temporary ps1-file "powershell3439435836265.ps1 ", the parameter Environment is Test BUT exit code is now 0 again! So what now…

Solution:

Powershell is by default continuing when "minor" error occurs. I guess the Powershell-runner has done something with that and therefore it works using the –Command switch. I also guess they have forgotten that errorhandling when creating the –File switch. When setting Script=SourceCode all the code is wrapped in a temporary script and you have to take care of the errors yourself. One solution that at first glance worked for me was to wrap my script in a try catch and then just have a ‘throw’ in my errorhandling. What was not so good about this is that the exception itself was suppressed and therefore not visible in the build output.

So the best solution I found is to add the following code (see the first line) in Team City’s Source Code-field:

$ErrorActionPreference = ‘Stop’

(…)/ConfigTransform.ps1 –Enviroenment Test

And then finally everything worked as I wanted. I got exit code 1 in the examples above. Smile

  • http://blog.degree.no/bloggere/ Njål

    Nice blogpost! I love to learn new powershell stuff. We use TeamCity and CTT to transform various config files as well. But we invoke ctt.exe directly – like this: http://screencast.com/t/KwlPegT98O

    Could I ask what the purpose of invoking ctt.exe through powershell is?

  • thorhalvor

    All my buildconfigurations has dependency to a project I call “CI-Tools”. This project has an artifact with all the latest Tools and Scripts. So my ctt.exe is in the Tools folder (checked in to sourcecontrol) and my ConfigTransform.ps1 is located in the Scripts folder. This is to keep track of changes in Tools and Scripts also. It is almost impossible to track changes in buildsteps over time in Teamcity. and compare between them. When as much as possible is in sourcecontrol this is easy.

    Since this is really a “one-liner” calling ctt.exe, as you point out, you could offcourse do it like you are doing. But I also have logging and some tiny logic. You might want to handle some of the errors that might occur when calling ctt.exe. -configs not found, config already exist, etc etc. And some times you want to inject something into the config file based on other criterias or artifacts. My ConfigTransform is responsible for all changes i need to do with to config before i put it into a deployable package. not just the xslt transformations.

    But the same scenario about “exit code 0” when it should be “exit code 1” applies to all calls to external executables within a .ps1. (psake.exe, psexec.exe, .bat etc)

  • thorhalvor

    I would suggest one of my colleagues blog if your are interested in cool stuff and tweeks in Teamcity:
    Gøran Kvarv: http://gorankvarv.blogspot.com/

  • Graham

    I use $ErrorActionPreference = “Stop”, but still get exit code 0 :(

    # wtf.ps1
    $ErrorActionPreference = “Stop”;

    $null.split()

    # cmd
    powershell -file wtf.ps1
    echo %errorlevel%

    • thorhalvor

      Hi Graham,
      you are right. my blogpost was not specific enough and i didnt write all the possibilites.
      My scenario was that i had to add arguments. When I used File/File it never worked no matter what i added in ErrorAction. When using File/Command it worked but then i couldnt add arguments.
      Using SourceCode/File (like you might did) nothing helped.. When using SourceCode/Command then it started to listen to ErrorAction. And that is what i am using now. Dont have time to go in depth to understand the reason for this though..

      ScriptType ExecutionMode $ErrorActionPreference TeamCityExitWithCode Comment
      FILE -FILE Continue or Stop 0 should be 1
      FILE -Command Continue or Stop 1 fails, but i cant add arguments
      Source code -FILE Continue or Stop 0 !!! nothing helps…
      Source code -Command Continue 0 Here it silently continues
      Source code -Command Stop 1 Here it stopps