Walkthrough: Customizing Team Foundation Build with a Custom Task

You can extend Team Foundation Build by creating your own custom tasks and running them during a build. This topic explains the steps needed to extend a build type with a custom task.

Required Permissions

To complete this walkthrough, you must have the have the Administer a build permission set to Allow. For more information, see Team Foundation Server Permissions.

Creating a Build Type

Use the New Team Build Type Creation Wizard to create a new build type. This creates the TfsBuild.proj file and checks it in to source control. You edit this file to customize the build type. For more information on creating build types, see How to: Create a New Build Type.

Creating Custom Tasks

Tasks provide the code that runs during the build process and are contained in Target elements of MSBuild project files. MSBuild is the engine behind Team Foundation Build and the custom tasks have to be in a format that MSBuild understands. Each task has to be implemented as a .NET class that implements the ITask interface, which is defined in the Microsoft.Build.Framework.dll assembly.

There are two approaches you can use when implementing a task:

  • Implement the ITask interface directly.

  • Derive your class from the helper class, Task, which is defined in the Microsoft.Build.Utilities.dll assembly. Task implements ITask and provides default implementations of some ITask members.

In both cases, you must add to your class a method named Execute, which is the method called when the task runs. This method takes no parameters and returns a Boolean value: true if the task succeeded or false if it failed. The following example shows a task that performs no action and returns true.

using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace MyTasks
{
    public class SimpleTask : Task
    {
        public override bool Execute()
        {
            return true;
        }
    }
}

Tasks also can accept parameters, raise events, and log output. For more information, see MSBuild Tasks and MSBuild Overview.

Checking Out TfsBuild.proj

After you have written your task, you have to register it and call it out in one of the targets so that your task code executes at the desired point in the build process. You will find TfsBuild.proj file in the folder $/MyTeamProject/TeamBuildTypes/MyBuildName in your Visual Studio Team System source control, where MyTeamProject is the name of your team project and is the root node of all your team project sources and MyBuildName is the name you gave to your build type when you created it using the New Team Build Type Creation Wizard. Each build type is stored in a folder in the TeamBuildTypes folder under the team project root node.

Note

Do not edit the Microsoft.TeamFoundation.Build.targets file because the customizations will apply to all the builds on that computer.

For information on checking out files, see Working with Team Foundation Source Control.

Registering Your Task

After you have created your task, you must register it by specifying your task in a UsingTask element in the TfsBuild.proj file. The UsingTask element maps the task to the assembly that contains the task's implementation. For more information, see UsingTask Element (MSBuild).

To register a custom task

  1. Open the TfsBuild.proj file.

  2. Add a UsingTask element to the file and specify the details of your task. For example:

    <UsingTask 
        TaskName="MyTasks.SimpleTask" 
        AssemblyName="MyAssembly.Build.Tasks"/>
    

    -Or-

    <UsingTask 
        TaskName="MyTasks.SimpleTask" 
        AssemblyFile="MyAssembly.Build.Tasks.dll"/>
    

    -Or-

    <UsingTask
        TaskName="MyTasks.SimpleTask"
        AssemblyFile="c:\somediskpath\MyAssembly.Build.Tasks.dll"/>
    
  3. Save the file.

Running the Custom Task

Now that you have created and registered your task, you must specify the point in the build process that you want to run your task.

To run a task

  1. Decide where in the build process you want to run your custom task. For more information on where you can extend the build process, see Understanding Team Foundation Build Configuration Files.

  2. Open TfsBuild.proj and add the Target element that you chose above.

  3. Add the task element to run your task inside the Target element. For example, the following XML in TfsBuild.proj runs the SimpleTask task in the BeforeGet target, which runs immediately before the Get target.

    <Target Name="BeforeGet">
        <SimpleTask />
    </Target>
    
  4. Save the file.

Checking in the Files

You must check in the TfsBuild.proj file for the changes to take affect. Team Foundation Build copies this file from source control onto the build computer, so any changes made to your local copy will not affect the build. For more information on checking files into source control, see How to: Check In Pending Changes.

If you need Team Foundation Build to copy the task DLL to the build computer, you must add the task DLL to source control under the team project node.

Example Task

This example creates a custom task that extends the MyBuildType build type by logging the size of the files produced by the build. The example contains two parts:

  • The task code.

  • The TfsBuild.proj file.

The logged information from this task can be seen in the build log file, Buildlog.log, which is located in the build drop folder. The build log contains information similar to the following:

The total size is 9216 bytes in d:\BuildDir\MyTeamProj\MyBuildType\sources\..\Binaries\Release dir

C# Task Code

The example below contains the code that calculates the total binary size by adding the size of the files in the binaries folder.

Note

All binaries generated during a build are located in the Binaries folder in the build directory folder on the build computer.

For this task to be included in a build, the compiled DLL must be checked into source control under the team project folder. This guarantees that the file will be copied to the build computer during a build.

The code below calculates the size of binaries present in the Binaries folder of the build directory. The solution root property is passed to this task by the Team Foundation Build script.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Framework; 
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.IO;

namespace BuildTask
{
    public class BinSize : Task
    {
        private string sourceDir;

        [Required]
        public string SourceDir
        {
            get { return sourceDir; }
            set { sourceDir = value; }
        }
        public override bool Execute()
        {
            string szDir = sourceDir + "\\..\\Binaries";
            ProcessDirectory(szDir);
            return true;
        }
        private void ProcessDirectory(string targetDirectory)
        {
            // Process the list of files found in the directory.
            string[] fileEntries = Directory.GetFiles(targetDirectory, "*.*");
            if (fileEntries.Length > 0)
            {
                dwSize = 0;
                szCurrDir = targetDirectory;
                foreach (string fileName in fileEntries)
                    ProcessFile(fileName);
                ////////////////////////////////////////////////////////////////////////
                // This log message would just print out a line in the build log file. 
                // You need to add code to do what you need to do with this data. e.g. 
                // publishing it into the warehouse for reporting. 
                ///////////////////////////////////////////////////////////////////////
                Log.LogMessage("The total size of is {0} bytes in {1} dir",
                    dwSize, targetDirectory);
            }
            // Recurse into subdirectories of this directory.
            string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
            foreach (string subdirectory in subdirectoryEntries)
                ProcessDirectory(subdirectory);
        }
        private void ProcessFile(string path)
        {
            FileInfo fi = new FileInfo(path);
            dwSize = dwSize + fi.Length;
        }
        private long dwSize;
        private string szCurrDir;
    }
}

TfsBuild.proj File

Once the task is compiled and checked into source control, it must be called from the TfsBuild.proj file. In this example, the task should be called after the files have been compiled and all binaries have been copied to the Binaries directory. Therefore, the task should be run in the BeforeDropBuild target. For more information on the extensible targets in TfsBuild.proj, see Understanding Team Foundation Build Configuration Files.

The example below contains the code in the modified TfsBuild.proj file. The example XML is almost entirely generated by the New Team Build Type Creation Wizard with the exception of the UsingTask element and the Target element located at the end of the file.

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild" xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
  <!-- TO EDIT BUILD TYPE DEFINITION

  To edit the build type, you will need to edit this file which was generated
  by the Create New Build Type wizard.  This file is under source control and
  needs to be checked out before making any changes.

  The file is available at -
      $/{TeamProjectName}/TeamBuildTypes/{BuildTypeName}
  where you will need to replace TeamProjectName and BuildTypeName with your
  Team Project and Build Type name that you created

  Checkout the file
    1. Open Source Control Explorer by selecting View -> Other Windows -> Source Control Explorer
    2. Ensure that your current workspace has a mapping for the $/{TeamProjectName}/TeamBuildTypes folder and 
       that you have done a "Get Latest Version" on that folder
    3. Browse through the folders to {TeamProjectName}->TeamBuildTypes->{BuildTypeName} folder
    4. From the list of files available in this folder, right click on TfsBuild.Proj. Select 'Check Out For Edit...'


  Make the required changes to the file and save

  Checkin the file
    1. Right click on the TfsBuild.Proj file selected in Step 3 above and select 'Checkin Pending Changes'
    2. Use the pending checkin dialog to save your changes to the source control

  Once the file is checked in with the modifications, all future builds using
  this build type will use the modified settings
  -->
  <!-- Do not edit this -->
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v8.0\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
  <ProjectExtensions>
    <!--  DESCRIPTION
     The description is associated with a build type. Edit the value for making changes.
    -->
    <Description>
    </Description>
    <!--  BUILD MACHINE
     Name of the machine which will be used to build the solutions selected.
    -->
    <BuildMachine>MyBuildMachine</BuildMachine>
  </ProjectExtensions>
  <PropertyGroup>
    <!--  TEAM PROJECT
     The team project which will be built using this build type.
    -->
    <TeamProject>MyTeamProject</TeamProject>
    <!--  BUILD DIRECTORY
     The directory on the build machine that will be used to build the
     selected solutions. The directory must be a local path on the build
     machine (for example, c:\build).
    -->
    <BuildDirectoryPath>d:\build</BuildDirectoryPath>
    <!--  DROP LOCATION
      The location to drop (copy) the built binaries and the log files after
     the build is complete. This location must be a valid UNC path of the
     form \\Server\Share. The build machine service account and application
     tier account must have read write permission on this share.
    -->
    <DropLocation>\\MyDropServer\drops</DropLocation>
    <!--  TESTING
     Set this flag to enable/disable running tests as a post build step.
    -->
    <RunTest>true</RunTest>
    <!--  WorkItemFieldValues
      Add or edit key value pairs to set values for fields in the work item created
      during the build process. Please make sure the field names are valid 
      for the work item type being used.
    -->
    <WorkItemFieldValues>Priority=1;Severity=1</WorkItemFieldValues>
    <!--  CODE ANALYSIS
       To change CodeAnalysis behavior, edit this value. Valid values for this
       can be Default, Always, or Never.

     Default - To perform code analysis according to individual project settings.
     Always - To always perform code analysis irrespective of project settings.
     Never - To never perform code analysis irrespective of project settings.
     -->
    <RunCodeAnalysis>Default</RunCodeAnalysis>
    <!--  UPDATE ASSOCIATED WORK ITEMS
     Set this flag to enable/disable updating associated work items on a successful build
    -->
    <UpdateAssociatedWorkItems>true</UpdateAssociatedWorkItems>
  </PropertyGroup>
  <ItemGroup>
    <!--  SOLUTIONS
     The path of the solutions to build. To add or delete solutions, edit this
     value. For example, to add a solution MySolution.sln, add following line -
         <SolutionToBuild Include="$(SolutionRoot)\path\MySolution.sln" />

     To change the order in which the solutions are built, modify the order in
     which the solutions appear below.
    -->
    <SolutionToBuild Include="$(SolutionRoot)\ConsoleApplication7\ConsoleApplication7.sln" />
  </ItemGroup>
  <ItemGroup>
    <!--  CONFIGURATIONS
     The list of configurations to build. To add or delete configurations, edit
     this value. For example, to add a new configuration, add the following lines -
         <ConfigurationToBuild Include="Debug|x86">
             <FlavorToBuild>Debug</FlavorToBuild>
             <PlatformToBuild>x86</PlatformToBuild>
         </ConfigurationToBuild>

     The Include attribute value should be unique for each ConfigurationToBuild node.
    -->
    <ConfigurationToBuild Include="Release|Any CPU">
      <FlavorToBuild>Release</FlavorToBuild>
      <PlatformToBuild>Any CPU</PlatformToBuild>
    </ConfigurationToBuild>
    <ConfigurationToBuild Include="Debug|Any CPU">
      <FlavorToBuild>Debug</FlavorToBuild>
      <PlatformToBuild>Any CPU</PlatformToBuild>
    </ConfigurationToBuild>
    <ConfigurationToBuild Include="Debug|x86">
      <FlavorToBuild>Debug</FlavorToBuild>
      <PlatformToBuild>x86</PlatformToBuild>
    </ConfigurationToBuild>
    <ConfigurationToBuild Include="Release|x86">
      <FlavorToBuild>Release</FlavorToBuild>
      <PlatformToBuild>x86</PlatformToBuild>
    </ConfigurationToBuild>
  </ItemGroup>
  <ItemGroup>
    <!--  TEST ARGUMENTS
     If the RunTest is set to true then the following test arguments will be
     used to run tests.

     To add or delete new testlist or to choose a metadata file (.vsmdi) file, edit this value.
     For example, to run BVT1 and BVT2 type tests mentioned in the Helloworld.vsmdi file, add the following -

     <MetaDataFile Include="$(SolutionRoot)\HelloWorld\HelloWorld.vsmdi">
         <TestList>BVT1;BVT2</TestList>
     </MetaDataFile>

     Where BVT1 and BVT2 are valid test types defined in the HelloWorld.vsmdi file.
     MetaDataFile - Full path to test metadata file.
     TestList - The test list in the selected metadata file to run.

     Please note that you must specify the .vsmdi file relative to $(SolutionRoot)
    -->
    <MetaDataFile Include="$(SolutionRoot)\ConsoleApplication7\ConsoleApplication7.vsmdi">
      <TestList>P1 Tests;P2 Tests</TestList>
    </MetaDataFile>
  </ItemGroup>
  <ItemGroup>
    <!--  ADDITIONAL REFERENCE PATH
     The list of additional reference paths to use while resolving references.
     For example,
         <AdditionalReferencePath Include="C:\MyFolder\" />
         <AdditionalReferencePath Include="C:\MyFolder2\" />
    -->
  </ItemGroup>
  
  <UsingTask TaskName="BuildTask.BinSize" AssemblyFile="$(SolutionRoot)\\tools\\BuildTask.dll" />

  <Target Name="BeforeDropBuild">
    <BinSize SourceDir="$(SolutionRoot)" />
  </Target>
  
</Project>

See Also

Tasks

How To: Write a Task

Concepts

MSBuild

Other Resources

Customizing Team Foundation Build