devxlogo

Customize Your Builds in Visual Studio Using the Standalone MSBuild Tool

Customize Your Builds in Visual Studio Using the Standalone MSBuild Tool

he build process has always been one of the dark mysteries of Visual Studio. What exactly happened when you pressed F5 inside the IDE? Nobody really knew for sure.

This is one of the reasons why Microsoft decided to redesign the whole build process from scratch in Visual Studio 2005. In this upcoming release the whole build process is separated from the IDE and is bundled in a standalone tool called MSBuild. The advantage of this approach is that MSBuild is part of the .NET Framework 2.0 and in the Longhorn timeframe the .NET Framework will be part of the underlying operating system. So you don’t need anything in your build lab except a computer running the .NET Framework?there are no other deployment issues, like there are in Visual Studio 2005.

Why should you switch to MSBuild?

  • All project files created in Visual Studio 2005 are MSBuild project files.
  • MSBuild is designed for scalability, performance, and extensibility.
  • You are able to customize the whole build process for your own requirements: as a example you can include functionality from Source Code Control Provider like Visual Source Safe into your build process (e.g. getting the latest version, labeling builds etc.)

You don’t need to install Visual Studio 2005 in your build lab. You only need the .NET Framework 2.0.

Project files in Visual Studio 2005 are XML files that describe the whole build process, splitting it up into several fine-grained tasks. The visible part of MSBuild is contained in the console application msbuild.exe, which drives the whole build process. It is a thin wrapper around the core build engine implemented in the assembly MSBuildEngine.dll. This application receives project files as input and processes them synchronously.

Overview of MSBuild
The execution engine of MSBuild is responsible for executing the project files and calling all defined Tasks synchronously. Tasks are part of a so-called Target. You can see a Target as a component that is created during the build process (like an assembly). Tasks can receive their inputs from Properties and Items and can produce so-called Output Items that other Tasks can use as their inputs. Table 1 gives the definitions for these important terms.

Table 1. You should be familiar with these important terms before using MSBuild.

Term

Description

Target

A target is a component that is created during the build process?like an assembly.

Task

A Target can consist of several tasks that describe the workflow used to create the Target. A Task represents a work item that must be executed?think of the compilation of C# files with csc.exe.

Properties

Properties are defined as key/value pairs. Properties are used to configure the build process. An example is the output path of the created assemblies.

Items

Items are lists of objects that can be used for Tasks as inputs. For example, the Task used to call csc.exe retrieves C# files as input items for the compilation.

Output Items

Tasks can produce Output Items that can be consumed from other Tasks as Input Items. Because of the loosely-coupled architecture it is possible to link tasks together written by different vendors in different languages.

Conditions

Conditions are used to drive the build process. With Conditions you can define if Tasks or Targets should be executed during the build process.

Building an App
Now that you have a basic understanding of MSBuild and its key terms, I’ll walk through a simple project file that describes how a traditional C# Hello World program can be built:

         Application1                           

You can start the build process with the command msbuild.exe MyProjectFile.csproj typed from the command line. The project files consist of the element with the attributes shown in Table 2.

Table 2. The project files consist of the element with these attributes.

Attribute

Description

MSBuildVersion

Current version number of MSBuild; currently 2.0 is used.

DefaultTargets

Lists the Targets that must be created during the build process. Several Targets can be split using a semicolon (;). Each Target listed here must be defined in the project file with a element. Furthermore you have the possibility to import Targets from predefined project files (each programming language defines its own Targets).

Underneath the element you can see the elements and . Within you can define your properties used within the whole project file. You can reference properties through the syntax $(PropertyName). l defines the items that are used during the build process. Here you can define for example which C# files should be compiled.

Each Item array can be referenced through it’s name. The name of an array is the name of the XML element used. For example, when you use the element then the name of this array is Compile.

An Item group can be referenced with the syntax @(ItemName). All in all there are three different ways to add items to an Item Group. The first way requires you to specify each file that should be added to the specified Item Group:

         

The second way allows wildcards such as *:

   

Finally, you can explicitly list your files:

   

The files in an are snapshots. So when you create a new C# file during the build process and you have used a wildcard such as * in your project file, the new generated C# file isn?t added to the files in the specified .

Checking Conditions
Conditions can be applied to the elements , , , and . When you are using conditions you must supply an expression that must be evaluated to True or False. In the following example I use a condition to set the output path of the assemblies to binDebug or binRelease depending on the configuration:

         Application1            binDebug            binRelease                           

With msbuild.exe MyProjectFile.csproj /p:Configuration=Release you can start the build process and specify the appropriate configuration.

I have already mentioned that it is possible that Tasks can communicate with each other through a loosely coupled-architecture. Tasks can create Output Items that can be consumed from other Tasks as Input Items. The code that follows illustrates this concept: The C# compiler creates an assembly used by the Task Copy to move it to another folder.

                                             

As you can see, the Output Items must be explicitly defined within the Task. Therefore the element exists. With the attribute ItemName you can define the name with which another Task can refer to that Output Item. Additionally you must set the Attribute TaskParameter, which defines which property from the Task is exposed as an Output Item.

Table 3 describes the new XML tags you’ll be using with this project.

Table 3. XML tags control the MSBuild process.

XML-Tag

Description

Defines the root element of the project file.

All properties are defined within this element.

Items that should be processed from Tasks are defined within this element.

Defines a target that should be built like an assembly.

Represents a Task that should be executed during the building of a target.

Use this element to output Items from Tasks to other Tasks as Input Items.

Use this element to register your own written Tasks within MSBuild.

?

?

Extensibility Through User-defined Tasks
Up to this point, this article has described the inner workings of MSBuild and how you can write project files that can be consumed from msbuild.exe. Next I’ll show you how to extend MSBuild and how to write your own Tasks that can be fully integrated within MSBuild. You will see how you can write a simple task that changes the output date of the source files to the current date.

First, what exactly is a ‘user-defined Task’? Simply put, it’s a managed class implementing the interface Microsoft.Build.Framework.ITask defined in the assembly MSBuildFramework.dll. Let’s have a look at the definition of the interface:

public interface ITask{   public bool Execute();   public IBuildEngine BuildEngine { get; set; }   public object HostObject { get; set; }}
Figure 1. The Class Model: This image shows the class model that is located behind a Task.

The most important method is Execute. Execute is called when the Task is started through MSBuild. Furthermore you can find in the namespace Microsoft.Build.Utilities (assembly MSBuildUtilities) the class Task that is already implementing the interface ITask. The only thing you must do when deriving from this class is override the method Execute and implement your own build process logic to execute. Figure 1 shows the class model that is located behind a Task.

Your own Tasks must be registered within MSBuild when you want to use them. For the registration you can use the XML element within the project file, as shown here:

   

After this registration the Task can be used as every other Task shipped with MSBuild. But you’ll also need to know how a concrete Task can be implemented. The following code implements a Task that shows a Hello World message box.

using System;using System.Windows.Forms;using Microsoft.Build.Framework;using Microsoft.Build.Utilities;namespace MyTasks{   public class HelloWorld : Task   {      public override bool Execute()      {         MessageBox.Show("Hello World from MSBuild!");         return true;      }   }}

Finally, here’s how to register and use a user-defined Task within a project file:

               

As you already know you have the possibility to connect several Tasks through the use of Input and Output Items. The next thing to learn is how to create a user-defined Task that produces Output Items that can be consumed from other Tasks as Input Items. Every public property within the implemented Task class can be accessed from the project file as an XML attribute. Properties that are marked with the attribute [Output] are Output Items.

The following example declares the property Files that accepts a list of files for which the last write time should be changed to the current time. Furthermore this property is marked with the attribute

MSBuild is completely extensible and scalable for large-scale enterprise applications. MSBuild was used for several years internally at Microsoft for building its own software products. Even better, there are rumors that Microsoft may decide to migrate the whole build process for Longhorn to MSBuild. While that doesn’t change what MSBuild can do for you it does provide some welcome ‘eating their own dog food’-level comfort. If Longhorn can be built with MSBuild then this tool is a first-class citizen for our own enterprise applications.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist