home learn tableau about
SDLC VS 2010 Coded UI Test Coded UI Test and Fitnesse MSBuild 4.0 MSBuild and IronPython MSBuild and IronPython - TFS checkins MSBuild and IronPython - Custom SQL Data

previous next

MSBuild 4.0 Custom Task Walkabout Part 3

With the two new Workflow Activities in place, I first make two edits to the properties for WriteBuildMessage: 1) add some random text to Message simply so the error icon goes away and, 2) set the Importance to High. This latter step is helpful so that the text will be written to the build log even when the logging verbosity is set to its default Normal level. The alternative would be to increase log verbosity level but that will slow the build down and while in play-around mode a quick turnaround is important:

Properties for WriteBuildMessage

Now to the MSBuild activity, the properties for which are a tad more complicated:

Properties for MSBuild

The one property that requires a value, at least in order to get rid of the error icon in the workflow designer, is the project that is going to be built. It expects a string I'm going to set it to that AllInOneTargets.targets file that was used earlier in this walkabout:

System.IO.Path.Combine(SourcesDirectory, "AllInOne\BuildProcessTemplates\CustomTasks\_targets\

where SourcesDirectory should resolve to the build agent's local build directory at buildtime. With that change the .targets file also needs to be updated for the new workflow scenario. In the earlier .csproj -> .targets process all that the latter file contained was a UsingTask element but it should now be fully populated so that the ProduceHashManifest custom task is properly executed:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="CTBinaries.ProduceHashManifest" AssemblyFile="..\_compiled\CTBinaries.dll" />
  <Target Name="CustomAfterBuild"  >
    <ProduceHashManifest BinariesFolder="$(OutDir)" />

With that done, time to check in the process template .xaml...


It is important to remember that your process template file is just a Workflow Activity XAML file checked in to version control. The build will use the latest version of that file at run - time. However, both the build definition editor and the Queue Build dialog base what they display on the latest version of the process template that is checked in to version control, not the version you have locally. While this is obvious, it is surprisingly easy to forget. You will not be alone if you get caught on this as you start experimenting with customizing build processes.
   - pp501-2 of Professional Application Lifecycle Management with Visual Studio 2010
Verily, I can attest as to the truthiness of the above warning. To aid in the troubleshooting/testing of build process customization I would also recommend keeping the 'Pending Changes' window of Visual Studio in view at all times. It may also make sense to set up the trigger for the build-definition-under-test to Continuous Integration, as the most likely reason for checking in files during the play-around phase will be in order to test updated Workflow etc. via a new build.

(and AllInOneTargets.targets) and give it a go. And the go fails:

Build Summary - Failure
 (37): The "ProduceHashManifest" task was not given a value for the required parameter "BinariesFolder".

MSBuild claims the BinariesFolder property, which was [Required] within the custom task .dll, was not passed in. The short reason why is that while the $(OutDir) reference in the .targets file worked fine when evaluated in the .csproj, in the current flow that property has not been defined. I go looking for another property that will work out and try a bunch by adding them to the CustomAfterBuild target in the .targets file:

    <Message Text="outdir: $(OutDir)" Importance="High" />
    <Message Text="BinariesRoot: $(BinariesRoot)" Importance="High" />
    <Message Text="BuildDirectory: $(BuildDirectory)" Importance="High" />
    <Message Text="MSBuild: $(MSBuild)" Importance="High" />
    <Message Text="MSBuildBinPath: $(MSBuildBinPath)" Importance="High" />

but none of the ones I used evaluated to the correct path. Some had values that perhaps could be parsed down to the output directory value I need, and the exact property probably does exist, but the issue also provides the opportunity to look at passing properties from the Workflow to the msbuild process. In order to illustrate that process I will first take a step back and look at a few of the properties available in the MSBuild activity within the Workflow designer.

(some) Properties for MSBuild Workflow Activity
  • DisplayName
    • simple but very useful for debugging purposes. I will rename the current activity as 'MSBuild of AllInOneTargets'.
  • LogFile and LogFileDropLocation
    • which I had already set before running above .targets containing the list of properties to be evaluated. Both expect strings, I enter "myTargetsLog.log" for the former and 'logFileDropLocation' variable for the latter. The result is the generation of a new myTargetsLog.log in the \logs directory of any new builds. As opposed to the primary team build->msbuild->proj log file, this one contains information directly relating to the 'MSBuild of AllInOneTargets' (and reveals that the a number of the properties I passed in did not exist at build time and were evaluated as an empty strings):
          MSBuildBinPath: c:\Windows\Microsoft.NET\Framework\v2.0.50727
  • Targets
    • the current AllInOneTargets.targets file only contains a single Target, which msbuild will run since it would then be considered the default target. But if there are multiple targets in that file I need to be able to specify the ones I want run by this Activity. The Expression Editor indicates this property expects an 'IEnumerable<String>' and rather than try to figure out what form this would take in VB I go to an exising MSBuild activity already embedded in the Workflow. From the current location I can go up a couple of levels and click on 'Run MSBuild for Project'. There I see Targets is actually empty but TargetsNotLogged is defined in a similar manner and has a value of New String() {"GetNativeManifest", "GetCopyToOutputDirectoryItems", "GetTargetPath"}. Back to 'my' Targets and set value to New String() {"CustomAfterBuild"}, the name of the Target in AllInOneTargets.targets that holds the call to ProduceHashManifest.
  • CommandLineArguments
    • And this is how I will pass the desired output directory value on to the .targets file, where it will then be passed on to the ProduceHashManifest task (and .dll). That is the only command line modification I need right now so this property gets set to String.Format("/p:OutputDirectory={0}", outputDirectory).

With so many types of properties floating around (and so many things being referred to as Properties) it can get very confusing as to what text needs to be used where. This is where the WriteBuildMessage activity that was inserted just before the custom MSBuild comes in handy. Remove the dummy text there and replace with:
 "bldMessage1  CompilationStatus: " + BuildDetail.CompilationStatus.ToString() + "  ||  " + _
 "Binaries directory: " + BinariesDirectory + "sources directory: " + "  ||  " + _
 SourcesDirectory + "output directory: " + outputDirectory
Once the build has completed, going to 'View Log' from the Build Summary within Visual Studio IDE to see the results.
    bldMessage1  CompilationStatus: Unknown  ||  Binaries directory: C:\Builds\1\AllInOne\AllInOne_ManualDef1\Binaries
    ||  sources directory: C:\Builds\1\AllInOne\AllInOne_ManualDef1\Sources  ||  output directory: 

The Workflow part of the next test has now been set up but the contents of AllInOneTargets.targets needs to be cleaned up to remove the debugging Message tasks and configure ProduceHashManifest to expect the new OutputDirectory property that it is being passed on the command line:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="CTBinaries.ProduceHashManifest" AssemblyFile="..\_compiled\CTBinaries.dll" />
  <Target Name="CustomAfterBuild"  >
    <ProduceHashManifest BinariesFolder="$(OutputDirectory)" />

Confirm all relevant files have been checked into TFS and kick off a new build. This one succeeds and the expected hashManifest.txt is written to the build's output directory.

One area worthy of further investigation concerns the details of the custom MSBuild activity, as recorded by the team build process. Within Visual Studio, look at the details on the build that just suceeded and click 'View Log' from the Summary screen. Within the Activity Log is a section that relates directly to the custom flow I inserted:

log file for custom MSBuild activity

After the WriteBuildMessage output we can see how the DisplayName (MSBuild of AllInOneTargets) I used in the .xaml helps delineate where the new MSBuild Activity customization began. The target name, CustomAfterBuild, appears right below that and also in the actual MSBuild command line, passed with the /t: parameter. Logfile is set to the name I entered, myTargetsLog.log, without further pathing since it is being generated in the default log directory. Finally, OutputDirectory value is passed as a standard MSBuild Property, using /p: syntax.

Next, Configure as an Inline Task...

previous next