utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 (177 lines of code) (raw):
<#
.SYNOPSIS
Remove a specific resource
.DESCRIPTION
Remove a specific resource. Tries to handle different resource types accordingly
.PARAMETER ResourceId
Mandatory. The resourceID of the resource to remove
.PARAMETER Type
Mandatory. The type of the resource to remove
.EXAMPLE
Invoke-ResourceRemoval -Type 'Microsoft.Insights/diagnosticSettings' -ResourceId '/subscriptions/.../resourceGroups/validation-rg/providers/Microsoft.Network/networkInterfaces/sxx-vm-linux-001-nic-01/providers/Microsoft.Insights/diagnosticSettings/sxx-vm-linux-001-nic-01-diagnosticSettings'
Remove the resource 'sxx-vm-linux-001-nic-01-diagnosticSettings' of type 'Microsoft.Insights/diagnosticSettings' from resource '/subscriptions/.../resourceGroups/validation-rg/providers/Microsoft.Network/networkInterfaces/sxx-vm-linux-001-nic-01'
#>
function Invoke-ResourceRemoval {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory = $true)]
[string] $ResourceId,
[Parameter(Mandatory = $true)]
[string] $Type
)
# Load functions
. (Join-Path $PSScriptRoot 'Invoke-ResourceLockRemoval.ps1')
# Remove unhandled resource locks, for cases when the resource
# collection is incomplete, usually due to previous removal failing.
if ($PSCmdlet.ShouldProcess("Possible locks on resource with ID [$ResourceId]", 'Handle')) {
Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type
}
switch ($Type) {
'Microsoft.Insights/diagnosticSettings' {
$parentResourceId = $ResourceId.Split('/providers/{0}' -f $Type)[0]
$resourceName = Split-Path $ResourceId -Leaf
if ($PSCmdlet.ShouldProcess("Diagnostic setting [$resourceName]", 'Remove')) {
$null = Remove-AzDiagnosticSetting -ResourceId $parentResourceId -Name $resourceName
}
break
}
'Microsoft.Authorization/locks' {
if ($PSCmdlet.ShouldProcess("Lock with ID [$ResourceId]", 'Remove')) {
Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type
}
break
}
'Microsoft.KeyVault/vaults/keys' {
$resourceName = Split-Path $ResourceId -Leaf
Write-Verbose ('[/] Skipping resource [{0}] of type [{1}]. Reason: It is handled by different logic.' -f $resourceName, $Type) -Verbose
# Also, we don't want to accidently remove keys of the dependency key vault
break
}
'Microsoft.KeyVault/vaults/accessPolicies' {
$resourceName = Split-Path $ResourceId -Leaf
Write-Verbose ('[/] Skipping resource [{0}] of type [{1}]. Reason: It is handled by different logic.' -f $resourceName, $Type) -Verbose
break
}
'Microsoft.ServiceBus/namespaces/authorizationRules' {
if ((Split-Path $ResourceId '/')[-1] -eq 'RootManageSharedAccessKey') {
Write-Verbose ('[/] Skipping resource [RootManageSharedAccessKey] of type [{0}]. Reason: The Service Bus''s default authorization key cannot be removed' -f $Type) -Verbose
} else {
if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) {
$null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop'
}
}
break
}
'Microsoft.Compute/diskEncryptionSets' {
# Pre-Removal
# -----------
# Remove access policies on key vault
$resourceGroupName = $ResourceId.Split('/')[4]
$resourceName = Split-Path $ResourceId -Leaf
$diskEncryptionSet = Get-AzDiskEncryptionSet -Name $resourceName -ResourceGroupName $resourceGroupName
$keyVaultResourceId = $diskEncryptionSet.ActiveKey.SourceVault.Id
$keyVaultName = Split-Path $keyVaultResourceId -Leaf
$objectId = $diskEncryptionSet.Identity.PrincipalId
if ($PSCmdlet.ShouldProcess(('Access policy [{0}] from key vault [{1}]' -f $objectId, $keyVaultName), 'Remove')) {
$null = Remove-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $objectId
}
# Actual removal
# --------------
if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) {
$null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop'
}
break
}
'Microsoft.RecoveryServices/vaults/backupstorageconfig' {
# Not a 'resource' that can be removed, but represents settings on the RSV. The config is deleted with the RSV
break
}
'Microsoft.Authorization/roleAssignments' {
$idElem = $ResourceId.Split('/')
$scope = $idElem[0..($idElem.Count - 5)] -join '/'
$roleAssignmentsOnScope = Get-AzRoleAssignment -Scope $scope
$null = $roleAssignmentsOnScope | Where-Object { $_.RoleAssignmentId -eq $ResourceId } | Remove-AzRoleAssignment
break
}
'Microsoft.RecoveryServices/vaults' {
# Pre-Removal
# -----------
# Remove protected VMs
if ((Get-AzRecoveryServicesVaultProperty -VaultId $ResourceId).SoftDeleteFeatureState -ne 'Disabled') {
if ($PSCmdlet.ShouldProcess(('Soft-delete on RSV [{0}]' -f $ResourceId), 'Set')) {
$null = Set-AzRecoveryServicesVaultProperty -VaultId $ResourceId -SoftDeleteFeatureState 'Disable'
}
}
$backupItems = Get-AzRecoveryServicesBackupItem -BackupManagementType 'AzureVM' -WorkloadType 'AzureVM' -VaultId $ResourceId
foreach ($backupItem in $backupItems) {
Write-Verbose ('Removing Backup item [{0}] from RSV [{1}]' -f $backupItem.Name, $ResourceId) -Verbose
if ($backupItem.DeleteState -eq 'ToBeDeleted') {
if ($PSCmdlet.ShouldProcess('Soft-deleted backup data removal', 'Undo')) {
$null = Undo-AzRecoveryServicesBackupItemDeletion -Item $backupItem -VaultId $ResourceId -Force
}
}
if ($PSCmdlet.ShouldProcess(('Backup item [{0}] from RSV [{1}]' -f $backupItem.Name, $ResourceId), 'Remove')) {
$null = Disable-AzRecoveryServicesBackupProtection -Item $backupItem -VaultId $ResourceId -RemoveRecoveryPoints -Force
}
}
# Actual removal
# --------------
if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) {
$null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop'
}
break
}
'Microsoft.OperationalInsights/workspaces' {
$resourceGroupName = $ResourceId.Split('/')[4]
$resourceName = Split-Path $ResourceId -Leaf
# Force delete workspace (cannot be recovered)
if ($PSCmdlet.ShouldProcess("Log Analytics Workspace [$resourceName]", 'Remove')) {
Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose
$null = Remove-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $resourceName -Force -ForceDelete
}
break
}
'Microsoft.MachineLearningServices/workspaces' {
$subscriptionId = $ResourceId.Split('/')[2]
$resourceGroupName = $ResourceId.Split('/')[4]
$resourceName = Split-Path $ResourceId -Leaf
# Purge service
$purgePath = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-06-01-preview&forceToPurge=true' -f $subscriptionId, $resourceGroupName, $resourceName
$purgeRequestInputObject = @{
Method = 'DELETE'
Path = $purgePath
}
Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose
if ($PSCmdlet.ShouldProcess("Machine Learning Workspace [$resourceName]", 'Purge')) {
$purgeResource = Invoke-AzRestMethod @purgeRequestInputObject
if ($purgeResource.StatusCode -notlike '2*') {
$responseContent = $purgeResource.Content | ConvertFrom-Json
throw ('{0} : {1}' -f $responseContent.error.code, $responseContent.error.message)
}
# Wait for workspace to be purged. If it is not purged it has a chance of being soft-deleted via RG deletion (not purged)
# The consecutive deployments will fail because it is not purged.
$retryCount = 0
$retryLimit = 240
$retryInterval = 15
do {
$retryCount++
if ($retryCount -ge $retryLimit) {
Write-Warning (' [!] Workspace [{0}] was not purged after {1} seconds. Continuing with resource removal.' -f $resourceName, ($retryCount * $retryInterval))
break
}
Write-Verbose (' [⏱️] Waiting {0} seconds for workspace to be purged.' -f $retryInterval) -Verbose
Start-Sleep -Seconds $retryInterval
$workspace = Get-AzMLWorkspace -Name $resourceName -ResourceGroupName $resourceGroupName -SubscriptionId $subscriptionId -ErrorAction SilentlyContinue
$workspaceExists = $workspace.count -gt 0
} while ($workspaceExists)
}
break
}
### CODE LOCATION: Add custom removal action here
Default {
if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) {
$null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop'
}
}
}
}