In this article describes how to implement MSBuild tasks with inline C# or in an external .NET assembly. After doing so, we will bundle these tasks in a NuGet package, so that they can easily be distributed and updated via NuGet and executed as part of the MSBuild compilation process. This is a powerfull technique to share and manage MSBuild tasks (e.g. build scripts, code generators, etc.) in bigger environments (enterprises, open-source libraries, common/shared tasks).
Write simple task with inline C
First we implement an MSBuild task with inline C# code. For the purpose of this article, a simple “Hello World” is sufficient:
MyTasks.props:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyInlineTask"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<MyParameter ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System"/>
<Using Namespace="System.Collections.Generic"/>
<Using Namespace="System.IO"/>
<Using Namespace="System.Linq"/>
<Using Namespace="System.Text"/>
<Using Namespace="System.Text.RegularExpressions"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
var myParameter = this.MyParameter;
Log.LogMessage(MessageImportance.High, "Hello from MyInlineTask: " + myParameter);
]]>
</Code>
</Task>
</UsingTask>
</Project>
You can now run the implemented task in your .csproj
file by importing the .props
file and executing the MyInlineTask
task:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="MyTasks.props" />
<Target Name="MyTasks" AfterTargets="AfterBuild">
<MyInlineTask MyParameter="Foobar"></MyInlineTask>
</Target>
</Project>
Write task in external assembly
If you implement tasks with more source code, it is recommended to implement them in a separate .NET project. In a new project, reference the GAC assembly Microsoft.Build.Utilities.Core
and create a new class which inherits from Microsoft.Build.Utilities.Task
:
public class MyExternalTask : Microsoft.Build.Utilities.Task
{
public string MyParameter { get; set; }
public override bool Execute()
{
Console.WriteLine("Hello from the MyAssembly: " + MyParameter);
return true;
}
}
The task can now be used by importing the assembly with its file path and defining the class with the full qualified name:
MyTasks.props:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyExternalTask" TaskFactory="MyAssembly.MyExternalTask" AssemblyFile="MyAssembly.dll"/>
</Project>
As before, the task can be called in your .csproj
file by importing the .props
file and executing the task:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="MyTasks.props" />
<Target Name="MyTasks" AfterTargets="AfterBuild">
<MyExternalTask MyParameter="Foobar"></MyExternalTask>
</Target>
</Project>
Distribution via NuGet
Now that we have implemented the two tasks, we can distribute them with a NuGet package and automatically run both tasks after the AfterBuild
target.
First, we create the MyPackage.props
file (the file name must match the package ID, previously called MyProps.props
) which contains the custom tasks and which will be added to the project where the NuGet package is installed:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask ... />
<UsingTask ... />
<Target Name="MyTasks" AfterTargets="AfterBuild">
<MyInlineTask MyParameter="Foo" />
<MyExternalTask MyParameter="Bar" />
</Target>
</Project>
Next, we create the MyPackage.nuspec
NuSpec file which references the MyPackage.props
file and the MyAssembly.dll
assembly file with the external task:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MyPackage</id>
<version>1.0.0.0</version>
<title>MyPackage</title>
<authors>Author</authors>
<description>Description</description>
<tags>MyTags</tags>
<copyright>Copyright © Company 2014</copyright>
<developmentDependency>true</developmentDependency>
<dependencies />
<references />
</metadata>
<files>
<file src="MyPackage.props" target="build" />
<file src="MyAssembly.dll" target="build" />
</files>
</package>
It is important to set developmentDependency
to true
so that the package is installed and marked as development dependency in the project’s packages.config
. This way the package is only installed in the project but not marked as a runtime dependency of the project.
Now we can generate the NuGet package:
nuget pack MyPackage.nuspec
After installing the package in another project, the project’s .csproj
file should import the .props
file from the NuGet package:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.props" />
<Import Project="..\packages\MyPackage.1.0.0.0\build\MyPackage.props" Condition="Exists('..\packages\MyPackage.1.0.0.0\build\MyPackage.props')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
...
Now the tasks should be executed when the MSBuild project is built. I used NuGet packaged MSBuild tasks in the NSwag project so that the code generators can be run as part of the MSBuild compilation.