MSBuild Transforms

November 4, 2007

Passing a list of items to a task is pretty straightforward in MSBuild. Consider a simple itemgroup which contains a bunch of files.

<ItemGroup>
    <MyFiles Include="C:\Windows\Microsoft.NET\Framework\v2.0.50727\1033\*"/>
</ItemGroup>

We can call a task and pass our files as a list using @(MyFiles).

<Message Text="@(MyFiles)"/>

Target Build:
    C:\Windows\Microsoft.NET\Framework\v2.0.50727\1033\alinkui.dll;C:\Windows\Mi
crosoft.NET\Framework\v2.0.50727\1033\cscompui.dll;C:\Windows\Microsoft.NET\Fram
ework\v2.0.50727\1033\CvtResUI.dll;C:\Windows\Microsoft.NET\Framework\v2.0.50727
\1033\vbc7ui.dll;C:\Windows\Microsoft.NET\Framework\v2.0.50727\1033\vjscui.dll;C
:\Windows\Microsoft.NET\Framework\v2.0.50727\1033\vjslibui.dll;C:\Windows\Micros
oft.NET\Framework\v2.0.50727\1033\Vsavb7rtUI.dll
    alinkui;cscompui;CvtResUI;vbc7ui;vjscui;vjslibui;Vsavb7rtUI

Build succeeded.
    0 Warning(s)
    0 Error(s)

We may not always want to pass in the full file name and path, what we might want to pass is item metadata. This is where MSBuild transforms come in. They let us change which pieces of information are used in the construction of the list. The syntax takes the form of @(itemgroup->’%(itemmetadata)’)

We can pass a just the file names (no extension) taken from the FileName well known item metadata.

<Message Text=@(MyFiles->’%(FileName)’)/>

alinkui;cscompui;CvtResUI;vbc7ui;vjscui;vjslibui;Vsavb7rtUI

or the file name and extension

<Message Text="@(MyFiles->'%(FileName)%(Extension)')"/>

alinkui.dll;cscompui.dll;CvtResUI.dll;vbc7ui.dll;vjscui.dll;vjslibui.dll;Vsavb7rtUI.dll

or we might want to pass the list of files but with a different extension.

<Message Text="@(MyFiles->'%(FileName).data')"/>

alinkui.data;cscompui.data;CvtResUI.data;vbc7ui.data;vjscui.data;vjslibui.data;Vsavb7rtUI.data

Hosting MSBuild

October 29, 2007

Most people tend to use MSBuild via a few standard scenarios. Inside Visual Studio, from the command line and invoked by a continuous integration system such as Cruise Control are probably the most common.

The MSBuild framework is very flexible and we can very easily host the build engine ourselves with very little effort.

All we need to do is:

Add a reference to the MsBuild engine and framework.

image

Its often a good idea to run MSBuild in its own thread. If your application is running in an MTA thread then you’ll need to do this otherwise you will get a lot of warnings. If you want to see this try creating a Powershell script that invokes MSBuild through .net rather than calling msbuild.exe.

Create and invoke the engine:

Engine engine = new Engine();
engine.BinPath = @"C:\Windows\Microsoft.NET\Framework\v2.0.50727\";

ConsoleLogger logger = new ConsoleLogger();

engine.RegisterLogger(logger);

bool result = engine.BuildProjectFile(
    @"C:\Projects\MyProj.csproj",
    "Build");

if (result)
{
    MessageBox.Show("Build Suceeded");
}
else
{
    MessageBox.Show("Build Failed");
}

There are two loggers that come with the base framework, the ConsoleLogger and FileLogger. Its pretty obvious from there names as to what they do. If we are hosting msbuild then chances are they won’t meet all of there needs. Fortunately we can roll our own fairly easily by implementing the ILogger interface.

public class MyLogger : ILogger

Then set up some events to handle all of the logging.

public void Initialize(IEventSource eventSource)
{
    eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
    eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
    eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
    eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
    eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
    eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);

}

Write some handlers for these events. This is where we get the details of the error/warning/message and handle it as we please. It this example we are just writing it to the debug output window.

void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
{
    //Handle the logging event
    System.Diagnostics.Debug.WriteLine(e.Message);            
}

Its quite common to use pre and post build events in your c# projects. You enter the command you want to run in the project properties, like below.

Prebuild event

But this isn’t the only way to achieve this. There is another way by editing your .csproj file and using the BeforeBuild target. 

First lets look how the PreBuildEvent from the project properties is actually handled. At the bottom of your project file you’ll find a property that lists your command. 

<PropertyGroup> 
  <PreBuildEvent>copy $(OutDir)\test.blah c:\test</PreBuildEvent> 
</PropertyGroup>

If you follow your imports in your project file you’ll find that your PreBuildEvent is handled by the PreBuildEvent target in Microsoft.Common.Targets. What does this do? It calls the Exec task on your command, which runs in as any other command prompt command.

<Target 
    Name="PreBuildEvent" 
    Condition="'$(PreBuildEvent)'!=''" 
    DependsOnTargets="$(PreBuildEventDependsOn)">
    <Exec WorkingDirectory="$(OutDir)" Command="$(PreBuildEvent)" />
</Target>

In your .csproj file you will find out two targets (BeforeBuild and AfterBuild) that have been commented out. The other way is to uncomment these and add msbuild tasks. These targets are called exactly when their name suggests.

<Target Name="BeforeBuild"> 
</Target>

If we want to copy a file then we can use the Copy task

<Target Name="BeforeBuild"> 
       <Copy SourceFiles="c:\test.txt" DestinationFolder="d:\dest\"/> 
</Target>

No command line, no batch files. Just pure msbuild

In a previous post I mentioned about creating your final output media as part of the build process. This can often include CD or DVDs. A great format to have these in is a .iso file. This is a close to having an actual disk as you can get.

There is nothing in MSBuild to do this for you so i’ve rolled a custom task to do it. Creating an iso file is normally non-trivial but luckily for us Microsoft have added this functionality into the OS with IMAPIv2.

If your running Vista everything you need is installed. For XP Sp2 and Server 2003 you’ll need the following updates.

IMAPIv2 for Server 2003
IMAPIv2 for XP Sp2

IMAPIv2 is COM API which is easy to use in C#. The main implementation in the Execute method is quite simple. Setup the file system, get the root directory, add a tree of files, Create the image and finally copy the image stream to disk.

ifs.MsftFileSystemImage fs = new IMAPI2FS.MsftFileSystemImage(); 
ifs.FsiDirectoryItem dir = fs.Root; 
fs.FileSystemsToCreate = IMAPI2FS.FsiFileSystems.FsiFileSystemISO9660 | 
                          IMAPI2FS.FsiFileSystems.FsiFileSystemJoliet;fs.VolumeName = _volumeName; 
dir.AddTree(_treeRoot, false); 
ifs.FileSystemImageResult res = fs.CreateResultImage(); 
ifs.FsiStream stream = res.ImageStream; 
return CreateISOFile(_outputFileName, (System.Runtime.InteropServices.ComTypes.IStream)stream);

In your msbuild file, add a reference to the target:

<UsingTask TaskName="Belfield.Build.Tasks.CreateOpticalDiskImageTask" AssemblyFile="buildtasks.dll"/> 

To create the image call the task in one of your targets

<CreateOpticalDiskImageTask OutputFileName="c:\build\cd.iso" 
            TreeRoot="c:\build\output\" VolumeName="My Product" />

Download Source.

(Note: The source project is set to generate CD-ROM images at the moment, for DVDs you need to change the filesystem)

Your build is your friend

September 24, 2007

The build is at the heart of every software project and when starting a project it is always worth spending the time at the start to get it right. Gone are the days when the build was just hitting F5, it is an essential part of the project setup and infrastructure and its worth getting it right.

Continuous Integration is a must, every project should install Cruise Control.Net or similar. Your build process shouldn’t just stop at building the your C# projects. You should be able to produce a full end to end production build in a single go. This is so much more than just compiling code, it should be along the lines of:

  • Labeling and getting latest code from source control
  • Update version and build numbers (make sure those assembly info files are up to date)
  • Generating any data files your app needs
  • Generate release notes, if you’ve got a bug tracker (which you should) then use it to get a list of issues fixed in this build
  • Compiling help files
  • Compiling the Code
  • Pass a full suite of tests (Unit, Regression, Performance)
  • FXCop, if your using it then make sure you can meet your required code quality
  • Obfuscate your code (not really an issue for LOB applications)
  • Signing code (all code should be authenticode signed, its worth doing even for LOB applications)
  • Creating Setup and Deployment packages (these should be integrated in to the build, reject any setup authoring system that requires you to fire up an IDE)
  • Sign msi files (if you have any)
  • Create distribution images (if your distributing on CD or DVD its nice to have a .iso file pop out the end of the build process)
  • For web apps upload them to a staging server (don’t build straight to production, have a staging server you can verify the build on and the a process to promote to production if your happy with it)

Not every project needs all of these steps, but they should form the basis of a successful build. Most of the above can be done inside Msbuild, and the bits that can’t be done out of the box aren’t that difficult to achieve with a few custom actions here and there.