Run batch file from TFS build?

From time to time, I have to run batch files to perform some cleanup or restore jobs on CI servers.

Usually this is as simple as logging into the remote server and manually launching a batch file. But what about automating this process a little bit and using TFS to launch scripts?

Currently I use TFS 2010, but this approach will also work with the 2012 and 2013 versions of TFS.

In order to do this, a new build definition must be created along with a template that has a few activities. After this, the batch file will be able to run on the build server using the TFS setup. The results will then be seen in the Build Details window.

bat

Build Template Creation
To get an empty template, I’ve taken DefaultTemplate.xaml (the default template in the TFS build system) and deleted all default activities using a graphic designer.

Here is a list of all arguments which are needed in the template.

agent vars

Then a few activities are added. See the final image below:

flow

1. Drag Sequence from the Toolbox and add a new variable into its scope. Call it BuildDetail, and set the type to: Microsoft.TeamFoundation.Build.Client.IBuildDetail

invoke args

2. Next, add GetBuildDetail using BuildDetail. BuildDetail will be used later when creating the workspace.

2

3. Add UpdateBuildNumber to show a more friendly build name. Use the BuildNumberFormat argument to configure it.

3

4. Add AgentScope and declare all of the variables listed below:

agent_v

5. Add GetBuildAgent and use BuildAgent to store the results.

5

6. Add GetBuildDirectory and store the results in BuildDirectory.

6

Steps 7, 8, and 9 initialize the path variables.

WorkspaceName = String.Format("{0}_{1}_{2}", BuildDetail.BuildDefinition.Id, Microsoft.TeamFoundation.LinkingUtilities.DecodeUri(BuildAgent.Uri.AbsoluteUri).ToolSpecificId, BuildAgent.ServiceHost.Name)

ScriptsDirectory = String.Format("{0}\Scripts", BuildDirectory)

ScriptFile = String.Format("{0}\{1}", ScriptsDirectory, ScriptFileName)

7

10. Add DeleteWorkspace and use WorkspaceName. This is used to rid the workspace of old data.

10

11. Add CreateWorkspace. This prepares the new workspace for the new batch file.

11

12. Add SyncWorkspace. This activity is used to load the batch file in a local folder on the build server.

12

13. Add InvokeProcess. Now the batch file can be executed.

13

There are a few more things to keep in mind regarding this activity. To view the build output and error output, use WriteBuildMessage and WriteBuildError. Set stdOutput as a parameter to WriteBuildOutput, and set errOutput to WriteBuildError.

call batch

In order to see these messages in the build log, don’t forget to set the Importance parameter for WriteBuildMessage to High.

14. The template is ready to be stored under TFS source control, so add it to the same folder where DefaultTemplate.xaml is.

Build definition
It’s time to create a new build definition. Add a new definition (using the Team Explorer Builds window) and set some basic settings like Definition Name, Description, etc.

Next, configure the Source Settings tab and Process tab.

In the first tab, select the batch file’s folder in TFS.

source

In the Process tab, select the custom build template that was stored on step 14.

templateSet ScriptFileName to file name that should be invoked. This file should be stored under TFS, in the folder that has been selected on the Source tab (previous step).

Set ScriptFileName to the batch filename that should be invoked. This file should be stored under TFS, in the folder that has been selected on the Source tab (previous step).

That’s it!

Updated: FMakeTemplate.xaml is a build template that I’ve used to launch a batch file with an F# Make Build automation system.

<Activity mc:Ignorable="sads sap sap2010" x:Class="FMake.Process" this:Process.BuildNumberFormat="[&quot;$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)&quot;]" this:Process.AgentSettings="[New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }]" this:Process.Verbosity="[Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]" this:Process.SupportedReasons="All" this:Process.SourceAndSymbolServerSettings="[New Microsoft.TeamFoundation.Build.Workflow.Activities.SourceAndSymbolServerSettings(True, Nothing)]"
 xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
 xmlns:ca="clr-namespace:CustomActivities.Activities;assembly=CustomActivities"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:mt="clr-namespace:Microsoft.TeamFoundation;assembly=Microsoft.TeamFoundation.Common"
 xmlns:mtbc="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client"
 xmlns:mtbc1="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
 xmlns:mtbw="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow"
 xmlns:mtbw1="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
 xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow"
 xmlns:mtbwa1="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
 xmlns:mtbwt="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Tracking;assembly=Microsoft.TeamFoundation.Build.Workflow"
 xmlns:mttbb="clr-namespace:Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities;assembly=Microsoft.TeamFoundation.TestImpact.BuildIntegration"
 xmlns:mtvc="clr-namespace:Microsoft.TeamFoundation.VersionControl.Client;assembly=Microsoft.TeamFoundation.VersionControl.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
 xmlns:mtvc1="clr-namespace:Microsoft.TeamFoundation.VersionControl.Client;assembly=Microsoft.TeamFoundation.VersionControl.Client"
 xmlns:mtvc2="clr-namespace:Microsoft.TeamFoundation.VersionControl.Common;assembly=Microsoft.TeamFoundation.VersionControl.Common"
 xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities"
 xmlns:s="clr-namespace:System;assembly=mscorlib"
 xmlns:s1="clr-namespace:System;assembly=System"
 xmlns:s2="clr-namespace:System;assembly=System.Core"
 xmlns:s3="clr-namespace:System;assembly=System.ServiceModel"
 xmlns:s4="clr-namespace:System;assembly=System.ComponentModel.Composition"
 xmlns:sa="clr-namespace:SourceLink.Activities;assembly=SourceLink.Tfs"
 xmlns:sa1="clr-namespace:System.Activities;assembly=System.Activities"
 xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities"
 xmlns:sads="http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger"
 xmlns:sae="clr-namespace:System.Activities.Expressions;assembly=System.Activities"
 xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"
 xmlns:sap2010="http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation"
 xmlns:sas="clr-namespace:System.Activities.Statements;assembly=System.Activities"
 xmlns:sav="clr-namespace:System.Activities.Validation;assembly=System.Activities"
 xmlns:sax="clr-namespace:System.Activities.XamlIntegration;assembly=System.Activities"
 xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
 xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.Core"
 xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System"
 xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel"
 xmlns:sco="clr-namespace:System.Collections.ObjectModel;assembly=mscorlib"
 xmlns:si="clr-namespace:System.IO;assembly=mscorlib"
 xmlns:si1="clr-namespace:System.IO;assembly=System"
 xmlns:si2="clr-namespace:System.IO;assembly=System.Core"
 xmlns:si3="clr-namespace:System.IO;assembly=WindowsBase"
 xmlns:si4="clr-namespace:System.IO;assembly=System.ServiceModel"
 xmlns:sl="clr-namespace:System.Linq;assembly=System.Core"
 xmlns:swm="clr-namespace:System.Windows.Markup;assembly=WindowsBase"
 xmlns:swm1="clr-namespace:System.Windows.Markup;assembly=PresentationFramework"
 xmlns:swm2="clr-namespace:System.Windows.Markup;assembly=PresentationCore"
 xmlns:swm3="clr-namespace:System.Windows.Markup;assembly=System.Xaml"
 xmlns:this="clr-namespace:FMake"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <x:Members>
 <x:Property Name="BuildNumberFormat" Type="InArgument(x:String)" />
 <x:Property Name="AgentSettings" Type="InArgument(mtbwa:AgentSettings)" />
 <x:Property Name="Verbosity" Type="InArgument(mtbw:BuildVerbosity)" />
 <x:Property Name="Metadata" Type="mtbw:ProcessParameterMetadataCollection" />
 <x:Property Name="SupportedReasons" Type="mtbc:BuildReason" />
 <x:Property Name="SourceAndSymbolServerSettings" Type="InArgument(mtbwa:SourceAndSymbolServerSettings)" />
 <x:Property Name="ScriptFileName" Type="InArgument(x:String)" />
 <x:Property Name="FMakeDirectory" Type="InArgument(x:String)" />
 </x:Members>
 <this:Process.Metadata>
 <mtbw:ProcessParameterMetadataCollection />
 </this:Process.Metadata>
 <sap2010:WorkflowViewState.IdRef>FMake.Process_1</sap2010:WorkflowViewState.IdRef>
 <mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
 <Sequence DisplayName="Run FAKE Process" sap2010:WorkflowViewState.IdRef="Sequence_2" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
 <Sequence.Variables>
 <Variable x:TypeArguments="mtbc:IBuildDetail" Name="BuildDetail" />
 <Variable x:TypeArguments="x:String" Name="BuildDirectory" />
 </Sequence.Variables>
 <mtbwa:GetBuildDetail DisplayName="Get Build Details" sap2010:WorkflowViewState.IdRef="GetBuildDetail_1" mtbwt:BuildTrackingParticipant.Importance="Low" Result="[BuildDetail]" />
 <mtbwa:UpdateBuildNumber BuildNumberFormat="[BuildNumberFormat]" DisplayName="Update Build Number" sap2010:WorkflowViewState.IdRef="UpdateBuildNumber_1" />
 <mtbwa:AgentScope DataToIgnore="" DisplayName="Run On Agent" sap2010:WorkflowViewState.IdRef="AgentScope_1" MaxExecutionTime="[AgentSettings.MaxExecutionTime]" MaxWaitTime="[AgentSettings.MaxWaitTime]" ReservationSpec="[AgentSettings.GetAgentReservationSpec()]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
 <mtbwa:AgentScope.Variables>
 <Variable x:TypeArguments="mtbc:IBuildAgent" Name="BuildAgent" />
 <Variable x:TypeArguments="x:String" Name="WorkspaceName" />
 <Variable x:TypeArguments="mtvc1:Workspace" Name="Workspace" />
 <Variable x:TypeArguments="x:String" Name="WorkingDirectory" />
 <Variable x:TypeArguments="x:String" Name="ScriptFile" />
 <Variable x:TypeArguments="x:String" Name="FMakeFile" />
 </mtbwa:AgentScope.Variables>
 <mtbwa:GetBuildAgent DisplayName="Get the Agent" sap2010:WorkflowViewState.IdRef="GetBuildAgent_1" mtbwt:BuildTrackingParticipant.Importance="Low" Result="[BuildAgent]" />
 <mtbwa:GetBuildDirectory sap2010:WorkflowViewState.IdRef="GetBuildDirectory_1" Result="[BuildDirectory]" />
 <Assign x:TypeArguments="x:String" DisplayName="Initialize Workspace Name" sap2010:WorkflowViewState.IdRef="Assign`1_1" mtbwt:BuildTrackingParticipant.Importance="Low" To="[WorkspaceName]" Value="[String.Format(&quot;{0}_{1}_{2}&quot;, BuildDetail.BuildDefinition.Id, Microsoft.TeamFoundation.LinkingUtilities.DecodeUri(BuildAgent.Uri.AbsoluteUri).ToolSpecificId, BuildAgent.ServiceHost.Name)]" />
 <Assign x:TypeArguments="x:String" DisplayName="Initialize Working Directory" sap2010:WorkflowViewState.IdRef="Assign`1_2" mtbwt:BuildTrackingParticipant.Importance="Low" To="[WorkingDirectory]" Value="[String.Format(&quot;{0}\&quot;, BuildDirectory)]" />
 <Assign x:TypeArguments="x:String" DisplayName="Initialize Script File" sap2010:WorkflowViewState.IdRef="Assign`1_3" mtbwt:BuildTrackingParticipant.Importance="Low" To="[ScriptFile]" Value="[String.Format(&quot;{0}\{1}&quot;, WorkingDirectory, ScriptFileName)]" />
 <Assign x:TypeArguments="x:String" DisplayName="Initialize FMake file" sap2010:WorkflowViewState.IdRef="Assign`1_4" mtbwt:BuildTrackingParticipant.Importance="Low" To="[FMakeFile]" Value="[String.Format(&quot;{0}&quot;, System.IO.Path.Combine(FMakeDirectory, &quot;fake.exe&quot;))]" />
 <Sequence DisplayName="Delete Workspace and Sources Directory" sap2010:WorkflowViewState.IdRef="Sequence_1" mtbwt:BuildTrackingParticipant.Importance="Low">
 <mtbwa:DeleteWorkspace DeleteLocalItems="[True]" DisplayName="Delete Workspace" sap2010:WorkflowViewState.IdRef="DeleteWorkspace_1" mtbwt:BuildTrackingParticipant.Importance="Normal" Name="[WorkspaceName]" />
 <mtbwa:DeleteDirectory Directory="[WorkingDirectory]" DisplayName="Delete Scripts Directory" sap2010:WorkflowViewState.IdRef="DeleteDirectory_1" mtbwt:BuildTrackingParticipant.Importance="Normal" />
 </Sequence>
 <mtbwa:CreateWorkspace BuildDirectory="[BuildDirectory]" Comment="[&quot;Workspace Created by Team Build&quot;]" DisplayName="Create Workspace" sap2010:WorkflowViewState.IdRef="CreateWorkspace_1" Name="[WorkspaceName]" Result="[Workspace]" SourcesDirectory="[WorkingDirectory]" />
 <mtbwa:SyncWorkspace DisplayName="Get Workspace" sap2010:WorkflowViewState.IdRef="SyncWorkspace_1" NoCIOption="True" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" Workspace="[Workspace]" />
 <mtbwa:InvokeProcess Arguments="[BuildDetail.DropLocation]" DisplayName="Call FMake" FileName="[FMakeFile]" sap2010:WorkflowViewState.IdRef="InvokeProcess_1" WorkingDirectory="[BuildDirectory]">
 <mtbwa:InvokeProcess.ErrorDataReceived>
 <ActivityAction x:TypeArguments="x:String">
 <ActivityAction.Argument>
 <DelegateInArgument x:TypeArguments="x:String" Name="errOutput" />
 </ActivityAction.Argument>
 <mtbwa:WriteBuildError sap2010:WorkflowViewState.IdRef="WriteBuildError_1" Message="[errOutput]" />
 </ActivityAction>
 </mtbwa:InvokeProcess.ErrorDataReceived>
 <mtbwa:InvokeProcess.OutputDataReceived>
 <ActivityAction x:TypeArguments="x:String">
 <ActivityAction.Argument>
 <DelegateInArgument x:TypeArguments="x:String" Name="stdOutput" />
 </ActivityAction.Argument>
 <mtbwa:WriteBuildMessage sap2010:WorkflowViewState.IdRef="WriteBuildMessage_1" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" Message="[stdOutput]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" />
 </ActivityAction>
 </mtbwa:InvokeProcess.OutputDataReceived>
 </mtbwa:InvokeProcess>
 </mtbwa:AgentScope>
 <sads:DebugSymbol.Symbol>dzVDOlxVc2Vyc1xsdWthc2h2b1xEZXNrdG9wXEZNYWtlVGVtcGxhdGUoQzEyNzEwNikueGFtbDUB5gMBpgQBBQH3BAHdBQEEAb4BAc0DAQMBYgGhAQECQQNxDgIBAUYFRrcBAgFSRwVHoAECAU9IBW8YAgECRqUBRrQBAgFTRzBHRQIBUEixAUjOAQIBB0iCAUikAQIBBUjfAUiKAgIBA1EHUbIBAgFMUgdScgIBSVMHU5QDAgFCVAdUhgICAT1VB1WPAgIBN1YHVqUCAgEyVwdaEgIBKVsHW58CAgEfXAdc+gECARhdB24dAgEJUaEBUa8BAgFNUl1SbwIBSlPKAVORAwIBRVOyAVPDAQIBQ1TQAVSDAgIBQFS1AVTJAQIBPlXEAVWMAgIBOlWvAVW9AQIBOFbCAVaiAgIBNVauAVa7AQIBM1gJWNkBAgEuWQlZzwECASpbLVs/AgEnW0hbdwIBJlvpAVv2AQIBJFuIAlucAgIBIlvQAVvhAQIBIFx1XHsCARxc6gFc9wECARpdJl1CAgEVXWVdcgIBE122AV3IAQIBEWsNa6kCAgENYw1jcAIBClgxWDkCATFYxQFY1gECAS9ZKlk+AgEsa7UBa8IBAgEPa2drrAECAQ5jYGNtAgEL</sads:DebugSymbol.Symbol>
 </Sequence>
 <sap2010:WorkflowViewState.ViewStateManager>
 <sap2010:ViewStateManager>
 <sap2010:ViewStateData Id="GetBuildDetail_1" sap:VirtualizedContainerService.HintSize="256,22" />
 <sap2010:ViewStateData Id="UpdateBuildNumber_1" sap:VirtualizedContainerService.HintSize="256,22" />
 <sap2010:ViewStateData Id="GetBuildAgent_1" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="GetBuildDirectory_1" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="Assign`1_1" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="Assign`1_2" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="Assign`1_3" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="Assign`1_4" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="DeleteWorkspace_1" sap:VirtualizedContainerService.HintSize="200,22" />
 <sap2010:ViewStateData Id="DeleteDirectory_1" sap:VirtualizedContainerService.HintSize="200,22" />
 <sap2010:ViewStateData Id="Sequence_1" sap:VirtualizedContainerService.HintSize="234,208">
 <sap:WorkflowViewStateService.ViewState>
 <scg:Dictionary x:TypeArguments="x:String, x:Object">
 <x:Boolean x:Key="IsExpanded">True</x:Boolean>
 </scg:Dictionary>
 </sap:WorkflowViewStateService.ViewState>
 </sap2010:ViewStateData>
 <sap2010:ViewStateData Id="CreateWorkspace_1" sap:VirtualizedContainerService.HintSize="234,22" />
 <sap2010:ViewStateData Id="SyncWorkspace_1" sap:VirtualizedContainerService.HintSize="234,150" />
 <sap2010:ViewStateData Id="WriteBuildError_1" sap:VirtualizedContainerService.HintSize="200,22" />
 <sap2010:ViewStateData Id="WriteBuildMessage_1" sap:VirtualizedContainerService.HintSize="200,22" />
 <sap2010:ViewStateData Id="InvokeProcess_1" sap:VirtualizedContainerService.HintSize="234,278" />
 <sap2010:ViewStateData Id="AgentScope_1" sap:VirtualizedContainerService.HintSize="256,1274" />
 <sap2010:ViewStateData Id="Sequence_2" sap:VirtualizedContainerService.HintSize="278,1522">
 <sap:WorkflowViewStateService.ViewState>
 <scg:Dictionary x:TypeArguments="x:String, x:Object">
 <x:Boolean x:Key="IsExpanded">True</x:Boolean>
 </scg:Dictionary>
 </sap:WorkflowViewStateService.ViewState>
 </sap2010:ViewStateData>
 <sap2010:ViewStateData Id="FMake.Process_1" sap:VirtualizedContainerService.HintSize="318,1602" />
 </sap2010:ViewStateManager>
 </sap2010:WorkflowViewState.ViewStateManager>
</Activity>
This entry was posted in build, TFS and tagged , , . Bookmark the permalink.

1 Response to Run batch file from TFS build?

  1. Sarah Zey says:

    This was incredibly helpful!! Thank you very much!!

    Liked by 1 person

Leave a comment