How to create TFS 2015 and Deploy as Scheduled Job

Many times we may need to deploy some code which will run in the back ground as a continuous process or scheduled job. For this we often create Server Plugins. But the problem with Server Plugin is, it runs every time something or other happens. Let’s say you have created one Server Plugin which monitors Work Item change activities and then does something at the backend. So when you do bulk activities it will create a lot of performance issues to the server. You may want to avoid this approach.

Firstly, Create the Job

You need to create a job. This is typically a class library project

Below are the references you need,

<Reference Include="Microsoft.TeamFoundation.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.Client.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Application Tier\TFSJobAgent\Microsoft.TeamFoundation.Common.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.Framework.Server">

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.Framework.Server.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.WorkItemTracking.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Application Tier\TFSJobAgent\Microsoft.TeamFoundation.WorkItemTracking.Client.dll</HintPath>

</Reference>

<Reference Include="Microsoft.VisualStudio.Services.WebApi, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Application Tier\TFSJobAgent\Microsoft.VisualStudio.Services.WebApi.dll</HintPath>

</Reference>

 

Note: Ideally you should not mix both the DLL versions 12.0.0.0 and 14.0.0.0, you might find issue of the code not working. This will not give the runtime error but it will simply not work.

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.TeamFoundation.Framework.Server;

using Microsoft.TeamFoundation.Client;

using Microsoft.TeamFoundation.WorkItemTracking.Client;

using System.Diagnostics;

 

namespace DHP.ArtifactReview

{

public class CreateNewChild : ITeamFoundationJobExtension

{

Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem eventWorkItem = null;

public TeamFoundationJobExecutionResult Run(IVssRequestContext requestContext, TeamFoundationJobDefinition jobDefinition, DateTime queueTime, out string resultMessage)

{

try

{

Uri TfsUri = new Uri("https://localhost:8080/tfs/ContosoCollection/");

TfsTeamProjectCollection tfsServer = new TfsTeamProjectCollection(TfsUri);

 

 

WorkItemStore wiStore = tfsServer.GetService<WorkItemStore>();

List<WorkItem> changedWorkItems = new List<WorkItem>();

//Add the logic to perform…………………..

 

eventWorkItem.Save();

 

if (changedWorkItems.Count > 0)

{

wiStore.BatchSave(changedWorkItems.ToArray());

resultMessage = changedWorkItems.Count + " work item titles updated.";

}

else

{

resultMessage = "no work item titles to update.";

}

return TeamFoundationJobExecutionResult.Succeeded;

}

catch (Exception ex)

{

resultMessage = "Job Failed: " + ex.ToString();

EventLog.WriteEntry("TFS Service", resultMessage, EventLogEntryType.Error);

return TeamFoundationJobExecutionResult.Failed;

}

 

}}}

Deploy

Copy the DLL and pdb file to

C:\Program Files\Microsoft Team Foundation Server 14.0\Application Tier\TFSJobAgent\Plugins

You may want to remove pdb file if you are not debugging.

Note: You may notice here that the using blog does not need so may references we have mentioned just above. But if you don’t have them you will get an error while running.

Create an App to Register the Job

Then you need to write a small console application to deploy it to the server. Earlier we could use TFS Client Object Model but now it is stopped due to security reason. So only option is to use Server Object Model. Let’s see how it works. If you try with the Client Object Model you might get the below error.

TF400444: The creation and deletion of jobs is no longer supported. You may only update the EnabledState or Schedule of a job.  Failed to create, delete or update job id 21b9237f-8c12-41de-86ae-04c407a1438c.

Important references to add

<Reference Include="Microsoft.TeamFoundation.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.Common.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.DistributedTask.Server">

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.DistributedTask.Server.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.Framework.Server">

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Application Tier\TFSJobAgent\Microsoft.TeamFoundation.Framework.Server.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.Lab.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.Lab.Common.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.Server.Core">

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.Server.Core.dll</HintPath>

</Reference>

<Reference Include="Microsoft.TeamFoundation.WorkItemTracking.Common, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">

<SpecificVersion>False</SpecificVersion>

<HintPath>C:\Program Files\Microsoft Team Foundation Server 14.0\Tools\Microsoft.TeamFoundation.WorkItemTracking.Common.dll</HintPath>

</Reference>

 

Notice all the references are not used below, but if you don’t add them you will get runtime error. May be due to dependency.

using Microsoft.TeamFoundation.Framework.Common;

using Microsoft.TeamFoundation.Framework.Server;

using System;

using System.Configuration;

 

namespace CreateTFSScheduleJob

{

class Program

{

/*Sample Command

Case: To Register a Job

><Path>/CreateTFSScheduleJob.exe /i"

 

Case: To DeRegister a Job which is already configured

>Path/CreateTFSScheduleJob.exe /u "0A887841-7FFD-423A-9ED8-D69BAD9949A0"

 

Tips: How to find the Guid See the Troubleshooting() method

*/

//static string JobId = null;

public static void Main(string[] args)

{

#region Collect commands from the args

if (args.Length != 1 && args.Length != 2)

{

Console.WriteLine("Usage: CreateTFSScheduleJob.exe <command " +

"(/i, /q, /u)> [job id]");

Console.WriteLine("/i -- > installation");

Console.WriteLine("/u GUID -- > uninstall");

Console.WriteLine("/q GUID -- > query job");

Console.WriteLine();

Console.WriteLine("SQL Query to run in TFS_Config Database");

Console.WriteLine("select * from Tfs_Configuration.dbo.tbl_JobDefinition WITH(NOLOCK) where JobName = 'Artifact Review WorkItem'");

Console.WriteLine();

 

return; //TODO: Revove in final, only for debug now

}

string command = args[0]; // "/i"; //TODO: Revove in final, only for debug now

Guid jobid = Guid.Empty;

if (args.Length > 1)

{

if (!Guid.TryParse(args[1], out jobid))

{

Console.WriteLine("Job Id not a valid Guid");

return;

}

}

#endregion

 

try

{

#region Build a DeploymentServiceHost

string databaseServerDnsName = "TFS Server IP"; //Read from App.config

string connectionString = $"Data Source={databaseServerDnsName};" +

"Initial Catalog=TFS_Configuration;Integrated Security=true;";

TeamFoundationServiceHostProperties deploymentHostProperties =

new TeamFoundationServiceHostProperties();

deploymentHostProperties.HostType =

TeamFoundationHostType.Deployment |

TeamFoundationHostType.Application;

deploymentHostProperties.Id = Guid.Empty;

deploymentHostProperties.PhysicalDirectory =

@"C:\Program Files\Microsoft Team Foundation Server 14.0\" +

@"Application Tier\TFSJobAgent"; //Read from App.config

deploymentHostProperties.PlugInDirectory =

$@"{deploymentHostProperties.PhysicalDirectory}\Plugins";

deploymentHostProperties.VirtualDirectory = "/";

ISqlConnectionInfo connInfo =

SqlConnectionInfoFactory.Create(connectionString,

null, null);

IVssDeploymentServiceHost host =

DeploymentServiceHostFactory.CreateDeploymentServiceHost(deploymentHostProperties, connInfo);

#endregion

using (IVssRequestContext requestContext = host.CreateSystemContext())

{

TeamFoundationJobService jobService = requestContext.GetService<TeamFoundationJobService>();

var jobDefinition =

new TeamFoundationJobDefinition(

"Wriju’s Schedule Job", //Read from App.config

"Namespace.ClassName"); //Read from App.config

jobDefinition.EnabledState = TeamFoundationJobEnabledState.Enabled;

jobDefinition.Schedule.Add(new TeamFoundationJobSchedule

{

ScheduledTime = DateTime.Now,

PriorityLevel = JobPriorityLevel.Normal,

Interval = 15, //Interval in 15 sec - cannot be less than 15 sec

});

 

 

if (command == "/i")

{

jobService.UpdateJobDefinitions(requestContext, null,

new[] { jobDefinition });

}

else if (command == "/q")

{

jobService.QueryJobDefinition(requestContext, jobid);

}

else if (command == "/u")

{

jobService.UpdateJobDefinitions(requestContext,

new[] { jobid }, null);

}

}

}

catch (Exception ex)

{

Console.WriteLine(ex.ToString());

}

Console.WriteLine("Job Added/Modifed Succesfully");

Console.ReadKey();

}}}

Deploy and Run the App to register

Take the .exe and the .pdb file and put it in Tools folder then run from command prompt in an elevated mode. The reason why you may want to keep it tools because it already has those DLLs you have referred.

C:\Program Files\Microsoft Team Foundation Server 14.0\Tools

Troubleshooting

After you deploy this Job you need to restart the Windows Service “Visual Studio Team Foundation Background Job Agent”, alternatively

net stop TfsJobAgent

net start TfsJobAgent

If you want to check if the job is working and its ID

select * from Tfs_Configuration.dbo.tbl_JobDefinition WITH(NOLOCK) where JobName = '<name of the job>'

 

You can also check the history,

select * from Tfs_Configuration.dbo.tbl_JobHistory WITH (NOLOCK) where JobId = '0A887841-7FFD-423A-9ED8-D69BAD9949A0'

 

Wriju Ghosh,

Senior Consultant - Developer, Microsoft Consulting Services, Microsoft