Wednesday, May 11, 2011

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>