utilities/pipelines/platform/deploymentRemoval/Clear-SubscriptionDeploymentHistory.ps1 (95 lines of code) (raw):
<#
.SYNOPSIS
Bulk delete all deployments on the given subscription scope
.DESCRIPTION
Bulk delete all deployments on the given subscription scope
.PARAMETER subscriptionId
Optional. The ID of the subscription to remove the deployments from. Defaults to the current context.
.PARAMETER DeploymentStatusToExclude
Optional. The status to exlude from removals. Can be multiple. By default, we exclude any deployment that is in state 'running' or 'failed'.
.PARAMETER maxDeploymentRetentionInDays
Optional. The time to keep deployments with a status to exclude. In other words, if a deployment is in a status to exclude, but older than the threshold, it will be deleted.
.EXAMPLE
Clear-SubscriptionDeploymentHistory -subscriptionId '11111111-1111-1111-1111-111111111111'
Bulk remove all 'non-running' & 'non-failed' deployments from the subscription with ID '11111111-1111-1111-1111-111111111111'
.EXAMPLE
Clear-SubscriptionDeploymentHistory -subscriptionId '11111111-1111-1111-1111-111111111111' -DeploymentStatusToExclude @('running')
Bulk remove all 'non-running' deployments from the subscription with ID '11111111-1111-1111-1111-111111111111'
#>
function Clear-SubscriptionDeploymentHistory {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory = $false)]
[string] $subscriptionId = (Get-AzContext).Subscription.Id,
[Parameter(Mandatory = $false)]
[string[]] $DeploymentStatusToExclude = @('running', 'failed'),
[Parameter(Mandatory = $false)]
[int] $maxDeploymentRetentionInDays = 14
)
# Load helper functions
. (Join-Path (Split-Path $PSScriptRoot) 'helper' 'Split-Array.ps1')
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Enables web response
$deploymentThreshold = (Get-Date).AddDays(-1 * $maxDeploymentRetentionInDays)
# Setting context explicitely in case the principal has permissions on multiple
Write-Verbose ('Setting context to subscription [{0}]' -f $subscriptionId)
$null = Set-AzContext -Subscription $subscriptionId
$getInputObject = @{
Method = 'GET'
Uri = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Resources/deployments?api-version=2020-06-01"
Headers = @{
Authorization = 'Bearer {0}' -f ((Get-AzAccessToken -AsSecureString).Token | ConvertFrom-SecureString -AsPlainText)
}
}
$response = Invoke-RestMethod @getInputObject
if (($response | Get-Member -MemberType 'NoteProperty').Name -notcontains 'value') {
throw ('Fetching deployments failed with error [{0}]' -f ($response | Out-String))
}
Write-Verbose ('Found [{0}] deployments in subscription [{1}]' -f $response.value.Count, $subscriptionId) -Verbose
$relevantDeployments = $response.value | Where-Object {
$_.properties.provisioningState -notin $DeploymentStatusToExclude -or
([DateTime]$_.properties.timestamp) -lt $deploymentThreshold -and
$_.properties.provisioningState -ne 'running' # we should never delete 'running' deployments
}
Write-Verbose ('Filtering [{0}] deployments out as they are in state [{1}] or newer than [{2}] days ({3})' -f ($response.value.Count - $relevantDeployments.Count), ($DeploymentStatusToExclude -join '/'), $maxDeploymentRetentionInDays, $deploymentThreshold.ToString('yyyy-MM-dd')) -Verbose
if (-not $relevantDeployments) {
Write-Verbose ('No deployments for subscription [{0}] found' -f $subscriptionId) -Verbose
return
}
$rawDeploymentChunks = Split-Array -InputArray $relevantDeployments -SplitSize 100
if ($relevantDeployments.Count -le 100) {
$relevantDeploymentChunks = , $rawDeploymentChunks
} else {
$relevantDeploymentChunks = $rawDeploymentChunks
}
Write-Verbose ('Triggering the removal of [{0}] deployments from subscription [{1}]' -f $relevantDeployments.Count, $subscriptionId) -Verbose
foreach ($deployments in $relevantDeploymentChunks) {
$requests = $deployments | ForEach-Object {
@{ httpMethod = 'DELETE'
name = (New-Guid).Guid # Each batch request needs a unique ID
requestHeaderDetails = @{
commandName = 'HubsExtension.Microsoft.Resources/deployments.BulkDelete.execute'
}
url = '/subscriptions/{0}/providers/Microsoft.Resources/deployments/{1}?api-version=2019-08-01' -f $subscriptionId, $_.name
}
}
if ($requests -is [hashtable]) {
$requests = , $requests
}
$removeInputObject = @{
Method = 'POST'
Uri = 'https://management.azure.com/batch?api-version=2020-06-01'
Headers = @{
Authorization = 'Bearer {0}' -f ((Get-AzAccessToken -AsSecureString).Token | ConvertFrom-SecureString -AsPlainText)
'Content-Type' = 'application/json'
}
Body = @{
requests = $requests
} | ConvertTo-Json -Depth 4 -EnumsAsStrings
}
if ($PSCmdlet.ShouldProcess(('Removal of [{0}] deployments' -f $requests.Count), 'Request')) {
$null = Invoke-RestMethod @removeInputObject
}
}
Write-Verbose 'Script execution finished. Note that the removal can take a few minutes to propagate.' -Verbose
}