One of the goals of build automation is to create a robust and reliable process that can be depended upon. To achieve this, our build script needs to be smart, transparent and maintainable.

  • Smart because we don’t want our builds to break when really simple things change.
  • Transparent because we need to be able to see what the script did.
  • Maintainable because we want anyone on the team to be able to maintain the build.

Currently, the build script we wrote last week expects that msbuild will be available on the PATH. If it isn’t available, the error message isn’t really that helpful:

Exception: The term 'msbuild' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path was
included, verify that the path is correct and try again.

It’s not a terrible error message - at least it tells you the name of the program that couldn’t be found.

But, when our build stops working, we don’t want people to be guessing about why - and having to find the right error message in the midst of all the other output is a pain.

Let’s add a new task to our build script, one that finds msbuild for us and generates a good diagnostic message if it couldn’t be found.

Task Requires.MSBuild {

    $script:msbuildExe = 
        resolve-path "C:\Program Files (x86)\Microsoft Visual Studio\*\*\MSBuild\*\Bin\MSBuild.exe"

    if ($msbuildExe -eq $null)
        throw "Failed to find MSBuild"

    Write-Host "Found MSBuild here: $msbuildExe"

There are a few interesting things going on here.

Note how we use resolve-path to find MSBuild without specifying the entire path. The wildcards (*) in the path get expanded appropriately, making the script somewhat adaptable if a different edition of Visual Studio is installed on the machine.

If the search for MSBuild fails, we throw an error which gets reported on the console:

Exception: Failed to find MSBuild

If the search works, we get a message telling us exactly which version has been found - useful if we start finding that builds on different machines are delivering different results.

Found MSBuild here: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe

Using the $script scope when we assign to $msBuildExe is necessary because each Psake task runs in a separate scope; without the $script modifier, our assignment would be abandoned as soon as the task completed.

To hook this into our original task, we make a couple of minor changes:

Task Compile.Assembly -Depends Requires.MSBuild {

    exec { 
        & $msbuildExe .\Niche.CommandLine.sln

The new -Depends option tells Psake that the Compile.Assembly task cannot start until the Requires.MSBuild task has finished. We also needed to modify our command line to use $msbuildExe when running the build.

We’ve made our build smarter because now it finds msbuild for itself; it’s not dependent on it being on the PATH. We’ve made our build more transparent because it now tells us which version of msbuild is being used. We’ve ensured our task is maintainable by breaking the logic for finding msbuild out into it’s own well named task.

What else can we do to improve the robustness of our build script?


blog comments powered by Disqus