Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C#
Article

Practical .NET2 and C#2: An introduction to MSBuild

Rate me:
Please Sign up or sign in to vote.
4.82/5 (43 votes)
7 Feb 200612 min read 229.1K   1.1K   142   17
An introduction to the MSBuild technology.

Author(s)Patrick Smacchia
Title Practical .NET2 and C#2
PublisherParadoxal Press
PublishedJan, 2006
ISBN 0-9766132-2-0
PriceUS$ 33.99
Pages896
Websitewww.PracticalDOT.NET

Content

Introduction

The .NET 2 platform is delivered with a new tool named msbuild.exe. This tool is used to build .NET applications. It accepts XML files which describe the sequence of tasks for the build process, in the same spirit as makefile files. Actually, in the beginning of this project, Microsoft had code named the tool XMake. The msbuild.exe executable is located in the .NET installation folder [Windows install folder]\Microsoft.NET\Framework\v2.0.50727\. It is planned that MSBuild will be part of the Windows Vista operating system. Its range of action will then be increased and may be used to construct all type of applications.

Until now, to construct your .NET applications, you needed to:

  • rither use the Build command in Visual Studio,
  • or use the Visual Studio devenv.exe executable as a command line,
  • or use a third party tool such as the open source tool named NAnt, or even use batch files which called the csc.exe C# compiler.

MSBuild aims to unify all these techniques. Those who know NAnt will not be in unknown territory as MSBuild borrows many concepts from this tool. The main advantage of MSBuild over NAnt is that it is used by Visual Studio 2005. MSBuild has no dependency on Visual Studio 2005 as it is an integral part of the .NET 2 platform. However, the .proj, .csproj, .vbproj etc. generated by Visual Studio 2005 to build projects are authored in the MSBuild XML format. During compilation, Visual Studio 2005 uses the services of MSBuild. In addition, the XML format used by MSBuild is fully supported and documented. The support of MSBuild is a significant progress for Visual Studio since until now, it used undocumented build scripts.

Building a multi modules assembly without MSBuild

Here, we will create an assembly with:

  • A main module <ci>Foo1.exe.
  • A module Foo2.netmodule.
  • A resource file Image.jpg.

Place in the same folder both the C# source files (Foo1.cs and Foo2.cs) as well as an image file named Image.jpg.

Foo2.cs

C#
namespace Foo {
   public class Bar {
      public override string ToString() {
         return "Hi from Foo2";
      }
   }
}

Foo1.cs

C#
using System;
using System.Reflection;
[assembly: AssemblyCompany("ParadoxalPress")]
namespace Foo {
   class Program {
      public static void Main(string[] argv) {
         Console.WriteLine("Hi from Foo1");
         Bar b = new Bar();
         Console.WriteLine( b );
      }
   }
}

Since we wish to construct an assembly with more than one module, we have no choice other than to use the csc.exe command line compiler as the Visual Studio environment cannot handle multi-module assemblies.

Create the files Foo2.netmodule and Foo1.exe by typing, in order, the following command (the csc.exe compiler can be found in the folder <WINDOWS-INSTALL-FOLDER>\Microsoft.NET\Framework\v2.0.50727):

> csc.exe /target:module Foo2.cs 
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg  Foo1.cs

Launch the Foo1.exe executable and the program displays the following on the console:

Hi from Foo1
Hi from Foo2

.proj files, targets and tasks

The root element of the MSBuild XML documents is <Project>. This element contains <Target> elements. These <Target> elements are the build units named targets. An MSBuild project can contain multiple targets and msbuild.exe is capable of chaining the execution of multiple targets. When you launch msbuild.exe through the command line, it takes as input a single .proj from the current folder. If multiple .proj files are present, you must specify to msbuild.exe which one to use. A single file must be specified.

An MSBuild target is a set of MSBuild tasks. Each child element to <Target> constitutes the definition of a task. The tasks of a target are executed in the order of their declaration. About forty types of tasks are provided by MSBuild, for example:

Task type

Description

Copy

Copies a file from a source folder to a destination folder.

MakeDir

Creates a folder.

Csc

Calls the csc.exe C# compiler.

Exec

Executes a system command.

AL

Calls the al.exe tool (Assembly Linker).

ResGen

Calls the resgen.exe tool (Resources Generator).

The complete list of possible tasks is available in the article named MSBuild Task Reference, on MSDN. An interesting aspect of MSBuild is that each type of task is materialized by a .NET class. It is then possible to extend MSBuild with new types of tasks by supplying your own classes. We will discuss this a little later.

Let us revisit our multi-module assembly example introduced in the previous section. Let me remind you that to build this assembly constructed from three modules Foo1.exe, Foo2.netmodule, and Image.jpg, we had to execute the following two commands:

>csc.exe /target:module Foo2.cs 
>csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg  Foo1.cs

In addition, we wanted that at the end of the construction of the assembly, the three modules be located in the \bin sub-folder from the current folder. Here is the MSBuild project Example1.proj which accomplishes the same work:

Example1.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Target Name="FooCompilation">
      <MakeDir Directories= "bin"/>
      <Copy SourceFiles="Image.jpg" DestinationFiles=".\bin\Image.jpg"/>
      <Csc Sources="Foo2.cs"       TargetType="module"
           OutputAssembly=".\bin\Foo2.netmodule" />
      <Csc Sources="Foo1.cs"       TargetType="exe"
           AddModules=".\bin\Foo2.netmodule" LinkResources="Image.jpg" 
           OutputAssembly=".\bin\Foo1.exe" />
   </Target>
</Project>

We see that the target named FooCompilation is built with four tasks:

  • A task of type MakeDir which creates the \bin folder;
  • A task of type Copy which copies Image.jpg to the \bin folder;
  • Two tasks of type Csc which invoke the csc.exe compiler.

To execute this build project, you need to create a folder containing the following files:

.\Foo.proj
.\Foo1.cs
.\Foo2.cs
.\Image.jpg

Go in this folder with the command window (Start Menu --> Microsoft .NET Framework SDK v2.0 --> SDK Command Prompt) and then launch the msbuild.exe command. Each target must be named. By default, msbuild.exe only executes the first target. You can specify a list of targets separated by semi-colons using the /target (shortcut /t). You can also specify such a list using the DefaultTarget attribute of the <Project> tag. If multiple targets are specified, the order of execution is undefined.

The default behavior of MSBuild is to stop execution as soon as one of the tasks emits an error. You may wish to have a build script tolerate errors. Also, each element containing a task can contain a ContinueOnError attribute which is set to false by default but may be set to true.

Properties

To allow you to add parameters to control your scripts, MSBuild presents the notion of a property. A property is a key/value couple defined in the <PropertyGroup> element. MSBuild properties work as an alias system. Each occurrence of $(key) in the script is replaced by the associated value. Typically, the name of the /bin folder is used in five places in our project. It constitutes a good candidate for a property:

Example2.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
      <OutputPath>.\bin</OutputPath>
   </PropertyGroup>
   <Target Name="FooCompilation">
      <MakeDir Directories= "$(OutputPath)"/>
      <Copy SourceFiles="Image.jpg" 
            DestinationFiles="$(OutputPath)\Image.jpg"/>
      <Csc Sources="Foo2.cs"        TargetType="module"
           OutputAssembly="$(OutputPath)\Foo2.netmodule" />
      <Csc Sources="Foo1.cs"        TargetType="exe"
           AddModules="$(OutputPath)\Foo2.netmodule" 
           LinkResources="Image.jpg" 
           OutputAssembly="$(OutputPath)\Foo1.exe" />
   </Target>
</Project>

You can also use pre-defined properties defined by MSBuild, such as:

Property

Description

MSBuildProjectDirectory

Folder hosting the current MSBuild project.

MSBuildProjetFile

Name of the current MSBuild project.

MSBuildProjectExtension

Calls the csc.exe. Extension of the current MSBuild project.

MSBuildProjectFullPath

Full path to the current MSBuild project.

MSBuildProjectName

Name of the current MSBuild project without the extension.

MSBuildPath

Folder which contains msbuild.exe.

During the edition of properties with Visual Studio 2005, you will notice that a certain number of keys are proposed by intellisense. OutputPath, for example, is such a key. You can use these keys, but nothing prevents you from defining your own keys.

Items

The base behind building a project by a script is the manipulation of folders, files (source, resource, executable…), and references (to assemblies, to COM classes, to resource files…). We use the term item to designate these entries which constitute the entries and outputs for most of the tasks. In our example, the Image.jpg file is an item consumed both by the Copy task and the second Csc task. The file Foo2.netmodule is an item produced by the first Csc task and is consumed by the second Csc task. Let us rewrite our project using the notion of item:

Example3.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup><OutputPath>.\bin</OutputPath></PropertyGroup>
   <ItemGroup>
      <File_Image Include="$(OutputPath)\Image.jpg"/>
      <NetModule_Foo2 Include="$(OutputPath)\Foo2.netmodule"/>
   </ItemGroup>
   <Target Name="FooCompilation">
      <MakeDir Directories= "$(OutputPath)"/>
      <Copy SourceFiles="Image.jpg" 
            DestinationFiles="@(File_Image)"/>
      <Csc Sources="Foo2.cs"        TargetType="module"
           OutputAssembly="@(NetModule_Foo2)" />
      <Csc Sources="Foo1.cs"        TargetType="exe"
           AddModules="@(NetModule_Foo2)" 
           LinkResources="@(File_Image)" 
           OutputAssembly="$(OutputPath)\Foo1.exe" />
   </Target>
</Project>

We notice the use of the @(item name) to reference an item. Moreover, an item can define a set of files through the use of the wildcard syntax. For example, the following item references all the C# files in the current folder except for Foo1.cs:

XML
<cs_source Include=".\*.cs" Exclude=".\Foo1.cs" />

Conditions

We may wish that the same MSBuild project build multiple different versions. For example, it would be a shame to have to create and maintain two projects to handle the build of a Debug and a Release version of the same application. Also, MSBuild introduces the notion of a condition. A Condition attribute can be added to any element of a MSBuild project (properties, item, target, task, property group, item group…). If during the execution, the condition of an element is not satisfied, the MSBuild engine will ignore it. The MSDN article named MSBuild Conditions describes the complete list of expression which can be used in a Condition attribute.

In the following example, we use the condition of type string equality compare to make sure that our script supports both a Debug and Release mode. We also use a condition of type test for the presence of a file or folder to execute the MakeDir task only when needed. This condition is there purely for learning reasons as the MakeDir only executes if the folder does not already exist:

Example4.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)'=='Debug'">
      <Optimize>false</Optimize>
      <DebugSymbols>true</DebugSymbols>
      <OutputPath>.\bin\Debug</OutputPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)'=='Release'">
      <Optimize>true</Optimize>
      <DebugSymbols>false</DebugSymbols>
      <OutputPath>.\bin\Release</OutputPath>
   </PropertyGroup>
   <ItemGroup>
...
   <Target Name="FooCompilation">
      <MakeDir Directories= "$(OutputPath)" 
               Condition="!Exists('$(OutputPath)')"/>
...

When they are defined, the Optimize and DebugSymbols standard properties are automatically taken into account by Csc tasks.

Before launching this script, you must specify as a command line parameter the value of the Configuration property. This can be done using the /property (shortcut /p) option:

>msbuild /p:Configuration=Release

The astute reader has noticed that it is possible to use a condition to define the default value for the Condition property:

Example5.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)'==''">
      <Configuration>Debug</Configuration>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)'=='Debug'">
...

Incremental build and dependencies between targets

In a real environment, the execution of an MSBuild project can take several minutes (even hours) to execute. As a side fact, did you know that since its beginning, the Windows operating system takes about 12 hours to build? This means the ever growing volume of code to be compiled compensates the performance increase of machines.

It is not necessarily a good thing to completely restart the build process for a minor change made on a source file for which no other components depend. Also, you can use the notion of incremental construction. For this, you must specify the list of input items and the output items for a target using the Inputs and Outputs attributes. If MSBuild detects that at least one of the input items is older than one of the output items, it will execute the target.

This incremental build technique forces you to partition your tasks into several targets. We have seen that if we specify multiple targets to build by MSBuild, for example with the DefaultTargets attribute, you can not assume any execution order. You can however specify a set of dependencies between targets using the DependsOnTargets attribute. MSBuild executes a target only when the totality of the targets on which it depends is executed. Naturally, the MSBuild engine will detect and emit an error when circular dependencies between targets exist:

Example6.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
   DefaultTargets="FooCompilation">
...
   <Target Name="CreateOutputPath" Condition="!Exists('$(OutputPath)')">
      <MakeDir Directories= "$(OutputPath)"/>
   </Target>
   <Target Name="FooCompilation" DependsOnTargets="CreateOutputPath"
           Inputs="Foo2.cs;Foo1.cs" 
           Outputs="@(NetModule_Foo2);$(OutputPath)\Foo1.exe">
      ...
   </Target>
</Project>

MSBuild transforms

You have the possibility of establishing an objective correspondence between the input items and the output items of a target. For this, you need to use MSBuild transformations detailed in the article named MSBuild Transforms, on MSDN. The advantage of using transformations is that MSBuild decides to execute the target if at least one of the input items is older than the output item which corresponds to it. Logically, such a target is going to be executed less often, hence a performance gain.

Splitting an MSBuild project on several files

We have seen that the msbuild.exe tool can only process a single project file for each execution. However, an MSBuild project file can import another MSBuild project file by using the <Import> element. In this case, all the children of the <Project> in the imported file are copied in place of the <Import> element. Our example script can then be broken into two files Example7.proj and Example8.target.proj as follows:

Example7.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" 
   DefaultTargets="FooCompilation">
   <PropertyGroup Condition="'$(Configuration)'==''"> ...
   <PropertyGroup Condition="'$(Configuration)'=='Debug'"> ...
   <PropertyGroup Condition="'$(Configuration)'=='Release'"> ...
   <ItemGroup> ...
   <Import Project="Foo.target.proj"/>
</Project>

Example8.target.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
   <Target Name="CreateOutputPath"   ...
   <Target Name="FooCompilation"   ...
</Project>

How does Visual Studio 2005 harness MSBuild?

I have already mentioned that the files of extension .proj, .csproj, .vbproj etc. generated by Visual Studio 2005 to build projects are authored in the MSBuild XML format. If you analyze such a file, you will notice that no targets are explicitly specified. In fact, the project files generated by Visual Studio 2005 import some .targets files which contain generic targets. For example, a file with a .csproj extension contains the following element:

XML
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

This Microsoft.CSharp.targets file contains two generic targets:

  • A target named CreateManifestResourceNames which takes care of the organization of resource files (transformation of .resx files into .resources).
  • A target named CoreCompile which contains a task named Csc which takes care of building C# files.

We recommend that you take a look at the .targets files located in the folder defined by $(MSBuildBinPath) which is the .NET 2.0 installation folder (i.e. [Windows folder]\Microsoft.NET\Framework\v2.0.50727).

In addition to importing such a .targets file, the files generated by Visual Studio 2005 contain essentially the definition of properties and items which will be used by the generic targets.

Creating custom MSBuild tasks

Another interesting aspect of MSBuild is that each type of task materializes itself in a .NET class. This means that it is possible to extend MSBuild with new task types by supplying your own classes. Such a class must support the following constraints:

  • It must implement the ITask interface defined in Microsoft.Build.Framework.dll, or derive from the helper class Task defined in Microsoft.Build.Utilities.dll.
  • It must implement the bool Execute() of the ITask interface. This method contains the body of the task and must return true if the execution succeeded.
  • It can offer properties which will be set by MSBuild from the attribute values in the task, before the call to Execute(). Only the properties marked with Required must necessarily be initialized.

Here is an example of a task named MyTouch which updates the timestamp of the files specified with the Files attribute (let us mention that such a task named Touch is offered by the MSBuild framework):

Example9.cs

C#
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace MyTask {
   public class MyTouch : Task {
      public override bool Execute() {
         DateTime now = DateTime.Now;
         Log.LogMessage(now.ToString() + 
            " is now the new date for the following files:");
         try {
            foreach(string fileName in m_FilesNames) {
               Log.LogMessage("   " + fileName);
               System.IO.File.SetLastWriteTime(fileName, now);
            }
         }
         catch (Exception ex) {
            Log.LogErrorFromException(ex, true);
            return false;
         }
         return true;
      }
      [Required]
      public string[] Files {
         get { return (m_FilesNames); } set { m_FilesNames = value; }
      }
      private string[] m_FilesNames;
   }
}

Note the use of the TaskLoggingHelper Task.Log{get;} which allows displaying information relating to the execution of the task. Our MyTouch task must be registered to all the MSBuild projects which may use it. This is done by using an element <UsingTask>. Here is such a script which updates the timestamps of the C# files in the current folder (the MyTask.dll assembly must be located in the C:\CustomTasks\ folder):

Example10.proj

XML
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <UsingTask AssemblyFile="C:\CustomTasks\MyTask.dll" 
              TaskName="MyTask.MyTouch"/>
   <ItemGroup>
      <FichierSrcCs Include="*.cs"/>
   </ItemGroup>
   <Target Name="TouchTheCsFiles" >
      <MyTouch Files= "@(CsSrcFiles)"/> 
   </Target>
</Project>

It is interesting to note that all standard tasks are declared by <UsingTask> elements located in the Microsoft.Common.Tasks file. This file is automatically and implicitly imported by msbuild.exe at each execution. By analyzing this file, we can see that the classes corresponding to the standard tasks are defined in the Microsoft.Build.Tasks.dll assembly. You can then access the code of these tasks using a tool such as Reflector.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
Patrick Smacchia is a .NET MVP involved in software development for over 15 years. He is the author of Practical .NET2 and C#2, a .NET book conceived from real world experience with 647 compilable code listings. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange at Société Générale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET application.

Comments and Discussions

 
QuestionVery helpful post, I am almost there but... Pin
Rajiv Verma1-Jul-15 0:13
Rajiv Verma1-Jul-15 0:13 
GeneralMy vote of 5 Pin
Vapsy Raghav6-Jan-15 23:26
professionalVapsy Raghav6-Jan-15 23:26 
QuestionHow do you retrieve the tasks in code Pin
Hexadigm Systems14-Mar-13 4:34
Hexadigm Systems14-Mar-13 4:34 
GeneralMy vote of 5 Pin
RohanThomas23-Jan-13 15:43
RohanThomas23-Jan-13 15:43 
GeneralMy vote of 5 Pin
cvvcodeproject17-Oct-12 17:55
cvvcodeproject17-Oct-12 17:55 
GeneralMy vote of 5 Pin
paulius_l29-Mar-12 20:39
paulius_l29-Mar-12 20:39 
GeneralProblem building only one project in a multiproject solution Pin
Sean Rhone2-Sep-10 8:47
Sean Rhone2-Sep-10 8:47 
GeneralTypo; Link to related article Pin
Ruben Bartelink16-Dec-09 4:55
Ruben Bartelink16-Dec-09 4:55 
GeneralRe: Typo; Link to related article Pin
Brian Herbert23-Sep-11 10:39
Brian Herbert23-Sep-11 10:39 
QuestionIs this a typo error? Pin
Amr Elsehemy ®23-Dec-08 3:56
Amr Elsehemy ®23-Dec-08 3:56 
Generalgreat article Pin
yetibrain11-Nov-08 14:16
yetibrain11-Nov-08 14:16 
QuestionRunning build for both configurations Pin
cecatecean23-Sep-08 1:06
cecatecean23-Sep-08 1:06 
AnswerRe: Running build for both configurations Pin
yetibrain11-Nov-08 14:23
yetibrain11-Nov-08 14:23 
AnswerRe: Running build for both configurations Pin
dB.13-Feb-09 9:03
dB.13-Feb-09 9:03 
Take a look here: http://code.dblock.org/ShowPost.aspx?id=33[^]


GeneralThanks. Pin
Mushtaque Nizamani18-Mar-08 23:39
Mushtaque Nizamani18-Mar-08 23:39 
GeneralQuestion Pin
Arash Sabet4-May-07 8:02
Arash Sabet4-May-07 8:02 
GeneralExcellent Article Pin
rajivpopat18-Jun-06 6:49
rajivpopat18-Jun-06 6:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.