MSBuild inline task explained

 Posted by on December 8, 2012
Dec 082012
 

In the last few days I was intensively working with MsBuild, as I had to automate the building and deployment of 3 projects to a large amount of environments. In order to achieve this, an obvious choice was to use the MsBuild, which in my opinion is a great framework that perfectly fits with the .NET world. Obviously there is a learning curve, but once mastered it’s really a joy to use.

MsBuild is primarily made of Targets and Tasks. Especially Tasks have a function to enable and enrich the MsBuild platform, and the great deal is that everyone can write a Task and extend the pre-existing functionalities.

If you are reading this page, my assumption is that you probably know what a MsBuild Task is all about. In case of doubt: a Task is simply a function/functionality exposed to MsBuild that accepts a number of parameters, and it could have or not a number of output parameters. As easy as it sounds.
Some of the typical, already built in the platform, tasks are Copy, Move, Delete files, just to name some of the probably most useful and very simple ones.

Popular extensions

MsBuild is very easy to extend, and there are two very important sources with tons of already written and ready to use Tasks. In case you are interested, feel free to download from the below links, as both are free to use and maintained by the community. There you will find Tasks such as Zip, SVN integration, SQL Server integration, and so on…

  1. Microsoft’s MSBuild ExtensionPack
  2. MSBuild Community Tasks

Using and integrating the above libraries goes beyond this post.

Creating own tasks

There are mainly two ways of creating tasks by:

  1. Implementing and compiling an ITask interface: That’s it creating a class that would implement ITask interface. In order to integrate this into the MSBuild script file, one would need to specify a path to the compiled assembly that needs to be deployed together with the script.
  2. Implementing an inline task.

While there are many posts in internet related to the MSBuild task creating by implementing the ITask interface, in this post I would love to concentrate on Inline tasks.
In the .NET Framework version 4, you can create tasks directly in the project file. You do not have to create a separate assembly to host the task. This makes it easier to keep track of source code and easier to deploy the task. The source code is integrated into the script itself.

I’ve created a very simple Tasks that is responsible for deleting files, given a list of files to be deleted.
So, let’s start with explaining a skeleton of an inline task:

UsingTask UsingTask is a declaring element that needs to be used in order to create a Task. Attributes of the element would specify the Task Name, the so called TaskFactory, which will specify what would be the engine to be used in order to interpret the underlying code, and the Assembly that provides such a functionality. In our case the CodeTaskFactory and the $(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll are default values.
ParameterGroup ParameterGroupsection provides a possibility to declare the input and output parameters. Important thing is to specify a full signature of the DataType to be used , such as System.String[], etc..
In my case, I am creating a task that accepts

  • FilesToDelete, which is an array of strings with full paths of the files to be deleted
  • Two more input parameters, IgnoreErrors and ShowErrors: that would be used to control the workflow of the task.
  • and an output parameter DeletedFiles that will contain a list of files that have been successfully deleted.
Task And, finally the Task element that defines the task logic itself. There are several subelements that could be specified, but in my opinion the most useful and typical ones are the ones specified in the below example. The possibility to specify the “Import” of namespaces by using the Using element and the actual Code itself. In my case the Task below is written in CSharp, and so it’s specified in the Language attribute.



I think that is kind of useless to go through the implementation of the task as it is really trivial. Looping through the list of files received as the FilesToDelete parameter, and filling a list of deleted items.

Here is what an Inline Task looks like:

<UsingTask TaskName="DeleteFiles" 
           TaskFactory="CodeTaskFactory"
           AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">

<ParameterGroup>
  <FilesToDelete    ParameterType="System.String[]" 
                    Required="true" 
                    Output="false"/>
  <IgnoreErrors     ParameterType="System.Boolean"  
                    Required="true" 
                    Output="false"/>
  <ShowErrors       ParameterType="System.Boolean" 
                    Required="true" 
                    Output="false"/>
  <DeletedFiles     ParameterType="System.String[]" 
                    Output="true" />
</ParameterGroup>

<Task>
  <Using Namespace="System.Collections.Generic" />
  <Using Namespace="System.IO" />
  
  <Code Type="Fragment" Language="cs">
	<![CDATA[
	List<string> DeletedFilesList = new List<string>();
	
	if(this.FilesToDelete!=null && this.FilesToDelete.Count()>0)
	{
	  foreach(string file in this.FilesToDelete)
	  {
		try
		{
		  if(File.Exists(file))
		  {
			File.Delete(file);
			DeletedFilesList.Add(file);
		  }
		}
		catch(Exception exc)
		{
		  if(this.ShowErrors == true)
		  {
			Console.WriteLine("Deleting file " + file 
                            + " failed because of " + exc.ToString());
		  }

		  if(this.IgnoreErrors == false)    
		  {
			break;
		  }
		}
	  }
	}
	this.DeletedFiles =  DeletedFilesList.ToArray();
	]]>
  </Code>  
</Task>
</UsingTask>

Integration

Using the above created inline task is pretty much straightforward. As shown here below, we simply need to create a tag with the task name, send parameters, and get back the output, if any.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" 
         ToolsVersion="4.0">

  <UsingTask> ... here goes the above implementation ... </UsingTask>

  <!-- Creating the target, as we would do usually -->
  <Target Name="DemoDeleteFiles" >
    <Message Text="Starting execution of the DemoDeleteFiles..."/>
    <ItemGroup>
      <FileToDelete Include="E:\svn\trunk\xxx.txt"/>
      <FileToDelete Include="E:\svn\trunk\xxx1.txt"/>

    </ItemGroup>

    <!-- Simply create an element with the same name 
         of the inline task created in the previous example -->
    <DeleteFiles FilesToDelete="@(FileToDelete)"
                 ShowErrors="true"
                 IgnoreErrors="false">
      
      <!--Define the output...-->
      <Output TaskParameter="DeletedFiles"
              ItemName="FilesSuccessfulyDeleted"/>
      
    </DeleteFiles>

    <!--Showing the output on the screen-->
    <Message Text="Files deleted: @(FilesSuccessfulyDeleted)"/>
  </Target>
</Project>

Save the full content in a file called myfile.build and execute it from the command line

Behind the scenes

When executing the task, the MSBuild Code Factory will implement a task as follows and compile it before usage. Te below example is exactly the script generated for the above defined inline task.
Things to note:

  1. Input and output parameters are exposed as public properties
  2. Execute method contains the logic written in the inline task

The full source code:

//-------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.18010
//
//     Changes to this file may cause incorrect behavior and will be lost 
//     if the code is regenerated.
// </auto-generated>
//-------------------------------------------------------------------------

namespace InlineCode
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using System.Linq;
    using System.IO;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;


    public class DeleteFiles : Microsoft.Build.Utilities.Task
    {

        private bool _Success = true;

        public virtual bool Success
        {
            get
            {
                return _Success;
            }
            set
            {
                _Success = value;
            }
        }

        private string[] _FilesToDelete;

        public virtual string[] FilesToDelete
        {
            get
            {
                return _FilesToDelete;
            }
            set
            {
                _FilesToDelete = value;
            }
        }

        private bool _IgnoreErrors;

        public virtual bool IgnoreErrors
        {
            get
            {
                return _IgnoreErrors;
            }
            set
            {
                _IgnoreErrors = value;
            }
        }

        private bool _ShowErrors;

        public virtual bool ShowErrors
        {
            get
            {
                return _ShowErrors;
            }
            set
            {
                _ShowErrors = value;
            }
        }

        private string[] _DeletedFiles;

        public virtual string[] DeletedFiles
        {
            get
            {
                return _DeletedFiles;
            }
            set
            {
                _DeletedFiles = value;
            }
        }

        public override bool Execute()
        {

            List<string> DeletedFilesList = new List<string>();

            if (this.FilesToDelete != null && this.FilesToDelete.Count() > 0)
            {
                foreach (string file in this.FilesToDelete)
                {
                    try
                    {
                        if (File.Exists(file))
                        {
                            File.Delete(file);
                            DeletedFilesList.Add(file);
                        }
                    }
                    catch (Exception exc)
                    {
                        if (this.ShowErrors == true)
                        {
                            Console.WriteLine("Deleting file " + file
                            + " failed because of " + exc.ToString());
                        }

                        if (this.IgnoreErrors == false)
                        {
                            break;
                        }
                    }
                }
            }
            this.DeletedFiles = DeletedFilesList.ToArray();

            return _Success;
        }
    }
}

Final Thoughts

I’ve learned about inline tasks while working on the above mentioned project and it turned out to be a great functionality as I didn’t want to fragment my script. Indeed I find Inline tasks are really great if you want to keep the MSBuild project script file external-reference free.
Per se, inline tasks are not more or less powerful than implementing c# Tasks, but I would say that inline tasks give us a bit less freedom i.e. in case we need to create some methods to be reused, or creating inline classes.

    About the author:
    My name is Zoran Maksimovic. I'm a passionate programmer and interested in everything about Software Development, Object-Oriented Design and Software Architecture. Feel free to contact me or to know more about me in the about section.

    Leave a Reply

    harpold_byron constante.fleta@mailxu.com