Skip to content

Instantly share code, notes, and snippets.

@jfoshee
Created March 13, 2025 12:39
Show Gist options
  • Save jfoshee/43450585b63b8c60a0a5c1b0ff3d2389 to your computer and use it in GitHub Desktop.
Save jfoshee/43450585b63b8c60a0a5c1b0ff3d2389 to your computer and use it in GitHub Desktop.
Delete retained builds for a specific build definition in Azure DevOps.
using System.Net.Http.Json;
/// <summary>
/// Delete retained builds for a specific build definition in Azure DevOps.
/// </summary>
static async Task CleanRetainedBuildsAsync()
{
// Configuration
string organization = "my-org";
string project = "my-proj";
int buildDefinitionId = 1234;
Console.WriteLine($"Cleaning retained builds for build definition {buildDefinitionId} in project {project} of organization {organization}");
// Personal Access Token (PAT) with permissions for Build (Read & execute) and Release (Read, write & execute)
string pat = "my-pat";
// Setup HttpClient with basic auth (username is blank)
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($":{pat}")));
string baseUri = $"https://dev.azure.com/{organization}/{project}";
// Get all builds for the build definition
string buildsUri = $"{baseUri}/_apis/build/builds?definitions={buildDefinitionId}&api-version=7.0";
var buildsResponse = await client.GetFromJsonAsync<BuildsResponse>(buildsUri);
if (buildsResponse is null || buildsResponse.Count == 0)
{
Console.WriteLine("No builds found.");
return;
}
foreach (var build in buildsResponse.Value)
{
if (!build.RetainedByRelease)
continue;
Console.WriteLine($"Processing retained build: {build.Id}");
// Get lease details for the build
string leaseDetailsUri = $"{baseUri}/_apis/build/builds/{build.Id}/leases?api-version=7.1";
var leaseResponse = await client.GetFromJsonAsync<LeaseResponse>(leaseDetailsUri);
if (leaseResponse?.Count > 0)
{
foreach (var lease in leaseResponse.Value)
{
Console.WriteLine($"Deleting lease {lease.LeaseId} for build {build.Id}");
string deleteLeaseUri = $"{baseUri}/_apis/build/retention/leases?ids={lease.LeaseId}&api-version=7.1";
var deleteLeaseResult = await client.DeleteAsync(deleteLeaseUri);
if (!deleteLeaseResult.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to delete lease {lease.LeaseId} for build {build.Id}: {deleteLeaseResult.StatusCode}");
}
}
}
// Now delete the build
Console.WriteLine($"Deleting build {build.Id}");
string deleteBuildUri = $"{baseUri}/_apis/build/builds/{build.Id}?api-version=7.1";
var deleteBuildResult = await client.DeleteAsync(deleteBuildUri);
if (!deleteBuildResult.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to delete build {build.Id}: {deleteBuildResult.StatusCode}");
}
}
}
await CleanRetainedBuildsAsync();
record Build(int Id, bool RetainedByRelease);
record BuildsResponse(int Count, List<Build> Value);
record Lease(int LeaseId);
record LeaseResponse(int Count, List<Lease> Value);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment