Blog Stats
  • Posts - 63
  • Articles - 0
  • Comments - 6
  • Trackbacks - 0


Friday, September 12, 2014

TFS Git Delete Remote Branch from Server

You can delete a published branch from Visual Studio, but to remove it from the server use the this git statement from the command prompt of the local repo:

C:\_s\Git\myRepo>git push origin --delete myBranch

Friday, September 5, 2014

Team Explorer Everywhere Command Line Client on a Mac

I was surprised at the dearth of information on this. Here’s the whole enchilada. I did not have to enter user or password information for the TF commands. This could be because my Mac account and password is identical to the Windows AD account and password. Use TF help to get a full listing of commands and TF Help [command] for details on each command.

  1. Download TEE-CLC on your Mac and unzip/move the TEE-CLC-x.x.x folder under your Applications folder.
  2. Open the Terminal command line on you Mac (search for Terminal)
  3. To see the current execution path on your Mac:
    • echo $PATH
  4. Enter the following to add the TEE-CLC to you path:
    • export PATH=$PATH:/Applications/{TEE CLC folder name}
    • example: export PATH=$PATH:/Applications/TEE-CLC-11.0.0
  5. Enter the echo command again to verify the TEE CLC folder has been added to the path
  6. Install the Java JDK (installing just the run time is  not sufficient)
  7. You should be able to execute the TF command from the Terminal window once the Java JDK install has completed
  8. Create a folder to store your TFS source control files. I used \Documents\_s\MyCollection
  9. Accept the TCC license by executing:
    • tf eula
  10. Create the workspace:
    • tf workspace -new -collection:{url} -location:server {workspace name}
  11. Create the workspace mapping
    • tf  workfold -map -collection:{url} -workspace:{workspace name} '$/' '/Users/bob_hardister/Documents/_s/MyCollection'
  12. Navigate to your local workspace folder in the Terminal window to run TF Get and other workspace centric commands
  13. A Get command would then be
    • tf get -r  '$/…desried TFS server location to pull from'

Friday, July 25, 2014

Move TFS 2013 Git Repo to a Different Team Project

1. Go to the target team project admin site

2. Create the new repo on the TFS server (with the same name as the source repo except under the target team project)

3. Go to the target team project site and display the empty repo. Note the new repo remote url

4. Open Visual Studio 2013 team explorer connect tab (Update 2 or >) and connect to the remote source repo

5. Clone the repo under the new team project or other location

6. Right click on the new (cloned) local repo and click Open Command Prompt

7. Copy the new remote url of the target team project repo (see #3 above)

8. Execute the following command in the command prompt:

[new local repo location]>git remote set-url origin [new remote url]

9. Right click on the new local repo in the Visual Studio 2013 team explorer connect tab

10. Click Open

11. Click "Unsynced Commits"

12. Click the "Sync" button to push the local repo to the remote

13. Go to the remote repo site and refresh to display the code just pushed from the local repo

Tuesday, July 8, 2014

TFS 2013 Build Process Template – Change the “AutomatedTest” Argument Default Value

It should be simple, but due to the length of the string, the syntax was not obvious. The out-of-the-box default is as follows. Note the backslash escape character and stick to the syntax:

{New Microsoft.TeamFoundation.Build.Common.BuildParameter(" { ""AssemblyFileSpec"": ""**\\*test*.dll;**\\*test*.appx"", ""RunSettingsFileName"": null, ""TestCaseFilter"": """", ""RunSettingsForTestRun"": { ""ServerRunSettingsFile"": """", ""TypeRunSettings"": ""Default"", ""HasRunSettingsFile"": false }, ""HasRunSettingsFile"": false, ""HasTestCaseFilter"": false, ""ExecutionPlatform"": ""X86"", ""FailBuildOnFailure"": false, ""RunName"": """" } ")}

If you remove the values to expose the syntax you get:

{New Microsoft.TeamFoundation.Build.Common.BuildParameter("")}

To add another “test” to the template use this:

{New Microsoft.TeamFoundation.Build.Common.BuildParameter(""), 
New Microsoft.TeamFoundation.Build.Common.BuildParameter("")}

and just provide the desired values. Here’s what I ended up with:

{New Microsoft.TeamFoundation.Build.Common.BuildParameter(" { ""AssemblyFileSpec"": ""*UnitTests\\*Tests.dll"", ""RunSettingsFileName"": null, ""TestCaseFilter"": """", ""RunSettingsForTestRun"": { ""ServerRunSettingsFile"": """", ""TypeRunSettings"": ""Default"", ""HasRunSettingsFile"": false }, ""HasRunSettingsFile"": false, ""HasTestCaseFilter"": false, ""ExecutionPlatform"": ""X64"", ""FailBuildOnFailure"": false, ""RunName"": ""Unit"" } "), New Microsoft.TeamFoundation.Build.Common.BuildParameter(" { ""AssemblyFileSpec"": ""*IntegrationTests\\*Tests.dll"", ""RunSettingsFileName"": null, ""TestCaseFilter"": """", ""RunSettingsForTestRun"": { ""ServerRunSettingsFile"": """", ""TypeRunSettings"": ""Default"", ""HasRunSettingsFile"": false }, ""HasRunSettingsFile"": false, ""HasTestCaseFilter"": false, ""ExecutionPlatform"": ""X64"", ""FailBuildOnFailure"": false, ""RunName"": ""Integration"" } ")}

Nuget Push to Klondike Requires Packages Appended to Source and SetApiKey URL

When we upgraded to NuGet 2.8 our PUSH command  starting failing.  The first fix was to appended “packages” to the source URL string like this:

We use the SetApiKey command on our server so we don’t have to explicitly include the API key itself in the push command. After making the change above we had issues with the PUSH command not finding the API key. To fix this, we appended “packages” to the source argument of the SetApiKey command like this:

nuget setApiKey myApiKey -Source

Everything then worked fine as before.

Wednesday, June 25, 2014

Get the Last Build By Build Number with Visual Studio ALM Team Foundation Server API

Here is a handy TFS 2013 build activity for getting the last build by build number filter. In this case:

  • We get the 4th version node number: we call it the revision number
  • We use semantic versioning and some of our branches are “version named” i.e. 1.4.10
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Build.Workflow;

namespace Alm.BuildActivities
    public sealed class GetVersionNamedBranchLastRevisionNumber : CodeActivity
        #region arguments
        public InArgument<string> BuildDefinitionName { get; set; }
        public InArgument<string> CollectionUrl { get; set; }
        public InArgument<string> ProjectName { get; set; }
        public InArgument<string> VersionNamedBranch { get; set; }
        public OutArgument<int> CurrentRevisionNumber { get; set; }

        protected override void Execute(CodeActivityContext context)
            #region initialize fields
            context.SetValue(this.CurrentRevisionNumber, 0);
            string buildDefinitionName = context.GetValue(this.BuildDefinitionName);
            string collectionUrl = context.GetValue(this.CollectionUrl);
            string projectName = context.GetValue(this.ProjectName);
            string versionNamedBranch = context.GetValue(this.VersionNamedBranch);
            int currentRevisionNumber = 0;

                TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(collectionUrl));

                if (!(tpc == null))
                    var bs = tpc.GetService<IBuildServer>();
                    var buildDefSpec = bs.CreateBuildDefinitionSpec(projectName, buildDefinitionName);
                    buildDefSpec.Options = QueryOptions.None;
                    var buildDef = bs.QueryBuildDefinitions(buildDefSpec).Definitions.FirstOrDefault();

                    var buildSpec = bs.CreateBuildDetailSpec(buildDef);
                    //gets only "stable builds" of the version named branch
                    buildSpec.BuildNumber = string.Format("*_{0}.?", versionNamedBranch); 
                    buildSpec.QueryDeletedOption = QueryDeletedOption.IncludeDeleted;
                    buildSpec.MaxBuildsPerDefinition = 1;
                    //gets last stable build of the version named branch
                    buildSpec.QueryOrder = BuildQueryOrder.StartTimeDescending; 

                    var buildQuery = bs.QueryBuilds(buildSpec).Builds.FirstOrDefault();

                    if (buildQuery == null)
                        currentRevisionNumber = 0; //there aren no stable builds of this version named branch
                        context.SetValue(this.CurrentRevisionNumber, currentRevisionNumber);
                        var lastStableBuildNumber = buildQuery.BuildNumber;
                        string currentRevisionNumberString = lastStableBuildNumber
                            .Substring(lastStableBuildNumber.LastIndexOf(".") + 1);
                        bool result = Int32.TryParse(currentRevisionNumberString, out currentRevisionNumber);
                        if (result)
                            context.SetValue(this.CurrentRevisionNumber, currentRevisionNumber); 
                            string text = "ERROR: unable to parse to int the currentRevisionNumberString value:";
                            throw new Exception(string.Format("{0} {1}.", text, currentRevisionNumberString));
                    throw new Exception("ERROR: unable to connect to TFS collection");
            catch (Exception ex)
                throw new Exception(string.Format("ERROR. Exception message: {0}", ex.Message));

Monday, April 28, 2014

Delete Build Drop Locations Some Number of Days Old

We don’t need to keep our drop folders after a week. Here some code to accomplish that.

public void SetDeleteBuildDropFolder()
    //method parms
    string collectionUrl = "http://myCollectionUrl";
    string projectName = "myTeamProjectName";
    var buildDefinitionNames = new List<string>();
    var fromDate = DateTime.Now.AddDays(-8);
    //execute method
    var buildsDropFoldersDeleted = tfsApi.SetDeleteBuildDropFolder(collectionUrl, projectName, buildDefinitionNames, fromDate);

    //evaluate result
public Dictionary<string, string> SetDeleteBuildDropFolder(string collectionUrl, string projectName, 
List<string> buildDefinitionNames, DateTime fromDateTime) { var buildsDropFoldersDeleted = new Dictionary<string, string>(); try { TfsTeamProjectCollection tpc = GetCollection(collectionUrl); if (!(tpc == null)) { var bs = tpc.GetService<IBuildServer>(); foreach (var buildDefName in buildDefinitionNames) { var buildSpec = bs.CreateBuildDetailSpec(projectName, buildDefName); buildSpec.QueryOptions = QueryOptions.None; buildSpec.InformationTypes = null; buildSpec.MaxBuildsPerDefinition = 100; buildSpec.QueryOrder = BuildQueryOrder.StartTimeDescending; buildSpec.QueryDeletedOption = QueryDeletedOption.ExcludeDeleted; if (buildSpec != null) { var buildsDrops = bs.QueryBuilds(buildSpec).Builds.Where(x => x.FinishTime < fromDate)
.Select(y => new { y.Uri, y.DropLocation }); foreach (var build in buildsDrops) { if (Directory.Exists(build.DropLocation)) { try { var bSpec = bs.CreateBuildDetailSpec(projectName, buildDefName); bSpec.QueryOptions = QueryOptions.None; bSpec.InformationTypes = null; bSpec.QueryDeletedOption = QueryDeletedOption.ExcludeDeleted; //Using the build service you can delete multiple build drop folders at the same time //var targetBuild = bs.QueryBuilds(bSpec).Builds.Where(b => b.Uri == build.Uri).ToArray(); //bs.DeleteBuilds(targetBuild, DeleteOptions.DropLocation); //Here I'm only deleting a single build drop folder at a time var targetBuild = bs.QueryBuilds(bSpec).Builds.Where(b => b.Uri == build.Uri).FirstOrDefault(); if (targetBuild.KeepForever) { targetBuild.KeepForever = false; targetBuild.Save(); } targetBuild.Delete(DeleteOptions.DropLocation); buildsDropFoldersDeleted.Add(targetBuild.BuildNumber, build.DropLocation); } catch (Microsoft.TeamFoundation.Build.Client.BuildDefinitionNotFoundException) { //build not found } } } } else { return null; } } return buildsDropFoldersDeleted; } else { return null; } } catch (Exception ex) { return null; } }

Monday, January 20, 2014

Formatting Command Output on the TFS Build Report

The Team Foundation Server build can include command output in the report log or summary by using the stdOutput and errOutput variables in the WriteBuildMessage or WriteCustomSummaryInformation activities. I’ve often been frustrated with bad formatting that you get.  However, I found that if you simply use String.Format with stdOutput or errOuput, you get much better formatting. For example, with String.Format("{0}", stdOutput) you get correct line returns.

Tuesday, January 14, 2014

Making Agile Work on Large, Complex Projects

Our TFS Austin User Group hosted this presentation last month given by Mike Haze, Director of Product Management for Volusion.  It was one of the best meetings we’ve had.  Here’s the abstract and a link to the recording of the meeting.

Recording Link: 

Presentation: Making Agile Work on Large, Complex Projects

There is a widely accepted myth that extremely complex software platforms, committed delivery schedules, large development teams, and dynamic business environments are in direct opposition to agile development processes and tools and if your product / project fall into one of these categories you will struggle with fully embracing agile methodologies. This is simply not the case, you can fully adopt and leverage an agile methodology through out the full product lifecycle, gaining all the advantages and leverage the tools your teams are already using today and are tightly integrated into the development process. We will explore how Volusion is currently leveraging TFS throughout the complete product lifecycle and discuss some of the key challenges and successes that we've experienced along the way.


Mike Haze is the Director of Product Management at Volusion. Mike holds a number of patents including "System and method for migration of digital assets" and "System and method for self-provisioning of virtual images." Beginning his career as a Software Developer, Mike has served as a Product Experience Strategist for Dell and the Director of Product Development and Innovation for Dun and Bradstreet.

Volusion is a leading e-commerce software development company based in Austin with $12B in merchant sales worldwide, over 40,000 online stores and over 450 employees. Volusion has won the Austin Business Journal's award for Best Places to Work for 2012 and 2013.

TfsUGMikeHaze1 TfsUGMikeHaze2

Wednesday, July 24, 2013

TFS 2012 API Create Alert Subscriptions

There were only a few post on this and I felt like really important information was left out:

  1. What the defaults are
  2. How to create the filter string

Here’s the code to create the subscription.

Get the Collection

public TfsTeamProjectCollection GetCollection(string collectionUrl)
                //connect to the TFS collection using the active user
                TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(collectionUrl));
                return tpc;
            catch (Exception)
                return null;

Use Impersonation

Because my app is used to create “support tickets” as stories in TFS, I use impersonation so the subscription is setup for the “requester.”  That way I can take all the defaults for the subscription delivery preferences.

public TfsTeamProjectCollection GetCollectionImpersonation(string collectionUrl, string impersonatingUserAccount)
            // see:
                TfsTeamProjectCollection tpc = GetCollection(collectionUrl);
                if (!(tpc == null))
                    //get the TFS identity management service (v2 is 2012 only)
                    IIdentityManagementService2 ims = tpc.GetService<IIdentityManagementService2>();

                    //look up the user we want to impersonate
                    TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName, 

                    //create a new connection using the impersonated user account
                    //note: do not ensure authentication because the impersonated user may not have 
                    //windows authentication at execution
                    if (!(identity == null))
                        TfsTeamProjectCollection itpc = new TfsTeamProjectCollection(tpc.Uri, identity.Descriptor);
                        return itpc;
                        //the user account is not found
                        return null;
                    return null;
            catch (Exception)
                return null;

Create the Alert Subscription

public bool SetWiAlert(string collectionUrl, string projectName, int wiId, string emailAddress, string userAccount)
            bool setSuccessful = false;
                //use impersonation so the event service creating the subscription will default to 
                //the correct account: otherwise domain ambiguity could be a problem
                TfsTeamProjectCollection itpc = GetCollectionImpersonation(collectionUrl, userAccount);

                if (!(itpc == null))

                    IEventService es = itpc.GetService(typeof(IEventService)) as IEventService;

                    DeliveryPreference deliveryPreference = new DeliveryPreference();
                    //deliveryPreference.Address = emailAddress;
                    deliveryPreference.Schedule = DeliverySchedule.Immediate;
                    deliveryPreference.Type = DeliveryType.EmailHtml;

                    //the following line does not work for two reasons: 
                    //string filter = string.Format("\"ID\" = '{0}' AND \"Authorized As\" <> '[Me]'", wiId);

                    //1. the create fails because there is a space between Authorized As
                    //2. the explicit query criteria are all incorrect anyway
                    //   see uncommented line for what does work: you have to create the subscription mannually 
                    //   and then get it to view what the filter string needs to be (see following commented code)
                    //this works
                    string filter = string.Format("\"CoreFields/IntegerFields/Field[Name='ID']/NewValue\" = '12175'" + 
                                                    " AND \"CoreFields/StringFields/Field[Name='Authorized As']/NewValue\"" + 
                                                    " <> '@@MyDisplayName@@'", projectName, wiId);

                    string eventName = string.Format("<PT N=\"ALM Ticket for Work Item {0}\"/>", wiId);

                    es.SubscribeEvent("WorkItemChangedEvent", filter, deliveryPreference, eventName);

                    ////use this code to get existing subscriptions: you can look at manually created 
                    ////subscriptions to see what the filter string needs to be
                    //IIdentityManagementService2 ims = itpc.GetService<IIdentityManagementService2>();
                    //TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName, 
                    //    userAccount, 
                    //    MembershipQuery.None, 
                    //    ReadIdentityOptions.None);
                    //var existingsubscriptions = es.GetEventSubscriptions(identity.Descriptor);

                    setSuccessful = true;

                    return setSuccessful;
                    return setSuccessful;
            catch (Exception)
                return setSuccessful;


Copyright © Bob Hardister