How can I delete Azure DevOps old build pipelines and there leases with Power Shell

Viewed 3187

Hello Azure DevOps Specialists

I do have in Azure DevOps some old build pipelines, that I want to delete using Power Shell. I wrote a little power shell script to get the build definitions first and then get the build id, name, etc. Before I can delete a definition, the retained releases must be deleted. The program code is prepared, but the API requires an ownerId or userId or Minimal Retention Leases. And that is my challenge!

I read the documentation https://docs.microsoft.com/en-us/rest/api/azure/devops/build/leases?view=azure-devops-rest-6.0 and the code behind (hosted on GitHub https://github.com/microsoft/azure-devops-node-api/blob/470f9ca7bdfccd87e1c1fdea8023b8c3d2b1047a/api/interfaces/BuildInterfaces.ts#L2006), but I have no idea what the ownerId or userId is or how I can find it.

$personalToken = "AzureDevOpsPersonalToken"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalToken)"))
$header = @{authorization = "Basic $token"}

$organization = "myOrga"
$project = "myProj"

$pipelineName = Read-Host "Please enter pipeline to delete"

#all build definitions
$url = "https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=6.0-preview.7"
$builddefinitions = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header

$builddefinitions.value | Sort-Object id|ForEach-Object {
    Write-Host $_.id $_.name $_.queueStatus
 
    if ( $_.name -ne $pipelineName ) {
        return;
    }

    #all builds for a definition
    $url = "https://dev.azure.com/$organization/$project/_apis/build/builds?definitions=" + $_.id + "&api-version=6.0-preview.5"
    $builds = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
 
    $builds.value | Sort-Object id|ForEach-Object {
        #report on retain status
        Write-Host " BuildId:" $_.id " retainedByRelease:" $_.retainedByRelease

        # ownerId UNKNOWN - where is this come from?
        $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ownerId=&api-version=6.0-preview.1"
        $leases = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header

        Write-Host $leases

        #$url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ids=" + $_.id + "&api-version=6.0-preview.1"
        #Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
        
        #Write-Host " BuildId:" $_.id " removed retentions"
        #
        #$url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "?api-version=6.0-preview.1"
        #Invoke-RestMethod -Uri $url -Method DELETE -ContentType "application/json" -Headers $header
        #Write-Host " BuildId:" $_.id " deleted"
    }

    #$url = "https://dev.azure.com/$organization/$project/_apis/build/definitions/" + $_.id + "?api-version=6.0-preview.1"
    #Invoke-RestMethod -Uri $url -Method Delete -ContentType "application/json" -Headers $header
    #
    #Write-Host " Pipeline:" $pipelineName " (" $_.id ") deleted"


    Write-Host
}

Can you help me? Thank you in advance Tino

3 Answers

Stuff in Azure DevOps Portal changes frequently. In February 2022 I composed my own script out of several Stackoverflow posts to remove old Build Pipelines, their Builds and their Leases.

#Azure DevOps Personal Access Token
# https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows

$personalAccessToken = "<Enter your personal access token here>"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalAccessToken)"))
$header = @{authorization = "Basic $token"}

$organization = "<Enter your Azure DevOps Organization here>"
$project = "<Enter your Project Name here>"

$pipelineName = "<Enter the Build Pipeline Definition Name to be deleted here>"

#Get all build definitions
# API: GET https://dev.azure.com/{organization}/{project}/_apis/build/definitions/{definitionId}?api-version=6.0
$url = "https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=6.0"
$allBuildDefinitions = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header

$allBuildDefinitions.value | Where-Object {$_.name -eq $pipelineName} | ForEach-Object {
    Write-Host $_.id $_.name $_.queueStatus
 
    # For debugging reasons, just to be sure that we don't delete the wrong build pipeline
    if ( $_.name -ne $pipelineName ) {
        return;
    }

    #Get all Builds for a Definition
    # API: GET https://dev.azure.com/{organization}/{project}/_apis/build/builds?definitions={definitions}&queues={queues}&buildNumber={buildNumber}&minTime={minTime}&maxTime={maxTime}&requestedFor={requestedFor}&reasonFilter={reasonFilter}&statusFilter={statusFilter}&resultFilter={resultFilter}&tagFilters={tagFilters}&properties={properties}&$top={$top}&continuationToken={continuationToken}&maxBuildsPerDefinition={maxBuildsPerDefinition}&deletedFilter={deletedFilter}&queryOrder={queryOrder}&branchName={branchName}&buildIds={buildIds}&repositoryId={repositoryId}&repositoryType={repositoryType}&api-version=6.0
    $url = "https://dev.azure.com/$organization/$project/_apis/build/builds?definitions=" + $_.id + "&api-version=6.0"
    $allBuildsOfDefinition = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
 
    #Process each Build of Definition
    $allBuildsOfDefinition.value | Where-Object {$_.retainedByRelease -eq "True"} | Sort-Object id | ForEach-Object {
        #Report on retain status
        Write-Host "Build Id:" $_.id " retainedByRelease:" $_.retainedByRelease

        #Get all Retention Leases for this Build
        # API: GET https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/leases?api-version=7.1-preview.1
        $url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "/leases?api-version=7.1-preview.1"
        $allLeasesOfBuild = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header

        #Delete each Lease of Build
        $allLeasesOfBuild.value | ForEach-Object {
            #Delete Lease
            # API: DELETE https://dev.azure.com/{organization}/{project}/_apis/build/retention/leases?ids={ids}&api-version=7.1-preview.2
            $url = "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ids=" + $_.leaseId + "&api-version=7.1-preview.2"
            Invoke-RestMethod -Uri $url -Method Delete -ContentType "application/json" -Headers $header

            #Report on Lease deleted
            Write-Host "Lease Id:" $_.leaseId " deleted"
        }

        #Delete Build
        # API: DELETE https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}?api-version=7.1-preview.7
        $url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "?api-version=7.1-preview.7"
        Invoke-RestMethod -Uri $url -Method Delete -ContentType "application/json" -Headers $header

        #Report on Build deleted
        Write-Host "Build Id:" $_.id " deleted"
    }

    #Delete the Build Definition
    # API: DELETE https://dev.azure.com/{organization}/{project}/_apis/build/definitions/{definitionId}?api-version=6.0
    $url = "https://dev.azure.com/$organization/$project/_apis/build/definitions/" + $_.id + "?api-version=6.0"
    Invoke-RestMethod -Uri $url -Method Delete -ContentType "application/json" -Headers $header
    
    Write-Host "Build Definition:" $pipelineName " (" $_.id ") deleted"
}
    
Write-Host "Habe fertig!"

Meanwhile I found am how-to from an unknown person on https://tenbulls.co.uk/2020/03/25/delete-old-build-definitions-in-azure-devops/ .

The trick is to patch the retainedByRelease value with retainedByRelease=false.

$url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "?api-version=6.0-preview.5"
 
Invoke-RestMethod -Uri $url -Method Patch -Body (ConvertTo-Json @{"retainedByRelease"="false"}) -ContentType "application/json" -Headers $header

$personalToken = "AzureDevOpsPersonalToken"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalToken)"))
$header = @{authorization = "Basic $token"}

$organization = "myOrga"
$project = "myProj"

$pipelineName = Read-Host "Please enter pipeline to delete"

#all build definitions
$url = "https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=6.0-preview.7"
$builddefinitions = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header

$builddefinitions.value | Sort-Object id|ForEach-Object {
    Write-Host $_.id $_.name $_.queueStatus
 
    if ( $_.name -ne $pipelineName ) {
        return;
    }

    #all builds for a definition
    $url = "https://dev.azure.com/$organization/$project/_apis/build/builds?definitions=" + $_.id + "&api-version=6.0-preview.5"
    $builds = Invoke-RestMethod -Uri $url -Method Get -ContentType "application/json" -Headers $header
 
    $builds.value | where {$_.retainedByRelease -eq "True"} | Sort-Object id|ForEach-Object {
        #report on retain status
        Write-Host " BuildId:" $_.id " retainedByRelease:" $_.retainedByRelease

        #api call for a build
        $url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "?api-version=6.0-preview.5"
 
        Invoke-RestMethod -Uri $url -Method Patch -Body (ConvertTo-Json @{"retainedByRelease"="false"}) -ContentType "application/json" -Headers $header
    }

    $url = "https://dev.azure.com/$organization/$project/_apis/build/definitions/" + $_.id + "?api-version=6.0-preview.1"
    Write-Host $url
    Invoke-RestMethod -Uri $url -Method Delete -ContentType "application/json" -Headers $header
    
    Write-Host " Pipeline:" $pipelineName " (" $_.id ") deleted"
}
    
Write-Host "done"

Just an FYI, I ran the above script and was still having problems with it not allowing me to delete all retained builds. Instead I had to query leases for the current build and delete them individually, then I was able to delete the build. My modified loop from the other answer is below.

    $builds.value | where {$_.retainedByRelease -eq "True"} | Sort-Object id|ForEach-Object {
        #report on retain status
        Write-Host " BuildId:" $_.id " retainedByRelease:" $_.retainedByRelease

        #api call for a build
        $url = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "?api-version=6.1-preview.7"

        #Get All leases for the current build

        $leaseurl = "https://dev.azure.com/$organization/$project/_apis/build/builds/" + $_.id + "/leases?api-version=6.1-preview.1"
        $result = Invoke-RestMethod -Uri $leaseurl -Method Get -Headers $header
         
        if ($result.count -gt 0){
            $result.value | ForEach-Object {
                $deleteLeaseUrl =  "https://dev.azure.com/$organization/$project/_apis/build/retention/leases?ids=" + $_.leaseId + "&api-version=6.1-preview.2"
                Invoke-RestMethod -Uri $deleteLeaseUrl -Method DELETE -Body "{}"  -ContentType "application/json"  -Headers $header
            }
        }
     

        Invoke-RestMethod -Uri $url -Method Get -Headers $header
        Invoke-RestMethod -Uri $url -Method DELETE  -Headers $header
    }
Related