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 !"
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
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 !"
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.