TFS 2015 – MSBuild in custom step

By Mirek on (tags: build, ci, msbuild, Powershell, tfs, categories: tools, infrastructure)

If you didn’t yet happen to create a custom build step for Team Foundation Server 2015 take a look at my previous posts here and here. Today I will show you the right (IMHO) way of calling MSBuild from the custom build step.

I had a task to create a custom build step which took the list of projects names and has to publish a ClickOnce installers for them. It first searches for the projects files in the source code, then it runs a MSBuild command with a Publish target for each of the projects. It could be of course done by adding as many MSBuild steps as many ClickOnce projects we have and setting the proper arguments in each of the steps. That would however be problematic and error prone if we have a lot of ClickOnce projects.

My first approach to handle it was to simply find the msbuild.exe on the build machine, take the full path and paste it in the step script like this:

$msbuild = "C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild.exe"
if (-not (Test-Path $msbuild))
    Write-Error "MSBUILD could not be found at $msbuild !"
    exit 1

then simply calling this

& $msbuild $($projpath.FullName) /target:Publish /p:Configuration=$env:BuildConfiguration /p:Platform=$env:BuildPlatform

foreach of the projects. Later on I started to thinking if this is the right way of doing it and decided to look how is this done in default build steps. So I went to vso-agent-tasks and downloaded all tasks. I found the answer in VSBuild task, in LegacyVSBuild.ps1 file exactly.
It turned out that there is a bunch of powershell modules available in your build step which you can benefit from. On the build agent machine they are physically located under build agent directory \Worker\Modules. There were not much documentation available on this topic at the time of writing this post, hope that will change soon, but there is a blog post of Mario Majčica which did the job and listed all modules including the cmdlets available.

In our case we only need one module to be imported into our build step script

import-module "Microsoft.TeamFoundation.DistributedTask.Task.Internal"

Then we can take the MSBuild location using appropriate command

$MSBuildLocation = Get-MSBuildLocation
if (-not (Test-Path $MSBuildLocation))
    Write-Error "MSBUILD could not be found !"
    exit 1

By default it will return the latest available version of MSBuild which is good. Then we can simply invoke the MSBuild with our parameters like so

Invoke-MSBuild $projpath.FullName -ToolLocation $MSBuildLocation -Targets Publish -CommandLineArgs $projectargs 

Apart from the above Invoke-MSBuild handles the logging by default, so we don’t have to worry about it as well.