There has been a lot of errors with various versions of TFS/DevOps Server about obsoleted builds in the drop folders, and the various versions have fixed part of it. Here comes a script to identify them.
To identify things, we don’t know, we need to get a list of what we know and then compare that with the actual dropfolders.
The following function will iterate through all build definitions in a project and find all the corresponding builds artifact locations
Function Get-TFSBuildDrops{
param (
[string]$ServerURL,
[string]$Project
)
$URL = "$($ServerURL)/$($Project)"
#Get all builddefinitions in Project
$Builddefinitions = (Invoke-RestMethod -Uri ($URL + '/_apis/build/definitions?api-version=3.2') -Method GET -UseDefaultCredentials).value
$Builds = @()
foreach($Builddefinition in $Builddefinitions)
{
#Get the Build IDs
$BuildIDs= (Invoke-RestMethod -Uri ($URL + '/_apis/build/builds?api-version=2.0&definitions=' + $Builddefinition.ID) -Method Get -UseDefaultCredentials).value.ID
foreach ($Build in $BuildIDs)
{
#Get Artifacts
$Artifacts = (Invoke-RestMethod -Uri ($URL + '/_apis/build/builds/' + $Build + '/artifacts') -Method Get -UseDefaultCredentials).value
if(!([string]::IsNullOrEmpty($Artifacts.resource.properties.artifactlocation)) -and !([string]::IsNullOrEmpty($Artifacts.name)))
{
#Handle multiple artifacts per build
foreach ($Artifact in $Artifacts)
{
$BuildDrops = New-Object -TypeName psobject
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Definition' -Value $Builddefinition.name
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Location' -Value $Artifact.resource.properties.artifactlocation
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Name' -Value $Artifact.Name
$Builds += $BuildDrops
}
}
}
}
Return $Builds
}
Then we can go through all the drop folders and see if we can find subfolders (builds) not known to the server
Drops = Get-TFSBuildDrops -ServerURL $ServerURL -Project $Project
$ObsoletedBuilds =@()
foreach ($Drop in ($Drops | Sort-Object -Property Location -Unique))
{
$KnownDrops = $Drops | Where-Object {$_.Location -eq $Drop.Location}
if (Test-Path $Drop.Location)
{
$Childs = Get-ChildItem -Path $Drop.Location -Directory | Where-Object {$_.name -notin $KnownDrops.name} | Select-Object fullName
foreach ($Child in $Childs)
{
$ObsoletedBuild = New-Object -TypeName psobject
Add-Member -InputObject $ObsoletedBuild -MemberType NoteProperty -Name "BuildDefinition" -Value $Drop.Definition
Add-Member -InputObject $ObsoletedBuild -MemberType NoteProperty -Name "Path" -Value $Child.FullName
$ObsoletedBuilds += $ObsoletedBuild
}
}
}
$ObsoletedBuilds | export-csv C:\Temp\$($Project).csv -Force
We now have a list of potential obsoleted drops. You need to verify the list!!!
If you have nested drop folders where builds is dropped inside other build drops it will be identified as a false positive. Also if you have builddrop paths containing the buildnumber, instead of in the artifact name it will not be identified, as they are all unique. Full script below
Enjoy
$ServerURL = "HTTP://[server]:8080/tfs/defaultcollection"
$Project = "[project]"
Function Get-TFSBuildDrops{
param (
[string]$ServerURL,
[string]$Project
)
$URL = "$($ServerURL)/$($Project)"
#Get all builddefinitions in Project
$Builddefinitions = (Invoke-RestMethod -Uri ($URL + '/_apis/build/definitions?api-version=3.2') -Method GET -UseDefaultCredentials).value
$Builds = @()
foreach($Builddefinition in $Builddefinitions)
{
#Get the Build IDs
$BuildIDs= (Invoke-RestMethod -Uri ($URL + '/_apis/build/builds?api-version=2.0&definitions=' + $Builddefinition.ID) -Method Get -UseDefaultCredentials).value.ID
foreach ($Build in $BuildIDs)
{
#Get Artifacts
$Artifacts = (Invoke-RestMethod -Uri ($URL + '/_apis/build/builds/' + $Build + '/artifacts') -Method Get -UseDefaultCredentials).value
if(!([string]::IsNullOrEmpty($Artifacts.resource.properties.artifactlocation)) -and !([string]::IsNullOrEmpty($Artifacts.name)))
{
#Handle multiple artifacts per build
foreach ($Artifact in $Artifacts)
{
$BuildDrops = New-Object -TypeName psobject
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Definition' -Value $Builddefinition.name
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Location' -Value $Artifact.resource.properties.artifactlocation
Add-Member -InputObject $BuildDrops -MemberType NoteProperty -Name 'Name' -Value $Artifact.Name
$Builds += $BuildDrops
}
}
}
}
Return $Builds
}
$Drops = Get-TFSBuildDrops -ServerURL $ServerURL -Project $Project
$ObsoletedBuilds =@()
foreach ($Drop in ($Drops | Sort-Object -Property Location -Unique))
{
$KnownDrops = $Drops | Where-Object {$_.Location -eq $Drop.Location}
if (Test-Path $Drop.Location)
{
$Childs = Get-ChildItem -Path $Drop.Location -Directory | Where-Object {$_.name -notin $KnownDrops.name} | Select-Object fullName
foreach ($Child in $Childs)
{
$ObsoletedBuild = New-Object -TypeName psobject
Add-Member -InputObject $ObsoletedBuild -MemberType NoteProperty -Name "BuildDefinition" -Value $Drop.Definition
Add-Member -InputObject $ObsoletedBuild -MemberType NoteProperty -Name "Path" -Value $Child.FullName
$ObsoletedBuilds += $ObsoletedBuild
}
}
}
$ObsoletedBuilds | export-csv C:\Temp\$($Project).csv -Force