I have recently started using psake to do some build automation at work, and I’ve found that there is not a great deal of information about how to write a build script using psake available on the internet. It isn’t all that amazingly difficult if truth be told, however there are a couple of ‘gotchas’, and I would like to share what I have learned in the hopes that it benefits someone.

If you have not heard of it, psake is a”…build automation tool written in PowerShell”, started by James Kovacs. With it, you can write build scripts, with which you can automate the build and deployment of your .NET project. Recently the version 1.00 and 2.0 0 version were released. Why two versions? Well, the 1.00 version is primarily for PowerShell 1.0, but psake 1.00 is being “retired”. This article assumes the reader is using psake 2.01 and PowerShell 2.0.

A quick note here, I should point out the primary example I based my first build script on is Ayende Rahien’s build script for Rhino Mocks.

Our first psake build script

First, I have to make two assumptions:

  1. I have to assume that you have PowerShell installed, if you are running Vista/Windows 7 then it's installed by default, if you are on XP, then it is a manual install.
  2. You have installed the psake module into Powershell - see the release announcement linked to above for details on how to do this.

[caption id=”attachment_120” align=”alignleft” width=”137” caption=”Example solution”]Example solution[/caption]

With those assumptions out of the way, we are going to write a build script to automate the building of a simple C# solution, containing a Windows Forms application, and two class library assemblies.

Hopefully this should be simple enough to easily follow along with what is happening in the build script, but complex enough that you can see how sophisticated your build scripts can be.

You can see that I have already taken the liberty of adding an extra file to the solution, “build.ps1”, this is our build script, with which we can command psake to do great things for us.

With our solution setup, we can now write our first build script:

properties {
 $base_dir = Resolve-Path .
 $sln_file = "$base_dirWindowsFormsApplication.sln"
}

Task default -depends Compile

Task Compile {
 msbuild "$sln_file"
}

Open a command prompt into the directory containing the .sln file, which is where your build.ps1 script should be, and run this command: invoke-psake .build.ps1 -taskList Compile

[caption id=”attachment_123” align=”aligncenter” width=”300” caption=”psake Compile task output”]psake Compile task output[/caption]

You should receive some output to the console window like the above.What the script does is to work out where on the file system the script is running, and gets the path to that folder, and builds the path to the specified solution file, and runs msbuild, passing the full path to the solution file as a parameter.

The script as it is has some drawbacks though. In it’s current form, msbuild will only build the default configuration of the solution. What if we want to build a Debug version, or a Release version, or some other configuration we’ve created? Additionally, it doesn’t let us specify a directory to put the built binaries, they just get put in the default locations as specified in the projects contained in the solution - what if we want to put them in a custom location?

If we modify our build script slightly, we can introduce this functionality. Firstly, we need to specify some additional properties:

properties {
 $base_dir = Resolve-Path .
 $build_dir = "$base_dirbuild"
 $sln_file = "$base_dirWindowsFormsApplication.sln"
 $debug_dir = "$build_dirDebug\"
 $release_dir = "$build_dirRelease\";
}

Notice the double backslash, msbuild requires a trailing slash when paths are specified, and it seems to require the additional backslash as well, or else it doesn’t work. With those additional properties in place, we can introduce two new tasks to our build script:

Task Clean {
 remove-item -force -recurse $debug_dir -ErrorAction SilentlyContinue
 remove-item -force -recurse $release_dir -ErrorAction SilentlyContinue
}

Task Init -depends Clean {
 new-item $debug_dir -itemType directory
 new-item $release_dir -itemType directory
}

Powershell’s easy to read syntax should make it easy to follow with what is happening now. In the Clean task, we forcefully and recursively remove any files and the folder from the specified path, and if there are any errors then they are not displayed. Now that we know that those folders are going to be cleaned and created, we can create two further tasks, where we can create Debug and Release versions of our solution:

Task Debug -depends Init {
 msbuild $sln_file "/nologo" "/t:Rebuild" "/p:Configuration=Debug" "/p:OutDir=""$debug_dir"""
}

Task Release -depends Init {
 msbuild $sln_file "/nologo" "/t:Rebuild" "/p:Configuration=Release" "/p:OutDir=""$release_dir"""

Again, the syntax here is relatively straightforward to follow along with, we execute msbuild, passing it the solution file to build, specify not to show the logo (suppressing the output of “copyright microsoft msbuild etc), tell it to execute the rebuild target and to build the Debug configuration, copying the output to the specified debug directory. Notice that that there are no spaces in the paramters that we pass to msbuild. For example, if we passed the parameters like this: “/p:Configuration=Release /p:OutDir=”“$release_dir”””, then it would fail and we would get a msbuild parse error saying it was invalid.

[caption id=”attachment_129” align=”alignleft” width=”196” caption=”Build script output for Debug task”]Build script output for Debug task[/caption]

Summary

In a relatively short amount of code, less than 30 lines, we have accomplished quite a lot. We can now issue the command: invoke-psake .build.ps1 -taskList Debug, and psake will automatically clean the output folders, do a full rebuild of the Debug configuration and copy the output to a custom location on our filesystem. What’s more, the build script we have written is small, compact, easy to read, easy to maintain and easy to build/extend upon in the future.

As this is getting a bit long already, I’m going to cut things short here, however, there are some additional things that you can do as part of the build script that are very nice, such as automatically versioning the assembly before you do the full build. If you take a look at Ayende Rahien’s example from Rhino Mocks, that is covered there.

Also in the new version of psake, there are pre and post conditions and actions that you can add onto your tasks, although I haven’t had the opportunity to use them yet. I’ll try to cover those in a future blog post though.