http://blogs.clariusconsulting.net/kzu

Daniel Cazzulino's Blog

Go Back to
kzu′s Latest post

Writing inline MSBuild tasks in C# for one-liners

Every now and then, when trying to do something in MSBuild, I hit a roadblock (or maybe just some unintuitive “feature”) and I’m left thinking “gosh, now I have to create a custom MSBuild task for this one-liner that is SO trivial in plain C#!”.

Well, no more with MSBuild 4.0. You can now write inline tasks in plain C# which can of course have input and output parameters.

For example, I needed to checkout a file from TFS before some task run and tried to write it. Checking out from TFS can easily be done with an Exec task:

<Exec Command="&quot;$(VS100COMNTOOLS)..\IDE\tf.exe&quot; checkout $(ExtenderNamesTargetFile)"
      WorkingDirectory="$(MSBuildProjectDirectory)" />

(Note how I’m NOT using $(DevEnvDir))

That’s simple enough, but it has a problem: performance. The tf.exe command connects to the TFS server every time for the checkout, and this can take some time. Not something you want to do if the file is already checked out! So I needed a simple condition: just checkout if the file is readonly.

Of course there’s no built-in way in MSBuild to check if a file is readonly. Inline MSBuild task to the rescue!

<UsingTask TaskName="IsFileReadOnly" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
        <FileName ParameterType="System.String" Required="true" />
        <IsReadOnly ParameterType="System.Boolean" Output="true" />
    </ParameterGroup>
    <Task>
        <Using Namespace="System.IO"/>
        <Code Type="Fragment" Language="cs">
            <![CDATA[
this.IsReadOnly = new FileInfo(this.FileName).IsReadOnly;
]]>
        </Code>
    </Task>
</UsingTask>

This task simply constructs a file info and returns its readonly state. Note that it has both input and output parameters.

The parameters simply become properties that you can access from within the code using “this.” as MSBuild is surely generating a class for you with the given usings and properties and compiling it on the fly with the optional References element that you can specify too.

You consume it from a targets like any built-in or custom task:

<IsFileReadOnly FileName="$(ExtenderNamesTargetFile)">
    <Output PropertyName="ShouldCheckoutExtenderNames" TaskParameter="IsReadOnly"/>
</IsFileReadOnly>

And with that, I have a property to use for my condition for the checkout command:

<Exec Command="&quot;$(VS100COMNTOOLS)..\IDE\tf.exe&quot; checkout $(ExtenderNamesTargetFile)"
      WorkingDirectory="$(MSBuildProjectDirectory)"
      Condition="$(ShouldCheckoutExtenderNames)"/>

Pretty cool and flexible!

Comments

11 Comments

  1. [...] Writing inline MSBuild tasks in C# for one-liners – Daniel Cazzulino shows how you can easily extend MSBuild using C# to define tasks in your build file. [...]

  2. Great task.

    I successfully modified your example to update my CommonAssemblyInfo.cs, which is the common file where I keep the version number for the whole solution.

    I also replaced
    "$(VS100COMNTOOLS)..\IDE\tf.exe"
    with
    "$(DevEnvDir)tf.exe&quot

    which looked a bit more compact.

    Stefano

  3. Stefano, DevEnvDir doesn’t work from cmdline or CI builds ;)

  4. Ouch… didn’t know that :)
    I am currently running the build project from the command line, but I’ll fix it just in case we move to a CI (hopefully very soon…).

  5. [...] to this blog entry, I changed it to [...]

  6. Thanks a lot for this concise and helpful writeup – I felt exactly the same when I read your “gosh, now I have to create a custom MSBuild task for this one-liner that is SO trivial in plain C#!”

  7. I am getting the below error when trying to use your task, though I am using tools version 12.0 – so I am not sure why?

    The element beneath element is unrecognized

  8. The element ParameterGroup beneath element UsingTask is unrecognized