I am making an assumption here that we know that TFS2015 Update2 does not support this out of the box and that MS claims to have resolved this in TFS15.
I am going to paste the whole program for everyone's benefit. This code works and I am using it. I run this utility to clean the build artifactdrop location for builds that have been wiped out of TFS. The assumption we are making is that we are going to call the exe with two parameters that are folder paths:
DeleteBuild.exe "D:\TFSDropLocation" "D:\DumpFolderBeforeIDeleteThis"
where TFSDropLocation is where TFS is dropping the build artifacts and DumpFolderBeforeIDeleteThis is a folder where I collect all builds before I delete them forever. We could have deleted them right from TFSDropLocation but I like this additional step where we remove them to a separate location so that just in case, we happen to need them, they are their or alternately delete them from this location after one more round of verification -if that makes you comfortable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Client;
using System.IO;
using System.Configuration;
namespace DeleteBuild
{
class Program
{
static void Main(string[] args)
{
if (args.Length!=2)
{
Console.WriteLine("Correct usage is: DeleteBuild.exe 'Absolute Folderpath Path to TFS drop location' 'Absolute Dump Location'");
Console.WriteLine("for e.g. something with quotes around the paths: DeleteBuild.exe D:\\TFSDropLocation D:\\Dumplocation");
return;
}
string BuildDropLocation = args[0];
string BuildDumpLocation = args[1];
#region TFSConnection
string accountUrl = ConfigurationManager.AppSettings["accountUrl"];
VssConnection connection = new VssConnection(new Uri(accountUrl), new VssAadCredential());
ProjectHttpClient projectClient = connection.GetClient<ProjectHttpClient>();
TeamProject project = projectClient.GetProject(ConfigurationManager.AppSettings["TeamProject"]).Result;
BuildHttpClient buildClient = connection.GetClient<BuildHttpClient>();
var builds = buildClient.GetBuildsAsync(project.Id, resultFilter: BuildResult.Succeeded).Result;
#endregion
//the parent directory that is the build definition name
DirectoryInfo directory = new DirectoryInfo(BuildDropLocation);
// list of subdirectories which are the actual buildnumber folders
DirectoryInfo[] ParentFolders = directory.GetDirectories();
foreach (DirectoryInfo ParentFolder in ParentFolders)
{
Console.WriteLine(Environment.NewLine + "Build Definition name: " + ParentFolder.Name + " contains the below mentioned builds");
//list of all folders inside the build definition folder which are buildnumber folders
DirectoryInfo[] ChildFolders = ParentFolder.GetDirectories();
//We evaluate each build number folder one by one
foreach (DirectoryInfo ChildFolder in ChildFolders)
{
Console.WriteLine(Environment.NewLine + "Evaluating folder: " + ChildFolder.Name);
//Create an array of builds where the definition name matches the parent foldername
Build[] RelevantBuilds = builds.Where(b => b.Definition.Name == ParentFolder.Name).Reverse().ToArray();
//set flag to false to
Boolean flag = false;
// traverse through each build in tfs
foreach (Build build in RelevantBuilds)
{
{
//compare build number with child folder name to see if it matches
if (string.Compare(build.BuildNumber, ChildFolder.Name) == 0)
{
Console.WriteLine("Found build existing, hence leaving here " + build.BuildNumber);
//this build has yet not been deleted from tfs so set flag to true and traverse to the next build
flag = true;
}
}
}
// if after travering the builds, we did not find any build that matches the foldername, flag will be set to initial value of false and this folder will be deleted
if (flag == false)
{
DirectoryInfo DropLocation = new DirectoryInfo(BuildDumpLocation);
string intermediatPath = Path.Combine(BuildDumpLocation, ParentFolder.Name);
DirectoryInfo IntermediateDirectory = new DirectoryInfo(intermediatPath);
if (!IntermediateDirectory.Exists)
{
DropLocation.CreateSubdirectory(ParentFolder.Name);
}
string sourcePath = ChildFolder.Name;
string targetPath = Path.Combine(DropLocation.FullName, ParentFolder.Name, ChildFolder.Name);
System.IO.Directory.Move(ChildFolder.FullName, targetPath);
Console.WriteLine("Did not find build in TFS hence moving: " + ChildFolder.Name + " to a separate dump location.");
}
}
Console.WriteLine("******************************************************");
}
Console.WriteLine("end of directory" + Environment.NewLine);
}
}
}