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 definition with a custom task.
Required Permissions
To complete this walkthrough, you must have the Administer a build permission set to Allow. For more information, see Team Foundation Server Permissions.
Creating a Build Definition
Use the Build Definition dialog box to create a new build definition. You can either share an existing TFSBuild.proj file, or create a new one with the MSBuild Project File Creation Wizard. You edit the TFSBuild.proj file to customize each build definition that is associated with it. For more information about creating build definitions, see How to: Create a Build Definition.
Creating Custom Tasks
Tasks provide the code that runs during the build process. These tasks are contained in Target elements of MSBuild project files. MSBuild is the engine behind Team Foundation Build. 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 approaches, 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 succeeds or false if it fails. 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. If you used the MSBuild Project File Creation Wizard to create an MSBuild project file, and accepted the default location in source control, your TFSBuild.proj file is located in the folder $/MyTeamProject/TeamBuildTypes/MyBuildName in your Visual Studio Team System source control. In this scenario, 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 definition for which you originally created the TFSBuild.proj file.
To determine the source control location of the TFSBuild.proj file, select the build definition in the Builds folder in Team Explorer, right-click it, and then click Edit. The TFSBuild.proj file's source control location is displayed on the Project File pane of the Build Definition dialog.
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 Version Control.
Registering Your Task
After you create 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
Open the TFSBuild.proj file.
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"/>
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
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.
Open TFSBuild.proj and add the Target element that you chose above.
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>
Save the file.
Checking in the Files
You must check in the TFSBuild.proj file for the changes to take effect. Team Foundation Build copies this file from source control onto the build computer, so any changes made to the local copy on your computer 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 build definition or definitions that are associated with the TFSBuild.proj file 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.txt, 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 agent.
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 agent during a build.
The following code 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 MSBuild Project File 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
TODO: Update all of the comments in this file!
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\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<ProjectExtensions>
<!-- Team Foundation Build Version - DO NOT CHANGE -->
<ProjectFileVersion>2</ProjectFileVersion>
<!-- DESCRIPTION
TODO: Obsolete.
-->
<Description>this one automatically builds on check in</Description>
<!-- BUILD MACHINE
TODO: Obsolete.
-->
<BuildMachine>ahetod-test2</BuildMachine>
</ProjectExtensions>
<PropertyGroup>
<!-- Properties set by the build type creation wizard -->
<!-- TEAM PROJECT
TODO: Obsolete.
-->
<TeamProject>TeamProjectName</TeamProject>
<!-- BUILD DIRECTORY
TODO: Obsolete.
-->
<BuildDirectoryPath>C:\Documents and Settings\user\Local Settings\Temp\1\TeamProjectName\BuildDefinitionName</BuildDirectoryPath>
<!-- DROP LOCATION
TODO: Obsolete.
-->
<DropLocation>\\UNKNOWN\drops</DropLocation>
<!-- TESTING
Set this flag to enable/disable running tests as a post build step.
-->
<RunTest>false</RunTest>
<!-- CODE ANALYSIS
To change CodeAnalysis behavior edit this value. Valid values for this
can be Default,Always or Never.
Default - To perform code analysis as per the individual project settings
Always - To always perform code analysis irrespective of project settings
Never - To never perform code analysis irrespective of project settings
-->
<RunCodeAnalysis>Never</RunCodeAnalysis>
<!-- Additional Properties -->
<!-- WorkItemType
The type of the work item created on a build break - if empty, "Bug" will be used
-->
<WorkItemType Condition=" '$(WorkItemType)'=='' "></WorkItemType>
<!-- WorkItemFieldValues
Add/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>Symptom=build break;Steps To Reproduce=Start the build using Team Build</WorkItemFieldValues>
<!-- WorkItemTitle
Title for the work item created on build failure
-->
<WorkItemTitle>Build failure in build:</WorkItemTitle>
<!-- DescriptionText
Description for the work item created on a build failure
-->
<DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>
<!-- BuildLogText
Additional text for the work item create on a build failure.
-->
<BuildlogText>The build log file is at:</BuildlogText>
<!-- ErrorWarningLogText
Additional text for the work item create on a build failure
-->
<ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>
<!-- UpdateAssociatedWorkItems
Set this flag to enable/disable updating associated workitems on a successful build
-->
<UpdateAssociatedWorkItems>true</UpdateAssociatedWorkItems>
<!-- AdditionalVCOverrides
Additional text for the VCOverrides file generated for Visual C++ projects
-->
<AdditionalVCOverrides></AdditionalVCOverrides>
<!-- CustomPropertiesForClean
Custom properties to pass to the MSBuild task while calling the "Clean" target for all solutions.
The format should be: PropertyName1=value1;PropertyName2=value2;...
-->
<CustomPropertiesForClean></CustomPropertiesForClean>
<!-- CustomPropertiesForBuild
Custom properties to pass to the MSBuild task while calling the default targets for all solutions.
The format should be: PropertyName1=value1;PropertyName2=value2;... To pass custom properties to
individual solutions, use the Properties metadata item of the SolutionToBuild ItemGroup.
-->
<CustomPropertiesForBuild></CustomPropertiesForBuild>
</PropertyGroup>
<ItemGroup>
<!-- SOLUTIONS
The paths of the solutions to build. To add/delete solutions, edit this
ItemGroup. For example, to add a solution MySolution.sln, add the following line:
<SolutionToBuild Include="$(BuildProjectFolderPath)\path\MySolution.sln" />
To change the order in which the solutions are built, modify the order in
which the solutions appear below.
To call a target (or targets) other than the default, add a metadata item named
Targets. To pass custom properties to the solution, add a metadata item named
Properties. For example, to call the targets MyCustomTarget1 and MyCustomTarget2,
passing in properties Property1 and Property2, add the following:
<SolutionToBuild Include="$(BuildProjectFolderPath)\path\MySolution.sln">
<Targets>MyCustomTarget1;MyCustomTarget2</Targets>
<Properties>Property1=Value1;PropertyTwo=Value2</Properties>
</SolutionToBuild>
-->
<SolutionToBuild Include="$(BuildProjectFolderPath)/../../SimpleAppToBuild/SimpleAppToBuild.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>
</ItemGroup>
<ItemGroup>
<!-- CONFIGURATIONS
The list of configurations to build. To add/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>
</ItemGroup>
<ItemGroup>
<!-- TEST ARGUMENTS
If the RunTest property is set to true then the following test arguments will be used to run
tests. Tests can be run by specifying one or more test lists and/or one or more test containers.
To run tests using test lists, add MetaDataFile items and associated TestLists here:
<MetaDataFile Include="$(SolutionRoot)\HelloWorld\HelloWorld.vsmdi">
<TestList>BVT1;BVT2</TestList>
</MetaDataFile>
To run tests using test containers, add TestContainer items here:
<TestContainer Include="$(OutDir)\HelloWorldTests.dll" />
<TestContainer Include="$(SolutionRoot)\TestProject\WebTest1.webtest" />
<TestContainer Include="$(SolutionRoot)\TestProject\LoadTest1.loadtest" />
-->
</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>