Creating “Fake” Tfs Builds

Sometimes you can’t always use the tools you want to use. This is especially true when there is already a bunch of stuff setup around existing tools. Lets say for example that you have an existing application doing your builds, for everything you need around TFS you can do it from within the Web Access although this is all fine and works well, when you want to see how the builds are running you need to go out to another tool to see this information.

Today I’m going to help you setup your builds external to TFS to send build information to TFS so that you can see this info in TFS, the purpose of this is just to give you visibility from TFS and is a very basic introduction that can be extended on a lot with bigger builds.

Before we dig into any code, you will need to create a Build Definition that we will use for our fake builds, if you don’t currently have any controllers setup for you specific collection you will need to create one, after you create the build definition you will be able to de register this build controller as it’s not needed for the manually builds which we will be using.

What we had initially was a build.bat file that has the basics in it (build the build.proj file and log to different files for multiple levels of logging and dumps the latest build in a folder) that our current build server would run that looked like below

   1: @echo off

   2: call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VsDevCmd.bat"

   3: msbuild build.proj /t:DoBuild /v:d  /fl1 /fl2 /fl3 /fl4 /flp1:logfile=build.log /flp2:logfile=build.errors.log;errorsonly /flp3:logfile=build.warnings.log;warningsonly /flp4:logfile=build.details.log;detailsonly /p:OutputPath=\\GORDON-PC\Demos\drops\TfsFakeBuilds-Latest

and a build.proj file that basically just let use build multiple projects that could be part of multiple solutions (for the purpose of this post we only have 1)

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <Project DefaultTargets="DoBuild" xmlns="">

   3:   <ItemGroup>

   4:     <BuildProject Include="TfsFakeBuilds.sln"/>

   5:   </ItemGroup>


   7:   <Target Name="DoBuild">

   8:     <MSBuild Projects="@(BuildProject)">

   9:     </MSBuild>

  10:   </Target>

  11: </Project>

The next steps was to create 2 msbuild tasks that we could use for the starting and stopping of our TFS Build. Create a new class library project and add a reference to the following assemblies


    Add 2 classes that each inherit from Microsoft.Build.Utilities.Task, you can call theses classes TfsFakeBuildStart and TfsFakeBuildFinish. All these 2 tasks are going to do is call a call into a 3rd class that is going to do all the work.


       1: namespace TfsBuildTask

       2: {

       3:     using System;

       4:     using Microsoft.Build.Framework;

       5:     using Microsoft.Build.Utilities;

       6:     public class TfsFakeBuildStart : Task

       7:     {

       8:         [Required]

       9:         public string Architecture { get; set; }

      10:         [Required]

      11:         public string CollectionUri { get; set; }

      12:         [Required]

      13:         public string Configuration { get; set; }

      14:         [Required]

      15:         public string DefinitionName { get; set; }

      16:         [Required]

      17:         public string DetailedLogPath { get; set; }

      18:         [Required]

      19:         public string DropLocation { get; set; }

      20:         [Required]

      21:         public string ErrorLogPath { get; set; }

      22:         [Required]

      23:         public string PathFromBuildRoot { get; set; }

      24:         [Required]

      25:         public string RegularLogPath { get; set; }

      26:         [Required]

      27:         public string ServerPath { get; set; }

      28:         [Required]

      29:         public string TargetNames { get; set; }

      30:         [Required]

      31:         public string TeamProjectName { get; set; }

      32:         [Required]

      33:         public string WarningLogPath { get; set; }

      34:         public override bool Execute()

      35:         {

      36:             try

      37:             {

      38:                 base.Log.LogMessage("Tfs Fake Build Starting on '" + this.DefinitionName + "'.");

      39:                 FakeBuildObject.Instance.Start(this.DetailedLogPath, this.ErrorLogPath, this.WarningLogPath, this.DropLocation, this.RegularLogPath, this.CollectionUri, this.TeamProjectName, this.DefinitionName, this.Configuration, this.PathFromBuildRoot, this.Architecture, this.ServerPath, this.TargetNames);

      40:                 base.Log.LogMessage("Tfs Fake Build Started on '" + this.DefinitionName + "'.");

      41:                 return true;

      42:             }

      43:             catch (Exception ex)

      44:             {

      45:                 base.Log.LogError("Failed to start Fake Build: " + ex);

      46:                 return false;

      47:             }

      48:         }

      49:         public IBuildEngine BuildEngine { get; set; }

      50:         public ITaskHost HostObject { get; set; }

      51:     }

      52: }


       1: namespace TfsBuildTask

       2: {

       3:     using System;

       4:     using Microsoft.Build.Framework;

       5:     using Microsoft.Build.Utilities;

       6:     public class TfsFakeBuildFinish : Task

       7:     {

       8:         public override bool Execute()

       9:         {

      10:             try

      11:             {

      12:                 base.Log.LogMessage("Tfs Fake Build Stopping.");

      13:                 FakeBuildObject.Instance.Stop();

      14:                 base.Log.LogMessage("Tfs Fake Build Stopped.");

      15:                 return true;

      16:             }

      17:             catch(Exception ex)

      18:             {

      19:                 base.Log.LogError("Failed to stop Fake Build: " + ex); 

      20:                 return false;

      21:             }

      22:         }

      23:         public IBuildEngine BuildEngine { get; set; }

      24:         public ITaskHost HostObject { get; set; }

      25:     }

      26: }

    In our 3rd class we will be getting a reference to a build definition and then kicking off a manual build in our start method and then in our stop method we will collection all the information to from the build and finish off the TFS manual build. Create a 3rd class like below


       1: namespace TfsBuildTask

       2: {

       3:     using System;

       4:     using System.IO;

       5:     using System.Linq;

       6:     using Microsoft.TeamFoundation.Build.Client;

       7:     using Microsoft.TeamFoundation.Client;

       8:     public class FakeBuildObject

       9:     {

      10:         private static FakeBuildObject instance;

      11:         private string architecture;

      12:         private string collectionUri;

      13:         private string configuration;

      14:         private string definitionName;

      15:         private string detailedLogPath;

      16:         private string dropLocation;

      17:         private string errorLogPath;

      18:         private string pathFromBuildRoot;

      19:         private string regularLogPath;

      20:         private string serverPath;

      21:         private string targetNames;

      22:         private string teamProjectName;

      23:         private string warningLogPath;

      24:         private IBuildDefinition buildDefinition;

      25:         private IBuildProjectNode buildProjectNode;

      26:         private IBuildServer buildServer;

      27:         private TfsTeamProjectCollection collection;

      28:         private IBuildDetail detail;

      29:         private DateTime startTime;

      30:         private DateTime endTime;

      31:         public static FakeBuildObject Instance

      32:         {

      33:             get

      34:             {

      35:                 if (instance == null)

      36:                 {

      37:                     instance = new FakeBuildObject();

      38:                 }

      39:                 return instance;

      40:             }

      41:         }

      42:         public void Start(string detailedLogPath, string errorLogPath, string warningLogPath, string dropLocation, string regularLogPath, string collectionUri, string teamProjectName, string definitionName, string configuration, string pathFromBuildRoot, string architecture, string serverPath, string targetNames)

      43:         {

      44:             this.detailedLogPath = detailedLogPath;

      45:             this.errorLogPath = errorLogPath;

      46:             this.warningLogPath = warningLogPath;

      47:             this.dropLocation = dropLocation;

      48:             this.regularLogPath = regularLogPath;

      49:             this.collectionUri = collectionUri;

      50:             this.teamProjectName = teamProjectName;

      51:             this.definitionName = definitionName;

      52:             this.configuration = configuration;

      53:             this.pathFromBuildRoot = pathFromBuildRoot;

      54:             this.architecture = architecture;

      55:             this.serverPath = serverPath;

      56:             this.targetNames = targetNames;

      57:             this.startTime = DateTime.Now;

      58:             this.collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(this.collectionUri));

      59:             this.buildServer = this.collection.GetService<IBuildServer>();

      60:             this.buildDefinition = this.buildServer.GetBuildDefinition(this.teamProjectName, this.definitionName);

      61:             this.detail = this.buildDefinition.CreateManualBuild(this.definitionName + " - " + DateTime.Now.ToString("yyyyMMddHHmmss"), this.dropLocation);

      62:             this.buildProjectNode = this.detail.Information.AddBuildProjectNode(this.configuration, this.pathFromBuildRoot, this.architecture, this.serverPath, this.startTime, this.targetNames);

      63:         }

      64:         public void Stop()

      65:         {

      66:             this.endTime = DateTime.Now;

      67:             string[] regularLinesFromLog = ConvertToLines(this.ReadFile(this.regularLogPath));

      68:             string[] errorLinesFromLog = ConvertToLines(this.ReadFile(this.errorLogPath));

      69:             string[] warningLinesFromLog = ConvertToLines(this.ReadFile(this.warningLogPath));

      70:             this.buildProjectNode.CompilationErrors = 0;

      71:             this.buildProjectNode.CompilationWarnings = 0;

      72:             DateTime nextMessageLogTime = this.startTime;

      73:             int timeBetweenMessages = Convert.ToInt32((this.endTime - this.startTime).TotalMilliseconds / regularLinesFromLog.Length);

      74:             foreach (string line in regularLinesFromLog)

      75:             {

      76:                 if (!string.IsNullOrEmpty(line))

      77:                 {

      78:                     if (errorLinesFromLog.Contains(line))

      79:                     {

      80:                         this.buildProjectNode.Node.Children.AddBuildError(line, nextMessageLogTime);

      81:                         this.buildProjectNode.CompilationErrors++;

      82:                     }

      83:                     else if (warningLinesFromLog.Contains(line))

      84:                     {

      85:                         this.buildProjectNode.Node.Children.AddBuildWarning(line, nextMessageLogTime);

      86:                         this.buildProjectNode.CompilationWarnings++;

      87:                     }

      88:                     else

      89:                     {

      90:                         this.buildProjectNode.Node.Children.AddBuildMessage(line, BuildMessageImportance.Normal, nextMessageLogTime);

      91:                     }

      92:                 }

      93:                 nextMessageLogTime.AddMilliseconds(timeBetweenMessages);

      94:             }

      95:             this.buildProjectNode.Node.Children.AddExternalLink("Detailed Log File", new Uri(this.detailedLogPath));

      96:             this.buildProjectNode.Node.Children.AddExternalLink("Error Log File", new Uri(this.errorLogPath));

      97:             this.buildProjectNode.Node.Children.AddExternalLink("Warning Log File", new Uri(this.warningLogPath));

      98:             this.buildProjectNode.Save();

      99:             this.detail.Information.Save();

     100:             this.detail.FinalizeStatus(errorLinesFromLog.Length == 1 && string.IsNullOrEmpty(errorLinesFromLog[0]) ? BuildStatus.Succeeded : BuildStatus.Failed);

     101:         }

     102:         private static string[] ConvertToLines(string sr)

     103:         {

     104:             return sr.Replace("\r",string.Empty).Split('\n');

     105:         }

     106:         private string ReadFile(string logPath)

     107:         {

     108:             using (FileStream fs = new FileStream(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))

     109:             {

     110:                 using (StreamReader sr = new StreamReader(fs))

     111:                 {

     112:                     return sr.ReadToEnd();

     113:                 }

     114:             }

     115:         }

     116:     }

     117: }

    The last parts is just to include this task in your build and then you will have the info in TFS to display on your dashboard and view without having to bounce out to another tool.

    For this we created a folder called tfs_config along side our build.proj file and then placed the dll of our fake build tasks in that folder as well as 2 files as below that will be used to kick off and complete the TFS Manual Build

       1: <?xml version="1.0" encoding="utf-8"?>

       2: <Project DefaultTargets="Default" xmlns="">

       3:   <UsingTask AssemblyFile="TfsBuildTask.dll" TaskName="TfsFakeBuildStart" />

       4:   <UsingTask AssemblyFile="TfsBuildTask.dll" TaskName="TfsFakeBuildFinish" />  

       5: </Project>

       1: <?xml version="1.0" encoding="utf-8"?>

       2: <Project DefaultTargets="Default" xmlns="">

       3:   <Import Project="" />

       4:   <PropertyGroup>

       5:     <BuildRoot>\\GORDON-PC\Demos\TfsFakeBuilds\</BuildRoot>

       6:     <DropLocation>\\GORDON-PC\Demos\TfsFakeBuilds\</DropLocation>

       7:   </PropertyGroup>  

       8:   <Target Name="StartTfsBuild">

       9:     <TfsFakeBuildStart Architecture="x86" CollectionUri="http://TfsServerUri:8080/tfs" Configuration="Debug" DefinitionName="Demo Definition" DetailedLogPath="$(BuildRoot)build.detailed.log" DropLocation="$(DropLocation)" ErrorLogPath="$(BuildRoot)build.errors.log" PathFromBuildRoot="$(BuildRoot)build.bat" RegularLogPath="$(BuildRoot)build.log" ServerPath="$/TfsFakeBuilds/build.bat" TargetNames="Defaults" TeamProjectName="Demos" WarningLogPath="$(BuildRoot)build.warnings.log">      

      10:     </TfsFakeBuildStart>

      11:   </Target>

      12:   <Target Name="EndTfsBuild" DependsOnTargets="StartTfsBuild;DoBuild">

      13:     <TfsFakeBuildFinish />

      14:   </Target>

      15: </Project>

    next we opened the build.proj file and added the line below above the target DoBuild

       1: <import Project="tfs_config\" />

    and for the last piece we duplicated the build.bat file so that full builds on dev machines would not log to TFS, all that we changed in that build.bat file was the target that we use with the msdbuild command to EndTfsBuild.

    Any finally we could stay in TFS for build info once again and we got all the info you see below Smile. Hope this helps somebody else as well.


    Download Files

    Tfs Js Extension using TypeScript

    Before I start I just want to give credit to Tiago Pascal for his help in getting me started with some of the basics and tips and tricks that got me start with the JavaScript extensions and to Alexander Vanwynsberghe for a debugging tip that makes this all a lot easier.

    This post will give you a basic starting point for creating TFS Web Access JavaScript extension with most of it in proper TypeScript. This post will give a brief overview of the whole process and supply enough to get you started. In later posts I will go into the different sections in more detail and explain what bits of code do or how I found I was able to know that bits of code could work in the web access.

    Files Required

    First you need a name for your extension, I have choose to use B1n4ryD1g1t.Tfs.Extensions so when ever you see that you will replace it with your name but I will try remember to point out the couple of places required.

    The easiest way I find to create these plugins is to create a new HTML Application with TypeScript, this provides a good starting point and also makes it more familiar to me as it’s a solution and will allow me to build the project and any TypeScript errors will be revealed to me through the normal error list window.


    Create 2 files (and remember to do the name replacing here Smile), manifest.xml and B1n4ryD1g1t.Tfs.Extensions.min.js and then rename the solutions current app.ts to be B1n4ryD1g1t.Tfs.Extensions.debug.ts. If you build the solution and also show all files in the solution folder you will notice that there is now a B1n4ryD1g1t.Tfs.Extensions.debug.js and file, you can include those in your solution. The last step of setting up the solution is to remove the app.css, default.htm and web.config as we won’t be needing these.

    Next you need to use NuGet to add a reference to JQuery to your project


    Now right click on your jquery-{version}.js file and click on Search for TypeScript Typings… and install the jquery.TypeScript.DefinitelyTyped typing.


    Your solution should end up looking something like below, we aren’t going to physically reference these jquery files in our extension they were purely used to get the typings.


    What goes into the the manifest.xml

    The mainifest.xml has the basic information for your extension that TFS will show on the extensions page in the web access.


    The plugin node is self explanatory and for now you will leave the module nodes intact (remember to replace the name though). The 2 modules below allow our extension to run on the task and portfolio boards.

       1: <WebAccess version="12.0">

       2:   <plugin name="B1n4ryD1g1t Tfs Extensions - Web Access" vendor="Gordon Beeming" moreinfo="" version="1.7">

       3:     <modules>

       4:       <module namespace="B1n4ryD1g1t.Tfs.Extensions" loadAfter="TFS.Agile.TaskBoard.View"/>

       5:       <module namespace="B1n4ryD1g1t.Tfs.Extensions" loadAfter="TFS.Agile.Boards.Controls"/>

       6:     </modules>

       7:   </plugin>

       8: </WebAccess>

    What is the minimum needed for the B1n4ryD1g1t.Tfs.Extensions.debug.ts?

    The minimum content for our extension will be plain Js and will leave 4 errors in our solution that we can ignore


    The content for the extension will look like below

       1: /// <reference path="Scripts/typings/jquery/jquery.d.ts" />

       2: var __extends = this.__extends || function (d, b) {

       3:     function __() { this.constructor = d; }

       4:     __.prototype = b.prototype;

       5:     d.prototype = new __();

       6: };

       7: define(["require", "exports", "Presentation/Scripts/TFS/TFS", "Presentation/Scripts/TFS/TFS.Core", "Presentation/Scripts/TFS/TFS.OM", "Presentation/Scripts/TFS/TFS.UI.Controls", "WorkItemTracking/Scripts/TFS.WorkItemTracking"],

       8: function (require1, exports, tfs, core, tfsOM, tfsUiControls, tfsWorkItemTracking) {

       9:     var TFS = tfs;

      10:     var Core = core;

      11:     var TFS_OM = tfsOM;

      12:     var TFS_UI_Controls = tfsUiControls;

      13:     var TFS_WorkItemTracking = tfsWorkItemTracking;

      14:     var B1n4ryD1g1tTfsExtension = (function (_super) {

      15:         __extends(B1n4ryD1g1tTfsExtension, _super);

      16:         function B1n4ryD1g1tTfsExtension(options) {

      17:   , options);

      18:         }

      19:         B1n4ryD1g1tTfsExtension.prototype.initializeOptions = function (options) {

      20:   , $.extend({

      21:             }, options));

      22:         };

      23:         B1n4ryD1g1tTfsExtension.prototype.initialize = function () {

      24:             alert('this is running');

      25:         };

      26:         B1n4ryD1g1tTfsExtension._typeName = "B1n4ryD1g1t.Tfs.Extensions";

      27:         return B1n4ryD1g1tTfsExtension;

      28:     })(TFS_UI_Controls.BaseControl);

      29:     TFS.initClassPrototype(B1n4ryD1g1tTfsExtension, {});

      30:     TFS_UI_Controls.Enhancement.registerEnhancement(B1n4ryD1g1tTfsExtension, ".taskboard");

      31:     TFS_UI_Controls.Enhancement.registerEnhancement(B1n4ryD1g1tTfsExtension, ".agile-board");

      32: });

    Add the extension to TFS Web Access

    Open your debug.js file and copy all of its contents to the min.js file and minify it, the web essentials extension for Visual Studio will help with this as it’s as easy as ctrl + alt + x. Open the folder where your extension is and zip the debug.js, min.js and mainifest.xml files.


    Now you will need to upload your extension into TFS. Browse to the TFS Server Home, click on the admin settings button and then on extensions tab.Click Install, browse for your newly created zip file and click ok. Now all you need to do it click enable and ok to the warning. Now just refresh any of the task boards or portfolio boards and you will see an alert from inside the extension.


    Making changes to the extension

    As you can see this is a very lengthy process to add the extension and none of that changes for checking for changes unless you use a trick that Alexander blogged about Debugging TFS Web Access Extensions. This will basically using fiddler tell your machine that when it requests the js file for your extension it must use the version js file that is in your solution instead of the one from TFS, this will speed up development a lot as you can just save the file which will trigger Visual Studio to update the debug.js file which refreshing TFS will now be loaded.

    For the Editor I used the values

    regex:http://labtfs01/_static/tfs/12/_scripts/TFS/.+//_plugins/.+/B1n4ryD1g1t.Tfs.Extensions.js with header:CachControl=no-cache and regex:http://labtfs01/_static/tfs/12/_scripts/TFS/.+//_plugins/.+/B1n4ryD1g1t.Tfs.Extensions.js with C:\c\r\Apps\TfsJsExtensions\TfsJsExtensions\B1n4ryD1g1t.Tfs.Extensions.debug.js. Similar to the blog post just adding a wild card for the debug/min difference in TFS.

    Adding some Real TypeScript

    To the bottom of you .ts file add the TypeScript code below (sorry about no full TS highlighting).

       1: module B1n4ryD1g1tModule {

       2:     export class Core {

       3:         Require: any;

       4:         Exports: any;

       5:         TFS: any;

       6:         Core: any;

       7:         TFS_OM: any;

       8:         TFS_WorkItemTracking: any;

       9:         WorkItemManager: any;

      10:         CurrentlyFetchingWorkItems: boolean;

      11:         constructor(require1, exports, tfs, core, tfsom, tfsWorkItemTracking) {

      12:             this.Require = require1;

      13:             this.Exports = exports;

      14:             this.TFS = tfs;

      15:             this.Core = core;

      16:             this.TFS_OM = tfsom;

      17:             this.TFS_WorkItemTracking = tfsWorkItemTracking;

      18:         }

      19:         public init(): any {

      20:             this.initWorkItemManagerEvents();

      21:             var that = this;

      22:             window.setTimeout(function () {

      23:                 if (that.isAgileBoard()) {

      24:                     that.setAgileBoardIDs();

      25:                 }

      26:                 if (that.isTaskBoard()) {

      27:                     that.setTaskBoardIDs();

      28:                 }

      29:             }, 100);

      30:         }

      31:         private getCurrentTeamName(): string {

      32:             return this.TFS.Host.TfsContext.getDefault();

      33:         }

      34:         private isTaskBoard(): boolean {

      35:             return $(".taskboard").length > 0;

      36:         }

      37:         private isAgileBoard(): boolean {

      38:             return $(".agile-board").length > 0;

      39:         }

      40:         private setAgileBoardIDs(): void {

      41:             var idsToFetch = [];

      42:             $(".board-tile").each(function () {

      43:                 var id = $(this).attr("data-item-id");

      44:                 idsToFetch.push(parseInt(id));

      45:             });

      46:             this.loadWorkItems(idsToFetch, this.setAgileBoardIDsWork);

      47:         }

      48:         private setAgileBoardIDsWork(index, row, that: Core): void {

      49:             that.workWithAgileBoard(row[0], that);

      50:         }

      51:         private workWithAgileBoard(id, that: Core): void {

      52:             if (that.workWithAgileBoard_WorkItem(id)) {

      53:             }

      54:         }

      55:         private setTaskBoardIDs(): void {

      56:             var idsToFetch = [];

      57:             $("#taskboard-table .tbTile").each(function () {

      58:                 var id = $(this).attr("id");

      59:                 id = id.split('-')[1];

      60:                 idsToFetch.push(parseInt(id));

      61:             });

      62:             $("#taskboard-table .taskboard-row .taskboard-parent").each(function () {

      63:                 var id = $(this).attr("id");

      64:                 if (id != undefined) {

      65:                     id = id.split('_')[1];

      66:                     id = id.substring(1);

      67:                     idsToFetch.push(parseInt(id));

      68:                 }

      69:             });

      70:             this.loadWorkItems(idsToFetch, this.setTaskBoardIDsWork);

      71:         }

      72:         private setTaskBoardIDsWork(index, row, that: Core): void {

      73:             if (that.workWithTaskBoard(row[0], row[1], that)) {

      74:             }

      75:         }

      76:         private workWithTaskBoard(id, state, that: Core): void {

      77:             if (that.workWithTaskBoard_Task(id)) {

      78:             }

      79:             if (that.workWithTaskBoard_Requirement(id)) {

      80:                 that.taskboard_setRequirementState(id, state);

      81:             }

      82:         }

      83:         private workWithTaskBoard_Requirement(id): boolean {

      84:             return this.boards_setID("taskboard-table_p" + id, id);

      85:         }

      86:         private workWithTaskBoard_Task(id): boolean {

      87:             return this.boards_setID("tile-" + id, id);

      88:         }

      89:         private workWithAgileBoard_WorkItem(id): boolean {

      90:             var titleObj = $(".board-tile[data-item-id='" + id + "'] .title");

      91:             if ($(titleObj).length > 0 && $(titleObj).find(".TitleAdded").length == 0) {

      92:                 var idHtml = "<span class='TitleAdded' style='font-weight:bold;'>" + id + "</span> - ";

      93:                 $(titleObj).html(idHtml + $(titleObj).html());

      94:                 return true;

      95:             }

      96:             return false;

      97:         }

      98:         private boards_setID(idTagLookFor, id): boolean {

      99:             var titleObj = $("#" + idTagLookFor + " .witTitle");

     100:             if ($(titleObj).length > 0 && $(titleObj).find(".TitleAdded").length == 0) {

     101:                 var idHtml = "<span class='TitleAdded' style='font-weight:bold;'>" + id + "</span> - ";

     102:                 $(titleObj).html(idHtml + $(titleObj).html());

     103:                 return true;

     104:             }

     105:             return false;

     106:         }

     107:         private taskboard_setRequirementState(id, state): boolean {

     108:             var titleObj = $("#taskboard-table_p" + id + " .witTitle");

     109:             if ($(titleObj).length > 0 && $(titleObj).find(".StateAdded").length == 0) {

     110:                 var stateHtml = "<br/><br/><span class='StateAdded' style='color:#505050;font-size:smaller;font-weight:bold;'>" + state + "</span>";

     111:                 $(titleObj).html($(titleObj).html() + stateHtml);

     112:                 return true;

     113:             }

     114:             return false;

     115:         }

     116:         private workItemChanged(sender, workItemChangedArgs): void {

     117:             if (workItemChangedArgs.change === this.TFS_WorkItemTracking.WorkItemChangeType.Reset || workItemChangedArgs.change === this.TFS_WorkItemTracking.WorkItemChangeType.SaveCompleted) {

     118:                 var that = this;

     119:                 var id =;

     120:                 var state = workItemChangedArgs.workItem.getFieldValue("System.State");

     121:                 if (that.isTaskBoard()) {

     122:                     window.setTimeout(function () {

     123:                         that.workWithTaskBoard(id, state, that);

     124:                     }, 100);

     125:                 } else if (that.isAgileBoard()) {

     126:                     window.setTimeout(function () {

     127:                         that.workWithAgileBoard(id, that);

     128:                     }, 100);

     129:                 }

     130:             }

     131:         }

     132:         private loadWorkItems(idsToFetch: Array, onComplete): void {

     133:             var that = this;

     134:             that.loadWorkItemsWork(idsToFetch, onComplete, that);

     135:         }

     136:         private loadWorkItemsWork(idsToFetch: Array, onComplete, that: Core): void {

     137:             var takeAmount = 100;

     138:             if (takeAmount >= idsToFetch.length) {

     139:                 takeAmount = idsToFetch.length;

     140:             }

     141:             if (takeAmount > 0) {

     142:       , takeAmount), [

     143:                     "System.Id",

     144:                     "System.State"

     145:                 ], function (payload) {

     146:                         that.loadWorkItemsWork(idsToFetch, onComplete, that);

     147:                         $.each(payload.rows, function (index, row) {

     148:                             onComplete(index, row, that);

     149:                         });

     150:                     }, function (err) {

     151:                         that.loadWorkItemsWork(idsToFetch, onComplete, that);

     152:                         alert(err);

     153:                     });

     154:             }

     155:         }

     156:         private initWorkItemManagerEvents(): void {

     157:             var service = this.TFS_OM.TfsTeamProjectCollection.getDefaultConnection().getService(this.TFS_WorkItemTracking.WorkItemStore);

     158:             this.WorkItemManager = service.workItemManager;

     159:             var that = this;

     160:             this.WorkItemManager.attachWorkItemChanged(function (sender, workItemChangedArgs) {

     161:                 that.workItemChanged(sender, workItemChangedArgs);

     162:             });

     163:         }

     164:     }

     165: }

    This is all the code we will need to do the magic, all that is left is to wire it up to the extension. This can be done by replacing the method below in the original snippet for the extension.

       1: B1n4ryD1g1tTfsExtension.prototype.initialize = function () {

       2:     var bdCore = new B1n4ryD1g1tModule.Core(require1, exports, TFS, Core, TFS_OM, TFS_WorkItemTracking);

       3:     bdCore.init();

       4: };

    If you fresh your page now and you have the debug tip running you will see that you have IDs on your work items and on the task board the requirement states are showing.





    Hope this sparks something in others as it has in me, As said in the intro I will be extending on what is covered in this article in later posts to provide more details.


    Solution – TfsJsExtensions_2013-09-27

    Usable Web Extension

    Running as Administrator with Click Once Application on Windows 8

    You have probably noticed that with windows 8 when you disable UAC it doesn’t fully turn off. You are able to completely turn it off but that then disables the ability to use any of the Modern UI applications. So today I dug a bit and eventually managed to get a solution to make your application run as admin when being run from a click once deploy.

    The way a user would be able to enable admin mode for a regular application is to Right click on the application and click ‘Run as administrator’


    With click once applications you aren’t able to do this. I suppose you could find one of the many ways to locate the actual executable and then run as administrator but then every time the application updates you will need to locate the application again and also many users (including myself) won’t see this as a suitable way to launch an application as administrator.

    After trying all the regular methods of enabling an application to run as full administrator I eventually got to the solution below.

    Add these usings into the program.cs file

       1: using System.ComponentModel;

       2: using System.Linq;

    Add the member and method to the program.cs class

       1: private const uint BCM_SETSHIELD = 0x160C;


       3: [DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]

       4: private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);

    Add the parameter string[] args to the main method if it does not already exist

       1: public static void Main(string[] args)

    And finally wrap all the code inside you main method with

       1: if (string.IsNullOrEmpty((from o in args where o == "--engage" select o).FirstOrDefault()))

       2: {

       3:     var btnElevate = new Button();

       4:     btnElevate.FlatStyle = FlatStyle.System;


       6:     SendMessage(btnElevate.Handle, BCM_SETSHIELD, 0, (IntPtr) 1);


       8:     var processInfo = new ProcessStartInfo();

       9:     processInfo.Verb = "runas";

      10:     processInfo.FileName = Application.ExecutablePath;

      11:     processInfo.Arguments = string.Join(" ", args.Concat(new[] { "--engage" }).ToArray());

      12:     try

      13:     {

      14:         Process p = Process.Start(processInfo);

      15:         p.WaitForExit();

      16:     }

      17:     catch (Win32Exception)

      18:     {

      19:         //Do nothing. Probably the user cancelled the UAC window or provided invalid credentials.

      20:     }


      22:     Application.Exit();

      23: }

      24: else

      25: {

      26:     // place code that was in the main method here

      27: }

    That’s all you need to be able to run your application as administrator on launch. Basically what is going to happen is your application will start up and see that there is no command argument for –engage, it will then get it’s own executable path and attempt to run itself again using administrator mode. If a user has UAC enabled they will be prompted as usual to allow the application to run in admin mode and if they have UAC disable in windows 8 the application will now run if real administrator mode.

    Important Notice to Microsoft Tag Customers

    Email From Microsoft

    Important Notice to our Microsoft Tag Customers

    To our valued Microsoft Tag Customer,

    This August 19, 2013 notice is to inform you that the Microsoft Tag service will terminate in two years, on August 19, 2015.  We are providing this two year termination notice in accordance with our Terms of Use for the Microsoft Tag Service located at this link: See Section 2 – Availability of Service; Changes to the Agreement & Service, paragraph 2.1.

    Through August 19, 2015, you will be able to continue to log into your existing Microsoft Tag service account, use existing Microsoft Tag codes, generate new Microsoft Tags, and run reports as usual.

    To help you prepare for the termination of the Microsoft Tag service on August 19, 2015, Scanbuy has been selected to support Microsoft Tag technology on the ScanLife platform beginning no later than September 18th, 2013, and to offer transition and migration services to Microsoft TAG customers who choose to migrate to the ScanLife platform.  This transition path will help you to continue running your campaigns using Microsoft Tags on the ScanLife platform.

    Scanbuy is the largest provider of QR codes and runs ScanLife, a cloud-based mobile engagement platform for creating personalized, uniquely tailored experiences for consumers to digitally engage with brands in their everyday surroundings through smartphones.

    If you wish to learn more about the ScanLife platform you may contact Adam Gold, VP of Sales at or call 212-278-0178 x 400.

    We thank you for allowing us to serve you as our Microsoft Tag customer.  If you have questions that Microsoft can assist you with please contact


    Eric Engstrom,     
    General Manager, Microsoft

    Embed images in mail with Outlook 2013 signature

    Have you recently tried embedding an image into your signature where you would normally in older outlook versions create a signature in html format and then use that as your signature.

    As you know this worked as expected in the older version and now in 2013 it doesn’t work anymore.

    The simple way to fix this is to add an entry into your registry using the below inside a .reg file.


       2: Windows Registry Editor Version 5.00 

       3: [HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Options\Mail]

       4: "Send 

       5: Pictures With Document"=dword:00000001 


    That’s all you need, restart outlook and now when you send an email the images will be embedded into the mail.

    The type caught or thrown must be derived from System.Exception

    Today I received a random error, the error message read “The type caught or thrown must be derived from System.Exception”. The reason why this was to me a random error was because I was trying to catch a Microsoft.TeamFoundation.WorkItemTracking.Client.ServerRejectedChangesException exception.


    To try see if maybe this was a bug or maybe my pc needed a reboot or something I started drilling into the definitions of the exception to try get all the way through to System.Exception.

    basically this looked like below

    public class ServerRejectedChangesException : ValidationException


    public class ValidationException : ClientException


    public class ClientException : TeamFoundationServerException


    public class TeamFoundationServerException : Microsoft.VisualStudio.Services.Common.VssException

    At the TeamFoundationServerException class I noticed that the VssException was not lit up by Visual Studio which to me meant that I didn’t have a reference added to be able to drill into it’s definition like I was for the previous levels.

    I added a reference to Microsoft.VisualStudio.Services.Common and suddenly the error been thrown when trying to build my project went away. Basically this allowed the IDE to navigate through to System.Exception like below.

    public abstract class VssException : ApplicationException


    public class ApplicationException : Exception


    public class Exception : ISerializable, _Exception

    It would be cool if this extra reference was not needed but I understand why it is Smile.

    Visual Studio 2013 Preview Update

    There is an update for the preview of Visual Studio 2013, this was release a couple days ago. There are 2 ways to get the update.

    The first look at the notifications in VS


    The other way is to browse to Microsoft Download Center using the link below 

    Once you have downloaded the exe file, the installation is the same as the updates for VS 2012, also remember if you are wanting to install this update on multiple machines you can download all the files using the /layout command argument (see below) and then copy the files to each machine.

    Open cmd.exe and navigate to the folder where you downloaded the exe update file to, then run “VS2013 Preview Update.exe” /layout


    This will launch the update application prompting for a location to download the installation files to. Next Click Download and wait until the download completes.

    image image image

    Now that the files are complete launch the installer again without /layout and then the installer will use the downloaded files instead of downloading each file each time you run the installer.

    This method can be used with all VS bootstrap installers and is not only a VS 2013 installer feature.

    TfsApi 1.2

    Documentation for this release can be found on the link

    Features adding to this release


    • Area Path Management
    • Team Management

    This will probably be the last update for a while but feel free to contact me for additions that you would like.


    The Release can be found using the below link on codeplex