77

I've got a PowerShell script as follows

##teamcity[progressMessage 'Beginning build']
# If the build computer is not running the appropriate version of .NET, then the build will not run. Throw an error immediately.
if( (ls "$env:windir\Microsoft.NET\Framework\v4.0*") -eq $null ) {
    throw "This project requires .NET 4.0 to compile. Unfortunately .NET 4.0 doesn't appear to be installed on this machine."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Setting up variables']
# Set up variables for the build script
$invocation = (Get-Variable MyInvocation).Value
$directorypath = Split-Path $invocation.MyCommand.Path
$v4_net_version = (ls "$env:windir\Microsoft.NET\Framework\v4.0*").Name
$nl = [Environment]::NewLine

Copy-Item -LiteralPath "$directorypath\packages\NUnit.2.6.2\lib\nunit.framework.dll" "$directorypath\Pandell.Tests\bin\debug" -Force

##teamcity[progressMessage 'Using msbuild.exe to build the project']
# Build the project using msbuild.exe.
# Note we've already determined that .NET is already installed on this computer.
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Release
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Debug

# Break if the build throws an error.
if(! $?) {
    throw "Fatal error, project build failed"
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Build Passed']
# Good, the build passed
Write-Host "$nl project build passed."  -ForegroundColor Green


##teamcity[progressMessage 'running tests']
# Run the tests.
cmd /c $directorypath\build_tools\nunit\nunit-console.exe $directorypath\Pandell.Tests\bin\debug\Pandell.Tests.dll

# Break if the tests throw an error.
if(! $?) {
    throw "Test run failed."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Tests passed']

From what I'm lead to believe, an uncaught Throw will result in an exit code of 1, but unfortunately TeamCity is saying otherwise.

[19:32:20]Test run failed.
[19:32:20]At C:\BuildAgent\work\e903de7564e599c8\build.ps1:44 char:2
[19:32:20]+     throw "Test run failed."
[19:32:20]+     ~~~~~~~~~~~~~~~~~~~~~~~~
[19:32:20]    + CategoryInfo          : OperationStopped: (Test run failed.:String) [],
[19:32:20]   RuntimeException
[19:32:20]    + FullyQualifiedErrorId : Test run failed.
[19:32:20]
[19:32:20]Process exited with code 0
[19:32:20]Publishing internal artifacts
[19:32:20][Publishing internal artifacts] Sending build.finish.properties.gz file
[19:32:20]Build finished

It might also be important to note that my Execution Mode is set to Execute .ps1 script with "-File" argument.

I tried changing it to Put script into PowerShell stdin with "-Command -" arguments, but then it failed with an exit code of 1 even with passing tests. I'm sure that running it as -File is going to be the right way.

If I open up the script located at C:\BuildAgent\work\e903de7564e599c8\build.ps1 and run it manually in CMD, it does the same thing... I.e., the failing tests fail, and the %errorlevel% is still 0.

Yet, if I run it in PowerShell and call $LASTEXITCODE, it returns the right code every time.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Chase Florell
  • 42,985
  • 56
  • 169
  • 364

5 Answers5

97

This is a known issue with PowerShell. Executing a script with -file returns an exit code of 0 when it shouldn't.

(Update: The links below no longer work. Please look for, or report, this problem on PowerShell: Hot (1454 ideas) – Windows Server)

Since using -command wasn't working for you, you could try adding a trap at the top of the script:

trap
{
    write-output $_
    ##teamcity[buildStatus status='FAILURE' ]
    exit 1
}

The above should result in a proper exit code when an exception is thrown.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Kevin Richardson
  • 3,482
  • 19
  • 13
  • 2
    Can you explain "why" this trap would work? I'll test it tomorrow when I'm back at the project. – Chase Florell Apr 03 '13 at 05:40
  • perfect, that worked. It's interesting that this is what's going on. Kind of frustrating actually. I did quite a lot of searching before asking this question, although I "assumed" it was an issue with my teamcity command and not a bug in powershell it's self. – Chase Florell Apr 04 '13 at 02:42
  • 5
    While suggested `write-output $_` is working `Write-Error -ErrorRecord $_` produces fancy error output just like PowerShell itself – Mike Jun 06 '13 at 21:19
  • Is there a reason to use '$_' and the string on new a line rather than Write-Output "##teamcity..." all on one line? – Robin Aug 13 '13 at 10:01
  • The key part of this is exiting with status=1 from the `trap`. You can structure the output how you like and/or do other things before exiting. – Kevin Richardson Aug 19 '13 at 05:46
  • While this is all factual and the solution works, I believe [my answer](http://stackoverflow.com/a/37025444/11635) works equivalently without necessitating warts in ones scripts -- its unfortunately just not very well documented in TeamCity. See http://stackoverflow.com/questions/11647987/how-do-i-get-errors-to-propagate-in-the-teamcity-powershell-runner – Ruben Bartelink May 04 '16 at 10:46
  • 2
    If using `Write-Error` as suggested by @Mike make sure your `$ErrorActionPreference` is not set to "Stop", otherwise that will cause the script to exit with code 0 and `exit 1` will never be reached. – EM0 Aug 24 '16 at 18:10
  • The last 2 two links are (effectively) broken: *"Microsoft Connect Has Been Retired"* – Peter Mortensen Nov 06 '18 at 20:33
25

I was having this exact issue while running with the -file, but for some reason the trap syntax or the 'exit' syntax provided by Kevin wasn't working in my scenario.

I am not sure why, but just in case somebody else hits the same problem, I used the below syntax and it worked for me:

try{
    #DO SOMETHING HERE
}
catch
{
    Write-Error $_
    ##teamcity[buildStatus status='FAILURE']
    [System.Environment]::Exit(1)
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Jay S
  • 7,774
  • 2
  • 35
  • 51
17

Until this (presumably) gets closed as dup of my self-answer of an older question, I'll summarise the cleanest solution here:

  • Most of the other answers involve emitting something to the stderr from the PowerShell bit. This can be accomplished directly with TeamCity via the Format stderr output as option (set it to Error instead of the default, which is Warning)

  • However, critically, it's also necessary to switch on the "Fail build if: ... An error message is logged by (sic) build runner" under "Failure Conditions" (if any of the other answers work for you, you'll likely have this switched on already but IME it's very easy to forget!)

Community
  • 1
  • 1
Ruben Bartelink
  • 55,135
  • 22
  • 172
  • 222
  • 3
    This is by far the cleanest solution on this page – lantrix Mar 08 '17 at 03:03
  • This is the true solution. Thank you. – Michiel Bugher Apr 16 '18 at 13:26
  • 1
    This will fail your build, but all of your subsequent build steps, even if set to only run on success, will still execute – Craig Brett Oct 10 '19 at 08:46
  • @craigBrett that's obviously a big problem if confirmed - I am not using TC atm (sigh!) so if this comment and [this one](https://stackoverflow.com/users/18966/dave-turvey) can be resolved to something conclusive for other readers that'd be a great help for everyone – Ruben Bartelink Oct 10 '19 at 09:00
  • This is going from the current issue I'm trying to solve. So that's why I shared my experience. But I agree this problem probably gets quite a lot of people. – Craig Brett Oct 11 '19 at 11:11
2

Using -ErrorAction stop on a command returns an Exit code 1 by default and is showing it also in TeamCity without adding a Failure Condition. We will now implement this behaviour by default for every PowerShell command using $ErrorActionPreference = "Stop";.

Bart VdA
  • 397
  • 1
  • 3
  • 12
  • I tried a lot of the methods on this site and others, but none worked. Perhaps because my powershell one-liner: `(Get-Content -path package.json -Raw) -replace '"version": "0.0.0"','"version": "0.0.1"' | Set-Content -Path package.json` doesn't throw an exception? Anyway I instead used _just_ the above -ErrorAction, and now the step fails e.g. if the file is missing. I added it to both the Get-Content and the Set-Content – ColH Nov 22 '19 at 12:36
1

None of these options worked for me in my PowerShell script for whatever reason. I spent hours on it.

For me the best option was to put a layer between TeamCity and PowerShell. So I simply wrote a C# console application which calls the PowerShell script.

The way I do it is, in TeamCity we call a script named: RemoteFile.ps1

With script arguments: %system.RemoteServerFQDN% %system.RemoteUser% %system.RemoteUserPassword% %system.RemoteScriptName% %system.RemotePropertiesFile% %system.BuildVersion% %system.RunList%

param (
    [Parameter(Mandatory=$true)]
    $Computername,
    [Parameter(Mandatory=$true)]
    $Username,
    [Parameter(Mandatory=$true)]
    $Password,
    [Parameter(Mandatory=$true)]
    $ScriptName,
    [Parameter(Mandatory=$true)]
    $Propfile,
    [Parameter(Mandatory=$true)]
    $Version,
    [Parameter(Mandatory=$true)]
    [string[]]$DeploymentTypes
)

$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
Write-Host "Readying to execute invoke-command..."
Invoke-Command -ComputerName $Computername -Credential $cred -ScriptBlock {       D:\Deployment\PowershellWrapper.exe $using:ScriptName $using:Propfile $using:Version      $using:DeploymentTypes } -ArgumentList $ScriptName,$Propfile,$Version,$DeploymentTypes

Which exists on the remote server in the specified location.

Then that file calls this: powershellwrapper.exe also in the specified location (my script has four parameters to pass to the PowerShell script)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace PowershellWrapper
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string argFull = @"""{0} {1} {2} {3}""";
                string arg0 = args[0];
                string arg1 = args[1];
                string arg2 = args[2];
                string arg3 = args[3];
                string argFinal = string.Format(argFull, arg0, arg1, arg2, arg3);

                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = @"powershell.exe";
                startInfo.Arguments = argFinal;
                startInfo.RedirectStandardOutput = false;
                startInfo.RedirectStandardError = false;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardInput = true;
                startInfo.CreateNoWindow = false;
                Process process = new Process();
                process.StartInfo = startInfo;
                process.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("{0} Exception caught.", e);
                Console.WriteLine("An error occurred in the deployment.", e);
                Console.WriteLine("Please contact test@test.com if error occurs.");
            }
        }
    }
}

And that calls my script with four parameters. The script being the first parameter, plus three arguments. So essentially what is going on here is that I'm executing the PowershellWrapper.exe instead of the PowerShell script itself to capture the erroneous exit code 0's, and it still reports the full script running back to the TeamCity log.

I hope that makes sense. It works like a charm for us.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
mumbles
  • 569
  • 1
  • 5
  • 17
  • Sholdnt be necessary [my answer](http://stackoverflow.com/a/37025444/11635) seems to work equivalently without requiring warts in one's script and/or other helpers -- its unfortunately just not very well documented in TeamCity. See http://stackoverflow.com/questions/11647987/how-do-i-get-errors-to-propagate-in-the-teamcity-powershell-runner – Ruben Bartelink May 04 '16 at 10:48