Executing psake build scripts from TeamCity

I came across an issue with a few of our TeamCity build configurations the other day; We had a situation where TeamCity was reporting ‘Success’ instead of ‘Failure’, lucky we spotted this early.

Executing psake build scripts from TeamCity



As can be seen above the build is failing with the following zero exit code.

[code language=”bash”]
`default.ps1:Error executing command: msbuild $sln_file /t:Build /p:Configuration=Release /v:q [17:15:08]: Process exited with code 0`

I had a sniff around and came to the conclusion the problem was in the way we were calling our psake build script from within a batch file. After a bit of googling I found this is a common stumbling block, there were some rather confusing and over the top approaches but I finally found this very useful article, this post builds on this approach and adds a bit more detail.

So we identified we were doing the following in our `build.bat`:

[code language=”powershell”]powershell.exe -command "& {.build.ps1 Package}"[/code]

This is potentially wrong due to the following reasons:

  1. PowerShell by default continues running scripts on errors
  2. The executing batch file `build.bat` was not trapping the error status returned from the psake PowerShell script `default.ps1`.
  3. We weren’t importing the psake PowerShell module and therefore the $psake hashtable variables weren’t getting set.

I didn’t need to do anything for point 1 as we had however already implemented the exec {} helper function which forces psake to throw an error.

To resolve point 2 I referred to Dusty’s article , split it out a bit more and implemented the following in the original batch file `build.bat` and new build.ps1 script respectively

[code language=”bash”]@echo off
powershell.exe -NoProfile -ExecutionPolicy unrestricted -command ".build.ps1 %1;exit $LASTEXITCODE"

[code language=”bash”]
if %ERRORLEVEL% == 0 goto OK
echo ##teamcity[buildStatus status=’FAILURE’ text='{build.status.text} in compilation’]



[code language=”powershell”]
param([string]$task = "compile")
Import-Module ‘.libpsakepsake.psm1’;
Invoke-psake -t $task;
if ($Error -ne ”)
Write-Host "ERROR: $error" -fore RED;
exit $error.Count
Remove-Module psake -ea ‘SilentlyContinue’;

The above do the following:

  • `build.bat` wraps `build.ps1` and executes it, passing the first parameter passed to the batch file in, i.e. the psake task.
  • `build.ps1` then starts by importing the psake PowerShell module (we have this in a relative path in source control).
  • `build.ps1` then invokes the default psake build script in the current working folder i.e. `default.ps1`, passing the first parameter passed to the batch file in as the psake task. `Default.ps1` obviously does all the build work for us.
  • the if block then simply checks the Powershell $error array and returns a count of the errors to `build.bat`
  • `build.bat` echo’s out a nice TeamCity buildStatus which then shows up in the TeamCity gui.
  • `build.bat` fails with the ERRORLEVEL returned by `build.ps1`.
  • finally the psake PowerShell module is removed.

Point 3 was resolved above by explicitly importing the psake PowerShell module. I also tapped the following into a PowerShell command line.

[code language=”powershell”]Get-Help Invoke-psake –full[/code]

Which gives, amongst other detail, the following Output definition.

[code language=”bash”]…..
If there is an exception and ‘$psake.use_exit_on_error’ -eq $true
then runs exit(1) to set the DOS lastexitcode variable
otherwise set the ‘$psake.build_success variable’ to $true or $false depend
on whether an exception was thrown


When the psake module is loaded a variabled called $psake is created it
is a hashtable
containing some variables that can be used to configure psake:

$psake.use_exit_on_error = $false # determines if psake uses the "exi
t()" function when an exception occurs
$psake.log_error = $false # determines if the exception detai
ls are written to a file
$psake.build_success = $false # indicates that the current build
was successful
$psake.version = "4.00" # contains the current version of p
$psake.build_script_file = $null # contains a System.IO.FileInfo for
the current build file
$psake.framework_version = "" # contains the framework version #
for the current build

$psake.use_exit_on_error and $psake.log_error are boolean variables tha
t can be set before you call Invoke-Psake.

You should see the following when you display the contents of the $psak
e variable right after importing psake

PS projects:psake> Import-Module .psake.psm1
PS projects:psake> $psake

Name Value
—- —–
version 4.00
use_exit_on_error False
build_success False
log_error False

I therefore modified the `build.ps1` psake script above set the $psake.use_exit_on_error variable to true and forced it to always use the 64 bit version of the .NET 3.5 framework:

[code language=”powershell”]param([string]$task = "compile")
Import-Module ‘.libpsakepsake.psm1’;
$psake.use_exit_on_error = $true
Invoke-psake -t $task -framework ‘3.5×64’;
if ($Error -ne ”)
Write-Host "ERROR: $error" -fore RED;
exit $error.Count
Remove-Module psake -ea ‘SilentlyContinue’;

Executing the batch file on the command line within my development environment was now returning the correct results. Therefore I purposefully checked in a dodgy change into our Source control system (TFS) and watched a TeamCity build kick off……

The correct error code and the buildStatus from `build.bat` displaying nicely.



….much to my delight the build failed with the correct error code this time

[code language=”bash”]
[13:47:47]: ControllersAccountController.cs(73,62): error CS1002: ; expected
[13:47:48]: default.ps1:Error executing command: & msbuild $sln_file /t:Build /p:Configuration=Release /v:q
[13:47:48]: ##teamcity[buildStatus status=’FAILURE’ text='{build.status.text} in compilation’]
[13:47:48]: Process exited with code 1

No more fibbing build status’ for us.