Extend MSBuild with a New Task
Tasks provide the code that runs during the build process and are contained in targets. A library of common tasks is provided with
MSBuild and you can also create your own tasks.
In this example, information is provided about the two approaches you can use when implementing a task, registering tasks, and raising an event from a task. The example project that is provided builds a .dll file that can be used as an
MSBuild task to log a comment.
Tasks
Tasks provide the code that runs during the build process and are contained in targets. Examples of tasks include Copy, which copies one or more files,
*MakeDir*, which creates a directory, and Csc, which compiles Visual C# source code files. Each task is implemented as a .NET class that implements the
*ITask* interface, which is defined in the
Microsoft.Build.Framework.dll assembly.
There are three approaches you can use when implementing a task:
* Derive your class directly from
*ITask* and implement the methods on this interface.
* Derive your class from the helper class, Task, which is defined in the
Microsoft.Build.Utilities.dll assembly.
* Derive your class from
ToolTask (in
Microsoft.Build.Framework.dll) to provide functionality for a task that wraps a command line tool.
Choosing to derive from Task or
ToolTask makes it easier to log events from your task.
In the case of
ITask or Task, you must add to your class a method named Execute, which is the method that is called when the task runs. This method takes no parameters and returns a Boolean value: True if the task succeeded or False if it failed. The following example shows the simplest possible task — it performs no action and returns True.
using System;
using Microsoft.Build.Utilities;
namespace [MyTasks]
{
public class [SimpleTask] : Task
{
public override bool Execute()
{
return true;
}
}
}
The following project file runs this task:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="MyTarget">
[<SimpleTask] />
</Target>
</Project>
Tasks can also get properties when they run if you create .NET properties on the task class.
MSBuild sets these properties immediately before calling the task's Execute method. To create a string property, use task code such as:
using System;
using Microsoft.Build.Utilities;
namespace [MyTasks]
{
public class [SimpleTask] : Task
{
public override bool Execute()
{
return true;
}
private string myProperty;
public string [MyProperty]
{
get { return myProperty; }
set { myProperty = value; }
}
}
}
The following project file runs this task:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="MyTarget">
[<SimpleTask] MyProperty="Value for MyProperty" />
</Target>
</Project>
Registering Tasks
If a project is going to be able to run a task,
MSBuild must know how to locate the assembly that contains the task class. Tasks are registered using the
UsingTask element, for example:
<UsingTask TaskName="MyTasks.SimpleTask"
AssemblyName="Microsoft.Build.Tasks"/>
The
*TaskName* attribute is required, and it is recommended that, to avoid ambiguity, it is the fully qualified name of the class that implements the task. The
*AssemblyName* attribute provides the name of the assembly that contains the class. The assembly name can also be the strong name, for example,
*SimpleTask*, Version=1.0.2,Culture=neutral. A strong name is required for locating task assemblies that are placed in the global assembly cache. When
*AssemblyName* is used,
MSBuild will discover the task assembly via standard fusion loading rules. If fusion loading rules do not apply to your task the
*AssemblFile* attribute can be used. Assembly file provides the disk location of library which contains the assembly. The assembly file attribute consumes either a full path or a relative path, for example:
<UsingTask TaskName="MyTasks.SimpleTask"
AssemblyFile="Microsoft.Build.Tasks.dll"/>
Or
<UsingTask TaskName="MyTasks.SimpleTask"
AssemblyFile="c:\somediskpath\Microsoft.Build.Tasks.dll"/>
The
MSBuild file Microsoft.Common.tasks is a project file that contains a list of
UsingTask elements that register all the tasks that are supplied with
MSBuild. This file is automatically included when building every project. If a task that is registered in Microsoft.Common.tasks is also registered in the current project file, the current project file takes precedence, that is, you can override a default task with your own task that has the same name.
You can see a list of the tasks that are supplied with MSBuild by viewing the contents of Microsoft.Common.tasks.
Raising Events from a Task
If your task derives from the Task helper class, you can use any of the following helper methods on the Task class to raise events that will be caught and displayed by any registered loggers:
public override bool Execute()
{
Log.LogError("messageResource1", "1", "2", "3");
Log.LogWarning("messageResource2");
[Log.LogMessage(MessageImportance.High,] "A comment");
…
}
If your task derives directly from
*ITask*, you can still raise such events but you must use the
*IBuildEngine* interface. The following example shows a task that derives directly from
ITask and raises a custom event:
public class [SimpleTask] : [ITask]
{
private [IBuildEngine] buildEngine;
public [IBuildEngine] [BuildEngine]
{
get{ return buildEngine; }
set{ buildEngine = value; }
}
public override bool Execute()
{
[BuildMessageEventArgs] args = new [BuildMessageEventArgs(]
[MessageImportance.Normal,] "message");
[BuildEngine.LogMessageEvent(args);]
return true;
}
}
Requiring Task Parameters to be Set
You can mark certain task properties as “required” so that any project file that runs the task must set values for these properties or the build will fail. Apply the
Required attribute to the .NET property in your task as follows:
private string requiredProperty;
Required public string
RequiredProperty {
get { return requiredProperty; }
set { requiredProperty = value; }
}
The
Required property is defined in the Microsoft.Build.Framework namespace.
Allowing a project to read a property from a task
You can mark certain task properties as “output” so that a project that uses the task may read off the value into an msbuild property or item list after the task is done executing. Apply the
Output attribute to the .NET property in your task as follows, e.g.:
Output public ITaskItem[]
CopiedFiles {
get { return _copiedFiles; }
set { _copiedFiles = value; }
}
The
Output property is defined in the Microsoft.Build.Framework namespace.
In this example, a project would retrieve the list like this, e.g.:
[<SomeTask] Sources="@(Stuff)">
<Output TaskParameter="CopiedFiles" ItemName="MyListOfCopiedFiles"/>
[</SomeTask>]
Building a Project
To build
*SimpleTask1* from the command line, navigate to the
*SimpleTask1* directory and type:
msbuild Simpletask1.proj
To build
*SimpleTask2* from the command line, navigate to the
*SimpleTask2* directory and type:
msbuild
SimpleTask2.proj To build
*SimpleTask3.proj* from the command line, navigate to the
*SimpleTask3* directory and type:
msbuild
SimpleTask3.proj To build the example project that uses
*SimpleTask3*, navigate to the directory that contains
*invokesimpletask3*.proj and type:
msbuild invokesimpletask3.proj
Project Files
Download Code - C#
Visual C# example
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyTasks.SimpleTask3"
AssemblyFile="SimpleTask3\bin\debug\SimpleTask3.dll"/>
<Target Name="MyTarget">
<SimpleTask3 MyProperty="Hello!"/>
</Target>
</Project>
Custom Task Library
Set Environment Variable Task
The
SetEnvVar task allows environment variables to be set from within an
MSBuild project file:
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace [Acme.Tools.MSBuildTasks]
{
public class [SetEnvVar] : Task
{
private string _variable;
private string _value;
[Required]
public string Variable
{
get { return _variable; }
set { _variable = value; }
}
[Required]
public string Value
{
get { return _value; }
set { _value = value; }
}
public override bool Execute()
{
Environment.SetEnvironmentVariable(_variable, _value);
return true;
}
}
}
Use it like so:
<Project DefaultTargets="SetupPath" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
[<UsingTask] TaskName="Acme.Tools.MSBuildTasks.SetEnvVar" AssemblyFile="Acme.dll"/>
[<PropertyGroup>]
<Path>$(PATH);C:\Tools\Bin</Path>
[</PropertyGroup>]
<Target Name="SetupPath">
[<SetEnvVar] Variable="PATH" Value="$(Path)"/>
</Target>
</Project>
SmtpMail Task
This task allows you to send email from a target:
using System;
using System.Collections.Generic;
using System.Net.Mail;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace [Acme.Tools.MSBuildTasks]
{
public class [SmtpMail] : Task
{
private string _smtpHost;
private string _from;
private string _to;
private string _cc = "";
private string _subject = "";
private string _body;
private string _attachments = "";
private int _timeout = 30 * 1000;
private Nullable<int> _portNumber;
[Required]
public string [SmtpHost]
{
get { return _smtpHost; }
set { _smtpHost = value; }
}
public string [SmtpPort]
{
get
{
string s = _portNumber.HasValue ? _portNumber.ToString() : "";
return s;
}
set
{
int port;
if [(Int32.TryParse(value,] out port))
{
_portNumber = port;
}
}
}
public int Timeout
{
get { return _timeout; }
set { _timeout = value; }
}
[Required]
public string From
{
get { return _from; }
set { _from = value; }
}
[Required]
public string To
{
get { return _to; }
set { _to = value; }
}
public string CC
{
get { return _cc; }
set { _cc = ((value == null) ? "" : value); }
}
public string Subject
{
get { return _subject; }
set { _subject = ((value == null) ? "" : value); }
}
[Required]
public string Body
{
get { return _body; }
set { _body = value; }
}
public string Attachments
{
get { return _attachments; }
set { _attachments = value; }
}
public override bool Execute()
{
[MailMessage] message = new [MailMessage();]
message.From = new MailAddress(_from);
string[] toAddresses =
_to.Split(" ".ToCharArray(), [StringSplitOptions.RemoveEmptyEntries);]
foreach (string address in toAddresses)
{
message.To.Add(address);
}
string[] ccAddresses =
_cc.Split(" ".ToCharArray(), [StringSplitOptions.RemoveEmptyEntries);]
foreach (string address in ccAddresses)
{
message.CC.Add(address);
}
string[] attachments =
_attachments.Split(";".ToCharArray(),
[StringSplitOptions.RemoveEmptyEntries);]
foreach (string attachment in attachments)
{
message.Attachments.Add(new Attachment(attachment.Trim()));
}
message.Subject = _subject;
message.Body = _body;
[SmtpClient] smtpClient = new SmtpClient(_smtpHost);
smtpClient.Timeout = _timeout;
if (_portNumber.HasValue)
{
smtpClient.Port = _portNumber.Value;
}
smtpClient.Send(message);
return true;
}
}
}
Use it like so:
<Project DefaultTargets="SendMail" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
[<UsingTask] TaskName="Acme.Tools.MSBuildTasks.SmtpMail" AssemblyFile="Acme.dll"/>
<Target Name="SendMail">
[<SmtpMail] SmtpHost="smtp.mail.com" From="john_doe@acme.com" To="jane_doe@acme.com"
Subject="Build Succeeded!!" Body="$(MessageInAVariable)"/>
</Target>
</Project>