MSBuild Task Factories: guest starring Windows Powershell

MSBuild Task Factories: guest starring Windows Powershell

  • Comments 6

One of the cool new features of MSBuild 4.0 is the extensible task factory.  Task factories allow you to include scripts directly in your project file (or an imported .targets file) and have those scripts parsed and executed by your favorite interpreter.  Those scripts might even be C# or VB.NET code snippets that get compiled into assemblies and executed on-the-fly during the build.  This significantly lowers the bar to doing specialized work in your build for which there is no built-in MSBuild Task, since you don’t have to check into your source control repository a precompiled .dll with your custom task any more.  You may still want to do that for performance reasons or for the design-time experience of writing MSBuild tasks in C# and get all the C# Intellisense that you only get in a C# project, but for short tasks, “inline tasks” that are built and executed on-the-fly may be just the ticket you need.

MSBuild 4.0 ships with C#, VB.NET and XAML task factories built-in, so you can define a custom task in C# or VB.NET inline today.  But writing your own task factory will allow you to write inline tasks in Perl, Python, or in this case… Windows Powershell script.

Consider, for the sake of example, that there is no MSBuild task that sends an email at the conclusion of a build.  Fixing that with a custom MSBuild task, compiled into a .dll, seems a bit heavyweight.  But defining that task right there in your project file is easy.  Here’s how you might write a Powershell inline task using the Windows Powershell task factory.  Notice below how we first define the task in terms of the code it executes and its input and output parameters.  Then in a standard MSBuild Target we invoke that inline task as we would any other MSBuild task. 

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask
    TaskFactory="WindowsPowershellTaskFactory"
    TaskName="SendMail"
    AssemblyFile="$(TaskFactoryPath)WindowsPowershellTaskFactory.dll">
    <ParameterGroup>
      <From Required="true" ParameterType="System.String" />
      <Recipients Required="true" ParameterType="System.String" />
      <Subject Required="true" ParameterType="System.String" />
      <Body Required="true" ParameterType="System.String" />
      <RecipientCount Output="true" />
    </ParameterGroup>
    <Task>
      <![CDATA[
        $smtp = New-Object System.Net.Mail.SmtpClient
        $smtp.Host = "mail.microsoft.com"
        $smtp.Credentials = [System.Net.CredentialCache]::DefaultCredentials
        $smtp.Send($From, $Recipients, $Subject, $Body)
        $RecipientCount = $Recipients.Split(';').Length
        $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High", "Send mail to {0} recipients.", $recipientCount)
      ]]>
    </Task>
  </UsingTask>

  <PropertyGroup>
    <BuildLabEmail>buildlab@yourcompany.com</BuildLabEmail>
    <BuildRecipients>interested@party.com;boss@guy.com</BuildRecipients>
  </PropertyGroup>
  
  <Target Name="Build">
    <SendMail From="$(BuildLabEmail)" Recipients="$(BuildRecipients)" Subject="Build status" Body="Build completed">
      <Output TaskParameter="RecipientCount" PropertyName="RecipientCount" />
    </Add>
  </Target>
</Project>

This project would build, and send a built completion email to recipients determined by the project itself.  The only assembly you need is the Windows Powershell task factory itself, which can be reused for all your projects that include inline Windows Powershell scripts.

Note that the inline task above will not run without the task factory .dll that it points to, which in this case is not shipped with VS2010.  The Windows Powershell task factory is available as a sample task factory on the MSDN Code Gallery.  Feel free to check it out, and give feedback.  It’s just a sample, so all the “no guarantees for fitness to any particular task” limitations apply.  But you get the source code, so you can tweak it or file bugs if you wish.

Leave a Comment
  • Please add 6 and 5 and type the answer here:
  • Post
  • This is simply outstanding and very useful. I like this post. Thanks for giving brief knowledge.

  • Great stuff! This looks very similar to NAnt's <script> task where you can build inline code snippets or custom tasks that get executed during the build.  Glad to see MSBuild finally incorporating this.

    Doug

  • Nice example and I'm glad to see that this *can* be done.  That said, it is a bummer that inline PowerShell script didn't make it into MSBuild 4.0.  Honestly, I would have expected inline PowerShell script *way* before inline C#.  I'm not sure why I would use inline C# when it is pretty easy to build & import a C# (task-based) dll.  Plus debugging the C# would be easier (I'm guessing) when I have built it into a DLL vs dynamic compilation.

  • Very nice. This should really simplify creating custom build scripts =)

  • Something I want to emphasize here is that there's 3 parts to make this work:

    (1) The task factory. This is compiled code that can take the script and list of parameters and create an object that MSbuild can call like a task. I'd expect few people to write these: there's only so many kinds of script, and as Andrew pointed out, MSbuild ships a factory in 4.0 that supports any CodeDOM language installed.

    (2) The declaration of a particular task using a factory. This is the <UsingTask> tag body shown here, with the script in it. More users will write this, but probably not most. It would typically be hidden away in a .tasks file, or a globally imported settings file, and not everyone using a build process will ever see it.

    (3) Actual useage of the task. This is the common case. The great part here is that the XML looks exactly like a regular task. There is no way to tell, from the use of the task, that it's not a regular compiled task! Typically, a task defined once with the <UsingTask> will be used in many places.

    The upshot of this is that, given the necessary factory implementation, a friend can send you a cool new task by just pasting the <UsingTask> body into an email or blog post, like this. Once you paste that in your shared imported file, you can use it like a regular task. You didn't write any code!

    Dan - MSBuild team

  • Hi Dan,

    thanks for your post & comment. I am pretty much done with Powershell support in rfb (MsBuild without XML). It's good you guys put that in. From my perspective, I'd allow an rfb user to target either 3.5 or 4.0 and then I could control internally how to construct the integration (either using my own Task or the UsingTask construct you show).

Page 1 of 1 (6 items)