0

Our company recently updated TFS to 2015 update 1. After that context menu item named Drop folder disappeared from completed builds. I found nothing about it and how to bring it back. When I click Open on completed build, VS opens web version of TFS where I forced to click through the menus and copy drop folder path manually. So I decided to write a simple extension that will add this item to the menu.
Some googling brought me to this page. But it seems that the example code is quite old and not working in VS2015:

IVsTeamFoundationBuild vsTfBuild = (IVsTeamFoundationBuild)GetService(typeof(IVsTeamFoundationBuild));
IBuildDetail[] builds = vsTfBuild.BuildExplorer.CompletedView.SelectedBuilds;

Property SelectedBuilds is always empty. I suppose that it relates to old window from VS2010. It returns items that are instance of IBuildDetail interface.
So I found this piece of code here:

var teamExplorer = (ITeamExplorer)ServiceProvider.GetService(typeof(ITeamExplorer));
var page = teamExplorer.CurrentPage;
var buildsPageExt = (IBuildsPageExt)page.GetExtensibilityService(typeof(IBuildsPageExt));
var build = buildsPageExt.SelectedBuilds[0];

Here build is the instance of IBuildModel interface. It lacks DropLocation property.

Is there any way to found drop location of selected build? Or maybe latest build?

Axm
  • 617
  • 1
  • 7
  • 11

2 Answers2

1

You can use IBuildDedetail.DropLocation in .NET client libraries for Visual Studio Team Services (and TFS). Basic code for your reference:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;

namespace BuildAPI
{
    class Program
    {
        static void Main(string[] args)
        {
            string project = "http://xxx.xxx.xxx.xxx";
            TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(project));
            IBuildServer ibs = tpc.GetService<IBuildServer>();
            var builds = ibs.QueryBuilds("TeamProjectName");
            foreach (IBuildDetail ibd in builds)
            {
                Console.WriteLine(ibd.DropLocation);
                Console.ReadLine();
            }
        }
    }
}
Eddie Chen - MSFT
  • 29,708
  • 2
  • 46
  • 60
  • We have 635 build definitions. Many of them build regularly. I want drop location for one specific selected build. How do I know, which one to choose from `builds` collection? – Axm Feb 12 '16 at 09:23
  • 1
    @JohnPreston Then you can use GetBuild method to get specified build. See this link: https://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.build.client.ibuildserver.getbuild(v=vs.120).aspx. And also, since you are using TFS2015, it support Rest API, you can also use this API to get these information. Link: https://www.visualstudio.com/integrate/api/build/builds#Getbuilddetails – Eddie Chen - MSFT Feb 15 '16 at 07:19
  • `GetBuild` works with build URI. `IBuildModel` doesn't contain this property. But private class BuildModel does. I used reflection to get it value. Shame on Microsoft for such poor API. Thank you for help. – Axm Feb 15 '16 at 13:13
0

So, after digging through TFS API, I ended up with this workaround.

    private void MenuItemCallback(object sender, EventArgs e)
    {
        var context = (ITeamFoundationContextManager)ServiceProvider.GetService(typeof(ITeamFoundationContextManager));
        IBuildServer buildServer = context.CurrentContext.TeamProjectCollection.GetService<IBuildServer>();
        var teamExplorer = (ITeamExplorer)ServiceProvider.GetService(typeof(ITeamExplorer));
        var buildsPageExt = (IBuildsPageExt)teamExplorer.CurrentPage.GetExtensibilityService(typeof(IBuildsPageExt));
        var menuCommand = (MenuCommand)sender;

        if (menuCommand.CommandID.Guid == CommandSetCompleted)
        {
            foreach (var buildDetail in buildsPageExt.SelectedBuilds)
                Process.Start("explorer.exe", GetBuild(buildServer, buildDetail).DropLocation);
        }

        if (menuCommand.CommandID.Guid == CommandSetFavorite)
        {
            var definitions = buildsPageExt.SelectedFavoriteDefinitions.Concat(buildsPageExt.SelectedXamlDefinitions).ToArray();

            foreach (var build in GetLatestSuccessfulBuild(buildServer, definitions))
                Process.Start("explorer.exe", build.DropLocation);
        }
    }

    private IBuildDetail GetBuild(IBuildServer buildServer, IBuildModel buildModel)
    {
        Uri buildUri = new Uri(buildModel.GetType().GetProperty("UriToOpen").GetValue(buildModel).ToString());
        return buildServer.GetBuild(buildUri);
    }

    private IBuildDetail[] GetLatestSuccessfulBuild(IBuildServer buildServer, IDefinitionModel[] buildDefinitions)
    {
        var spec = buildServer.CreateBuildDetailSpec(buildDefinitions.Select(bd => bd.Uri));
        spec.MaxBuildsPerDefinition = 1;
        spec.QueryOrder = BuildQueryOrder.FinishTimeDescending;
        spec.Status = BuildStatus.Succeeded;

        var builds = buildServer.QueryBuilds(spec);

        return builds.Builds;
    }
Axm
  • 617
  • 1
  • 7
  • 11