Passing SolutionDir to NAnt For Post-Build Event in TeamCity

I just spent the better part of a day trying to solve this problem, so I’m posting the solution for any who may encounter it in the future. ¬†You’re welcome ūüėČ

For a project I am currently working on, I needed to add a post-build event which invokes a PowerShell script.  The script performs some minor tasks involving a database and is called PostBuild-DbSync.ps1.  At the outset, this was a straightforward proposition.  I created the script file, then referenced it in the Build Events section in Visual Studio.

Powershell.exe -ExecutionPolicy Unrestricted -file “$(SolutionDir)Build\PostBuild-DbSync.ps1”

The Problem

Running the build locally in Visual Studio was successful, and I ran it repeatedly while tweaking the script. ¬†Once the script was perfected, I checked it in to our SVN repo, and that triggered an automated build on our TeamCity server. ¬†That’s when the problems began. ¬†The first build failed with the following error:

Processing -File ‘*Undefined*Build\PostBuild-DbSync.ps1’ failed: Illegal characters in path. Specify a valid path for the -File parameter.

The value for “$(SolutionDir)” was not being passed to the Nant file which was being used to run the build.

Attempt #1

After some research, I learned that you can specify the Solution Directory as a parameter to MSBuild. ¬†However, because TeamCity is running the builds, I don’t have a specific directory to use as the value for that parameter. ¬†It may change from one build to the next. ¬†But TeamCity does expose several predefined build parameters, including the working directory and the checkout directory. ¬†So I decided to use one of these parameters in my NAnt file to set the appropriate solution directory.

<arg value=”/p:SolutionDir=${teamcity.build.checkoutDir}” />

Unfortunately, this approach did not work.  I got a new error:

The argument ‘C:\TeamCity\buildAgent\work\86ef71b243852ab1Build\PostBuild-DbSync.ps1’ to the -File parameter does not exist.

So the path was getting passed into the NAnt process correctly, and to the post-build event. ¬†However, I was missing a slash at the end of the solution directory, so its not finding the PowerShell file. ¬†I’m close, right? ¬†It just needs a little more tweaking…

Attempts #2, #3, #4

This part of the process was the most frustrating of them all.  I went around and around trying to get that missing slash in the right place.  First I tried adding it to the post-build event.

Powershell.exe -ExecutionPolicy Unrestricted -file “$(SolutionDir)\Build\PostBuild-DbSync.ps1″

For some reason, that brought me back to the “*Undefined*” error that I got at first. ¬†Next I tried adding it in the NAnt file.

<arg value=”/p:SolutionDir=${teamcity.build.checkoutDir}\” />

Again,¬†“*Undefined*”. ¬†I even tried a forward slash, but that didn’t resolve the issue. ¬†The next approach was to edit the project file manually and try to specify the solution directory as a relative path there.

<SolutionDir Condition=”‘$(SolutionDir)’==” or ‘$(SolutionDir)’==’*Undefined*'”>..\</SolutionDir>

This attempt also failed, since the PowerShell command requires the full path to the file, not a relative path.

The Solution

For a few other values used by the NAnt file, I had set up environment variables in TeamCity. ¬†So I decided to use that approach to solve this problem. ¬†I set up an environment variable in the build configuration with the name ‘SolutionDir’ and the value ‘%teamcity.build.checkoutDir%\’. ¬†Notice that I’m using the predefined build parameter, and I can easily tack that extra slash on the end.

Now, I can go back to my NAnt file and set the SolutionDir parameter for MSBuild with the following line:

<arg value=”/p:SolutionDir=${environment::get-variable(‘SolutionDir’)}” />

With that, the build succeeds, and the post-build event processes flawlessly as well.