Writing MSBuild Custom Task

March 13, 2008 on 8:16 pm | In MSBuild, Continuous Integration, DotNet |

Scenario: we have Subversion server to manage our source code and a build server (CruiseControl.NET) to manage our deployment. We have decided to automatically set the SVN revision number to our assembly version.

[assembly: System.Reflection.AssemblyVersion(“1.2.3.0″)]
[assembly: System.Reflection.AssemblyFileVersion(“1.2.3.0″)]

So we will replace the last 0 with the current Subversion revision number. How to do this? One way to achieve this is to modify the AssemblyInfo.cs and read the modified number from that exists. The file modification is easy with MSBuild Community Task FileUpdate

<FileUpdate Files=Cic.P001001PropertiesAssemblyInfo.csRegex=(d+).(d+).(d+).(d+)ReplacementText=$1.$2.$3.$(RevisionNumber)/>

But how to read it back? Well there is no easy way. I have written a custom MSBuidl task to achieve this like this:

<AssemblyInfoReader Path=PropertiesAssemblyInfo.csProperty=AssemblyVersion>
    <Output TaskParameter=ValueItemName=ApplicationVersion/>
</AssemblyInfoReader>


Writing a custom MSBuild task is fairly easy. You have to Reference Microsoft.Build.Framework and Microsoft.Build.Utilities and implement Microsoft.Build.Framework.ITask. Just like this:

namespace Cic.MsBuildTasks
{
    public class AssemblyInfoReader : Microsoft.Build.Framework.ITask
    {
        #region Private Varaibels

        private string path;
        private string property;
        private string value;

        #endregion

        #region Fields
        [Microsoft.Build.Framework.Required]
        public string Path
        {
            get { return path; }
            set { path = value; }
        }

        [Microsoft.Build.Framework.Required]
        public string Property
        {
            get { return property; }
            set { property = value; }
        }

        [Microsoft.Build.Framework.Output]
        public string Value
        {
            get { return this.value; }
            set { this.value = value; }
        }

        #endregion

        #region ITask Members
        private Microsoft.Build.Framework.IBuildEngine engine;
        Microsoft.Build.Framework.IBuildEngine
            Microsoft.Build.Framework.ITask.BuildEngine
        {
            get
            {
                return engine;
            }
            set
            {
                engine = value;
            }
        }

        bool Microsoft.Build.Framework.ITask.Execute()
        {
            string message;
            value = string.Empty;
            try
            {
                value = MyReadAssemblyInfoProperty();
                message = string.Format(
                    “AssemblyInfo property {0} read. Property value {1}”,
                    property, value);
            }
            catch (System.Exception e)
            {
                message = string.Format(
                    “Error reading AssemblyInfo property {0}. Error: {1}”,
                    property, e.Message);
            }

            Microsoft.Build.Framework.BuildMessageEventArgs args =
                new Microsoft.Build.Framework.BuildMessageEventArgs(
                message, string.Empty, “AssemblyInfoReaderTask”,
                Microsoft.Build.Framework.MessageImportance.Normal);
            engine.LogMessageEvent(args);

            return true;
        }

        private Microsoft.Build.Framework.ITaskHost host;
        Microsoft.Build.Framework.ITaskHost Microsoft.Build.Framework.ITask.HostObject
        {
            get
            {
                return host;
            }
            set
            {
                host = value;
            }
        }

        #endregion

        #region Internals
        private string MyReadAssemblyInfoProperty()
        {
            string propertyValue;

            // Eraly return
            if (!System.IO.File.Exists(path)) return “”;

            foreach (string line in System.IO.File.ReadAllLines(path))
            {
                if (line.Contains(property))
                {
                    try
                    {
                        propertyValue = line.Remove(0, line.IndexOf(‘”‘) + 1);
                        propertyValue = propertyValue.Remove(
                            propertyValue.LastIndexOf(‘”‘),
                            propertyValue.Length - propertyValue.LastIndexOf(‘”‘));
                        // return matching property value
                        return propertyValue;
                    }
                    catch
                    {
                        // Ignore errors
                    }
                }
            }

            return string.Empty;
        }
        #endregion
    }
}


5 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Two questions:

    1. What is the significance of LogMessageEvent? Is that the way to return data to MSBuild? A little explanation (or link) might help there.

    2. Is it necessary to declare the “host” property? Maybe I’m going blind, but I can’t see where it is used. ;-)

    Comment by David White — April 14, 2008 #

  2. Heya mate, think you need to escape the periods in your regex, otherwise they’ll match anything:

    (d+)\.(d+)\.(d+)\.(d+)

    Comment by Nick — July 29, 2008 #

  3. Your blog is interesting!

    Keep up the good work!

    Comment by AlexM — August 16, 2008 #

  4. […] Writing MSBuild Custom Task […]

    Pingback by Automate adding/using of the SVN revision to/as the version of a .net project, through a build step « theKindOfMe — April 27, 2009 #

  5. […] Writing MSBuild Custom Task […]

    Pingback by Automate adding/using of the SVN revision to/as the version of a .net project, through a build target « theKindOfMe — April 27, 2009 #

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Powered by WordPress with Pool theme design by Borja Fernandez.
Entries and comments feeds. Valid XHTML and CSS. ^Top^