From 793e0e594c2c411b3dd9110026ff2d59c83a663c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 16:31:47 +0200 Subject: [PATCH 01/26] Update custom publishing resources --- settings.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.yml b/settings.yml index 04bdbab53d..92df91fe46 100644 --- a/settings.yml +++ b/settings.yml @@ -59,7 +59,7 @@ variables: # ---------------------- # templateSpecsDoPublish: true # Set to true, if you would like to publish module templates as template specs - templateSpecsRGName: 'artifacts-rg' # The name of the resource group to publish to. If the resource group does not exist, it will be created. + templateSpecsRGName: 'templateSpecs-rg' # The name of the resource group to publish to. If the resource group does not exist, it will be created. templateSpecsRGLocation: 'West Europe' # The location of the resource group to publish to templateSpecsDescription: components # The description to add to template specs published by this platform @@ -68,8 +68,8 @@ variables: # ------------------------------- # bicepRegistryDoPublish: true # Set to true, if you would like to publish module templates to a bicep registry - bicepRegistryName: adpsxxazacrx001 # The name of the bicep registry (ACR) to publish to. If it does not exist, it will be created. - bicepRegistryRGName: 'artifacts-rg' # The resource group that hosts the private bicep registry (ACR) + bicepRegistryName: 'msxcr' # The name of the bicep registry (ACR) to publish to. If it does not exist, it will be created. + bicepRegistryRGName: 'registry-rg' # The resource group that hosts the private bicep registry (ACR) bicepRegistryRgLocation: 'West Europe' # The location of the resource group to publish to ########################################################################################################################### From 38e2cf978f90c5a98c630f11fdb5f3d403f64ff6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 20:45:19 +0200 Subject: [PATCH 02/26] Add script for purging machine learning workspace --- .../helper/Invoke-ResourcePostRemoval.ps1 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 index d158ba6b94..b29148cdb4 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 @@ -149,6 +149,22 @@ function Invoke-ResourcePostRemoval { $null = Set-AzRecoveryServicesVaultProperty -VaultId $vaultId -SoftDeleteFeatureState $softDeleteStatus.TrimEnd('d') 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/{workspaceName}?api-version=2023-06-01-preview&forceToPurge=true' -f $subscriptionId, $resourceGroupName,$resourceName + $purgeRequestInputObject = @{ + Method = 'DELETE' + Path = $purgePath + } + if ($PSCmdlet.ShouldProcess(('API management service with ID [{0}]' -f $softDeletedService.properties.serviceId), 'Purge')) { + $null = Invoke-AzRestMethod @purgeRequestInputObject + } + break + } ### CODE LOCATION: Add custom post-removal operation here } } From 076fe87c1da8376f36b4170c9839da182966b649 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 20:50:39 +0200 Subject: [PATCH 03/26] reset settings --- settings.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.yml b/settings.yml index 92df91fe46..04bdbab53d 100644 --- a/settings.yml +++ b/settings.yml @@ -59,7 +59,7 @@ variables: # ---------------------- # templateSpecsDoPublish: true # Set to true, if you would like to publish module templates as template specs - templateSpecsRGName: 'templateSpecs-rg' # The name of the resource group to publish to. If the resource group does not exist, it will be created. + templateSpecsRGName: 'artifacts-rg' # The name of the resource group to publish to. If the resource group does not exist, it will be created. templateSpecsRGLocation: 'West Europe' # The location of the resource group to publish to templateSpecsDescription: components # The description to add to template specs published by this platform @@ -68,8 +68,8 @@ variables: # ------------------------------- # bicepRegistryDoPublish: true # Set to true, if you would like to publish module templates to a bicep registry - bicepRegistryName: 'msxcr' # The name of the bicep registry (ACR) to publish to. If it does not exist, it will be created. - bicepRegistryRGName: 'registry-rg' # The resource group that hosts the private bicep registry (ACR) + bicepRegistryName: adpsxxazacrx001 # The name of the bicep registry (ACR) to publish to. If it does not exist, it will be created. + bicepRegistryRGName: 'artifacts-rg' # The resource group that hosts the private bicep registry (ACR) bicepRegistryRgLocation: 'West Europe' # The location of the resource group to publish to ########################################################################################################################### From 0c5896b866a056c05209b1ff73c177e95652d897 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 21:05:49 +0200 Subject: [PATCH 04/26] remove double space --- .../pipelines/resourceRemoval/helper/Remove-Deployment.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 b/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 index ee1316357d..5c27684c08 100644 --- a/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 @@ -110,7 +110,7 @@ function Remove-Deployment { # Pre-Filter & order items # ======================== $rawTargetResourceIdsToRemove = $deployedTargetResources | Sort-Object -Property { $_.Split('/').Count } -Descending | Select-Object -Unique - Write-Verbose ('Total number of deployment target resources after pre-filtering (duplicates) & ordering items [{0}]' -f $rawTargetResourceIdsToRemove.Count) -Verbose + Write-Verbose ('Total number of deployment target resources after pre-filtering (duplicates) & ordering items [{0}]' -f $rawTargetResourceIdsToRemove.Count) -Verbose # Format items # ============ From fd73e7c63ff6f0dde01de335cb6d004b58644d4f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 21:14:00 +0200 Subject: [PATCH 05/26] fix attempt #1 --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 3885febb63..b06c4269f3 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -154,13 +154,14 @@ function Invoke-ResourceRemoval { $subscriptionId = $resourceId.Split('/')[2] $resourceGroupName = $resourceId.Split('/')[4] $resourceName = Split-Path $resourceId -Leaf - $purgePath = 'subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-04-01-preview&forceToPurge={3}' -f $subscriptionId, $resourceGroupName, $resourceName, $true + $purgePath = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-04-01-preview&forceToPurge={3}' -f $subscriptionId, $resourceGroupName, $resourceName, $true $purgeRequestInputObject = @{ Method = 'DELETE' Path = $purgePath } if ($PSCmdlet.ShouldProcess("Machine Learning Workspace [$resourceName]", 'Remove')) { - $null = Invoke-AzRestMethod @purgeRequestInputObject + #$null = Invoke-AzRestMethod @purgeRequestInputObject + Invoke-AzRestMethod @purgeRequestInputObject } break } From 03c29399242af4c7754aeb5eb4d7e6510cf0a622 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 21:52:04 +0200 Subject: [PATCH 06/26] correct spelling of retry --- .../pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 index 924c308db6..e418beaaf5 100644 --- a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 @@ -54,7 +54,7 @@ function Remove-ResourceListInner { [array]$processedResources += $resource.resourceId [array]$resourcesToRetry = $resourcesToRetry | Where-Object { $_.resourceId -notmatch $resource.resourceId } } catch { - Write-Warning ('Removal moved back for re-try. Reason: [{0}]' -f $_.Exception.Message) + Write-Warning ('Removal moved back for retry. Reason: [{0}]' -f $_.Exception.Message) [array]$resourcesToRetry += $resource } } @@ -115,7 +115,7 @@ function Remove-ResourceList { if (-not $resourcesToRetry) { break } - Write-Verbose ('Re-try removal of remaining [{0}] resources. Waiting [{1}] seconds. Round [{2}|{3}]' -f (($resourcesToRetry -is [array]) ? $resourcesToRetry.Count : 1), $removalRetryInterval, $removalRetryCount, $removalRetryLimit) + Write-Verbose ('Retry removal of remaining [{0}] resources. Waiting [{1}] seconds. Round [{2}|{3}]' -f (($resourcesToRetry -is [array]) ? $resourcesToRetry.Count : 1), $removalRetryInterval, $removalRetryCount, $removalRetryLimit) $removalRetryCount++ Start-Sleep $removalRetryInterval } while ($removalRetryCount -le $removalRetryLimit) From 583a976b301f17ccd0c320fbd2624c89b6cf7b0e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 20 Aug 2023 22:10:33 +0200 Subject: [PATCH 07/26] align with Invoke-ResourceRemoval.ps1 --- .../resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 index b29148cdb4..762f3cc837 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 @@ -152,15 +152,13 @@ function Invoke-ResourcePostRemoval { '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/{workspaceName}?api-version=2023-06-01-preview&forceToPurge=true' -f $subscriptionId, $resourceGroupName,$resourceName + $resourceName = Split-Path $resourceId -Leaf + $purgePath = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-04-01-preview&forceToPurge={3}' -f $subscriptionId, $resourceGroupName, $resourceName, $true $purgeRequestInputObject = @{ Method = 'DELETE' Path = $purgePath } - if ($PSCmdlet.ShouldProcess(('API management service with ID [{0}]' -f $softDeletedService.properties.serviceId), 'Purge')) { + if ($PSCmdlet.ShouldProcess("Machine Learning Workspace [$resourceName]", 'Remove')) { $null = Invoke-AzRestMethod @purgeRequestInputObject } break From bf37cd553bd2cb4458374bd5ad47b52fe924fe77 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 21 Aug 2023 20:35:11 +0200 Subject: [PATCH 08/26] fix --- .../helper/Invoke-ResourcePostRemoval.ps1 | 35 ++-- .../helper/Invoke-ResourceRemoval.ps1 | 177 ++++++++++++++---- .../helper/Remove-Deployment.ps1 | 22 +-- .../helper/Remove-ResourceList.ps1 | 4 +- 4 files changed, 156 insertions(+), 82 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 index 762f3cc837..7a4178a3e0 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourcePostRemoval.ps1 @@ -27,9 +27,9 @@ function Invoke-ResourcePostRemoval { [string] $Type ) - switch ($type) { + switch ($Type) { 'Microsoft.AppConfiguration/configurationStores' { - $subscriptionId = $resourceId.Split('/')[2] + $subscriptionId = $ResourceId.Split('/')[2] $resourceName = Split-Path $ResourceId -Leaf # Fetch service in soft-delete @@ -38,7 +38,7 @@ function Invoke-ResourcePostRemoval { Method = 'GET' Path = $getPath } - $softDeletedConfigurationStore = ((Invoke-AzRestMethod @getRequestInputObject).Content | ConvertFrom-Json).value | Where-Object { $_.properties.configurationStoreId -eq $resourceId } + $softDeletedConfigurationStore = ((Invoke-AzRestMethod @getRequestInputObject).Content | ConvertFrom-Json).value | Where-Object { $_.properties.configurationStoreId -eq $ResourceId } if ($softDeletedConfigurationStore) { # Purge service @@ -47,6 +47,7 @@ function Invoke-ResourcePostRemoval { Method = 'POST' Path = $purgePath } + Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose if ($PSCmdlet.ShouldProcess(('App Configuration Store with ID [{0}]' -f $softDeletedConfigurationStore.properties.configurationStoreId), 'Purge')) { $response = Invoke-AzRestMethod @purgeRequestInputObject if ($response.StatusCode -ne 200) { @@ -61,7 +62,7 @@ function Invoke-ResourcePostRemoval { $matchingKeyVault = Get-AzKeyVault -InRemovedState | Where-Object { $_.resourceId -eq $ResourceId } if ($matchingKeyVault -and -not $matchingKeyVault.EnablePurgeProtection) { - Write-Verbose ("Purging key vault [$resourceName]") -Verbose + Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose if ($PSCmdlet.ShouldProcess(('Key Vault with ID [{0}]' -f $matchingKeyVault.Id), 'Purge')) { try { $null = Remove-AzKeyVault -ResourceId $matchingKeyVault.Id -InRemovedState -Force -Location $matchingKeyVault.Location -ErrorAction 'Stop' @@ -77,11 +78,12 @@ function Invoke-ResourcePostRemoval { break } 'Microsoft.CognitiveServices/accounts' { - $resourceGroupName = $resourceId.Split('/')[4] + $resourceGroupName = $ResourceId.Split('/')[4] $resourceName = Split-Path $ResourceId -Leaf $matchingAccount = Get-AzCognitiveServicesAccount -InRemovedState | Where-Object { $_.AccountName -eq $resourceName } if ($matchingAccount) { + Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose if ($PSCmdlet.ShouldProcess(('Cognitive services account with ID [{0}]' -f $matchingAccount.Id), 'Purge')) { $null = Remove-AzCognitiveServicesAccount -InRemovedState -Force -Location $matchingAccount.Location -ResourceGroupName $resourceGroupName -Name $matchingAccount.AccountName } @@ -89,7 +91,7 @@ function Invoke-ResourcePostRemoval { break } 'Microsoft.ApiManagement/service' { - $subscriptionId = $resourceId.Split('/')[2] + $subscriptionId = $ResourceId.Split('/')[2] $resourceName = Split-Path $ResourceId -Leaf # Fetch service in soft-delete @@ -98,7 +100,7 @@ function Invoke-ResourcePostRemoval { Method = 'GET' Path = $getPath } - $softDeletedService = ((Invoke-AzRestMethod @getRequestInputObject).Content | ConvertFrom-Json).value | Where-Object { $_.properties.serviceId -eq $resourceId } + $softDeletedService = ((Invoke-AzRestMethod @getRequestInputObject).Content | ConvertFrom-Json).value | Where-Object { $_.properties.serviceId -eq $ResourceId } if ($softDeletedService) { # Purge service @@ -107,6 +109,7 @@ function Invoke-ResourcePostRemoval { Method = 'DELETE' Path = $purgePath } + Write-Verbose ('[*] Purging resource [{0}] of type [{1}]' -f $resourceName, $Type) -Verbose if ($PSCmdlet.ShouldProcess(('API management service with ID [{0}]' -f $softDeletedService.properties.serviceId), 'Purge')) { $null = Invoke-AzRestMethod @purgeRequestInputObject } @@ -116,7 +119,7 @@ function Invoke-ResourcePostRemoval { 'Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems' { # Remove protected VM # Required if e.g. a VM was listed in an RSV and only that VM is removed - $vaultId = $resourceId.split('/backupFabrics/')[0] + $vaultId = $ResourceId.split('/backupFabrics/')[0] $resourceName = Split-Path $ResourceId -Leaf $softDeleteStatus = (Get-AzRecoveryServicesVaultProperty -VaultId $vaultId).SoftDeleteFeatureState if ($softDeleteStatus -ne 'Disabled') { @@ -132,7 +135,7 @@ function Invoke-ResourcePostRemoval { Name = $resourceName } if ($backupItem = Get-AzRecoveryServicesBackupItem @backupItemInputObject -ErrorAction 'SilentlyContinue') { - Write-Verbose ('Removing Backup item [{0}] from RSV [{1}]' -f $backupItem.Name, $vaultId) -Verbose + Write-Verbose (' [-] Removing Backup item [{0}] from RSV [{1}]' -f $backupItem.Name, $vaultId) -Verbose if ($backupItem.DeleteState -eq 'ToBeDeleted') { if ($PSCmdlet.ShouldProcess('Soft-deleted backup data removal', 'Undo')) { @@ -149,20 +152,6 @@ function Invoke-ResourcePostRemoval { $null = Set-AzRecoveryServicesVaultProperty -VaultId $vaultId -SoftDeleteFeatureState $softDeleteStatus.TrimEnd('d') break } - 'Microsoft.MachineLearningServices/workspaces' { - $subscriptionId = $resourceId.Split('/')[2] - $resourceGroupName = $resourceId.Split('/')[4] - $resourceName = Split-Path $resourceId -Leaf - $purgePath = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-04-01-preview&forceToPurge={3}' -f $subscriptionId, $resourceGroupName, $resourceName, $true - $purgeRequestInputObject = @{ - Method = 'DELETE' - Path = $purgePath - } - if ($PSCmdlet.ShouldProcess("Machine Learning Workspace [$resourceName]", 'Remove')) { - $null = Invoke-AzRestMethod @purgeRequestInputObject - } - break - } ### CODE LOCATION: Add custom post-removal operation here } } diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index b06c4269f3..49a971908c 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -1,5 +1,63 @@ <# .SYNOPSIS +Remove unhandled delete locks from a resource. + +.DESCRIPTION +Remove unhandled delete locks from a resource. If the resource is locked for deletion, the lock is removed. + +.PARAMETER ResourceId +Mandatory. The resourceID of the resource to check, and remove the lock from if it is locked for deletion. + +.PARAMETER RetryLimit +The number of times to retry checking if the lock is removed. + +.PARAMETER RetryInterval +The number of seconds to wait between each retry. + +.EXAMPLE +Remove-UnhandledDeleteLock -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' + +Check if the resource 'resource-name' is locked for deletion. If it is, remove the lock. +#> +function Remove-UnhandledDeleteLock { + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true)] + [string] $ResourceId, + + [int] $RetryLimit = 10, + + [int] $RetryInterval = 10 + ) + + $deleteLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue | Where-Object { $_.Properties.level -eq 'CanNotDelete' } + $isDeleteLocked = $deleteLock.count -gt 0 + + if (-not $isDeleteLocked) { + return + } + + Write-Warning (' [-] 🔒 Unhandled delete lock detected. Removing lock.' -f $ResourceId) + $null = $deleteLocks | Remove-AzResourceLock -Force + + $retryCount = 0 + do { + $retryCount++ + if ($retryCount -ge $RetryLimit) { + Write-Warning (' [!] Lock was not removed after {1} seconds. Continuing with resource removal.' -f ($retryCount * $RetryInterval)) + return + } + Write-Verbose ' [⏱️] Waiting for lock to be removed.' -Verbose + Start-Sleep -Seconds $RetryInterval + $deleteLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue | Where-Object { $_.Properties.level -eq 'CanNotDelete' } + $isDeleteLocked = $deleteLock.count -gt 0 + } while ($isDeleteLocked) + return +} + + +<# +.SYNOPSIS Remove a specific resource .DESCRIPTION @@ -27,9 +85,11 @@ function Invoke-ResourceRemoval { [string] $Type ) - switch ($type) { + Remove-UnhandledDeleteLock -ResourceId $ResourceId + + switch ($Type) { 'Microsoft.Insights/diagnosticSettings' { - $parentResourceId = $resourceId.Split('/providers/{0}' -f $type)[0] + $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 @@ -37,28 +97,42 @@ function Invoke-ResourceRemoval { break } 'Microsoft.Authorization/locks' { - $lockName = ($resourceId -split '/')[-1] - $lockScope = ($resourceId -split '/providers/Microsoft.Authorization/locks')[0] + $lockName = ($ResourceId -split '/')[-1] + $lockScope = ($ResourceId -split '/providers/Microsoft.Authorization/locks')[0] $null = Remove-AzResourceLock -LockName $lockName -Scope $lockScope -Force - Write-Verbose "Removed lock [$resourceName]. Waiting 10 seconds for propagation." -Verbose - Start-Sleep 10 + + $retryCount = 0 + $retryLimit = 10 + $retryInterval = 10 + do { + $retryCount++ + if ($retryCount -ge $retryLimit) { + Write-Warning (' [!] Lock [{0}] was not removed after {1} seconds. Continuing with resource removal.' -f $lockName, ($retryCount * $retryInterval)) + break + } + Write-Verbose (' [⏱️] Waiting for lock [{0}] to be removed.' -f $lockName) -Verbose + Start-Sleep -Seconds $retryInterval + $lockExists = Get-AzResourceLock -LockName $lockName -Scope $lockScope -ErrorAction SilentlyContinue + } while ($lockExists) break } 'Microsoft.KeyVault/vaults/keys' { - Write-Verbose ('Skip resource removal for type [{0}]. Reason: handled by different logic.' -f $type) -Verbose + $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' { - Write-Verbose ('Skip resource removal for type [{0}]. Reason: handled by different logic.' -f $type) -Verbose + $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 ('Skip resource removal for type [{0}]. Reason: The Service Bus''s default authorization key [RootManageSharedAccessKey] cannot be removed.' -f $type) -Verbose + Write-Verbose ('[/] Skipping resource [RootManageSharedAccessKey] of type [{0}]. Reason: The Service Bus''s default authorization key cannot be removed' -f $Type) -Verbose } else { - $null = Remove-AzResource -ResourceId $resourceId -Force -ErrorAction 'Stop' + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' } break } @@ -66,23 +140,23 @@ function Invoke-ResourceRemoval { # Pre-Removal # ----------- # Remove access policies on key vault - $resourceGroupName = $resourceId.Split('/')[4] - $resourceName = Split-Path $resourceId -Leaf + $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 - Write-Verbose ('keyVaultResourceId [{0}]' -f $keyVaultResourceId) -Verbose - Write-Verbose ('objectId [{0}]' -f $objectId) -Verbose + Write-Verbose (' keyVaultResourceId [{0}]' -f $keyVaultResourceId) -Verbose + Write-Verbose (' objectId [{0}]' -f $objectId) -Verbose if ($PSCmdlet.ShouldProcess(('Access policy [{0}] from key vault [{1}]' -f $objectId, $keyVaultName), 'Remove')) { $null = Remove-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $objectId } # Actual removal # -------------- - $null = Remove-AzResource -ResourceId $resourceId -Force -ErrorAction 'Stop' + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' break } 'Microsoft.RecoveryServices/vaults/backupstorageconfig' { @@ -100,75 +174,98 @@ function Invoke-ResourceRemoval { # 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' + 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 + $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 + 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 + $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 + 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 # -------------- - $null = Remove-AzResource -ResourceId $resourceId -Force -ErrorAction 'Stop' + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' break } 'Microsoft.OperationalInsights/workspaces' { - $resourceGroupName = $resourceId.Split('/')[4] - $resourceName = Split-Path $resourceId -Leaf + $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.DevTestLab/labs' { - $resourceGroupName = $resourceId.Split('/')[4] - $resourceName = Split-Path $resourceId -Leaf + $resourceGroupName = $ResourceId.Split('/')[4] + $resourceName = Split-Path $ResourceId -Leaf if ($PSCmdlet.ShouldProcess("DevTestLab Lab [$resourceName]", 'Remove')) { Get-AzResourceLock -ResourceGroupName $resourceGroupName | Where-Object -FilterScript { $PSItem.properties.notes -eq "Reserved resource locked by '$resourceName' lab." } | ForEach-Object { $null = Remove-AzResourceLock -LockId $PSItem.LockId -Force - Write-Verbose "Removed lock [$($PSItem.Name)] created by the DevTest Lab [$resourceName] on resource [$($PSItem.ResourceName)]. Waiting 10 seconds for propagation." -Verbose - Start-Sleep 10 + Write-Verbose " [⏱️] Removed lock [$($PSItem.Name)] created by the DevTest Lab [$resourceName] on resource [$($PSItem.ResourceName)]. Waiting 10 seconds for propagation." -Verbose + Start-Sleep -Seconds 10 } - $null = Remove-AzResource -ResourceId $resourceId -Force -ErrorAction 'Stop' + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' } break } 'Microsoft.MachineLearningServices/workspaces' { - $subscriptionId = $resourceId.Split('/')[2] - $resourceGroupName = $resourceId.Split('/')[4] - $resourceName = Split-Path $resourceId -Leaf - $purgePath = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.MachineLearningServices/workspaces/{2}?api-version=2023-04-01-preview&forceToPurge={3}' -f $subscriptionId, $resourceGroupName, $resourceName, $true + $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 } - if ($PSCmdlet.ShouldProcess("Machine Learning Workspace [$resourceName]", 'Remove')) { - #$null = Invoke-AzRestMethod @purgeRequestInputObject - Invoke-AzRestMethod @purgeRequestInputObject + 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 { - $null = Remove-AzResource -ResourceId $resourceId -Force -ErrorAction 'Stop' + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' } } } diff --git a/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 b/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 index 5c27684c08..b277c49fb0 100644 --- a/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Remove-Deployment.ps1 @@ -15,12 +15,6 @@ Optional. The resource group of the resource to remove .PARAMETER ManagementGroupId Optional. The ID of the management group to fetch deployments from. Relevant for management-group level deployments. -.PARAMETER SearchRetryLimit -Optional. The maximum times to retry the search for resources via their removal tag - -.PARAMETER SearchRetryInterval -Optional. The time to wait in between the search for resources via their remove tags - .PARAMETER DeploymentNames Optional. The deployment names to use for the removal @@ -52,13 +46,7 @@ function Remove-Deployment { [string] $TemplateFilePath, [Parameter(Mandatory = $false)] - [string[]] $RemovalSequence = @(), - - [Parameter(Mandatory = $false)] - [int] $SearchRetryLimit = 40, - - [Parameter(Mandatory = $false)] - [int] $SearchRetryInterval = 60 + [string[]] $RemovalSequence = @() ) begin { @@ -80,7 +68,7 @@ function Remove-Deployment { $deploymentScope = Get-ScopeOfTemplateFile -TemplateFilePath $TemplateFilePath # Fundamental checks - if ($deploymentScope -eq 'resourcegroup' -and -not (Get-AzResourceGroup -Name $resourceGroupName -ErrorAction 'SilentlyContinue')) { + if ($deploymentScope -eq 'resourcegroup' -and -not (Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction 'SilentlyContinue')) { Write-Verbose "Resource group [$ResourceGroupName] does not exist (anymore). Skipping removal of its contained resources" -Verbose return } @@ -94,8 +82,8 @@ function Remove-Deployment { Name = $deploymentName Scope = $deploymentScope } - if (-not [String]::IsNullOrEmpty($resourceGroupName)) { - $deploymentsInputObject['resourceGroupName'] = $resourceGroupName + if (-not [String]::IsNullOrEmpty($ResourceGroupName)) { + $deploymentsInputObject['resourceGroupName'] = $ResourceGroupName } if (-not [String]::IsNullOrEmpty($ManagementGroupId)) { $deploymentsInputObject['ManagementGroupId'] = $ManagementGroupId @@ -138,7 +126,7 @@ function Remove-Deployment { '/subscriptions/{0}/providers/Microsoft.Security/securityContacts/' -f $azContext.Subscription.Id '/subscriptions/{0}/providers/Microsoft.Security/workspaceSettings/' -f $azContext.Subscription.Id ) - [regex] $ignorePrefix_regex = '(?i)^(' + (($resourceIdPrefixesToIgnore | ForEach-Object { [regex]::escape($_) }) –join '|') + ')' + [regex] $ignorePrefix_regex = '(?i)^(' + (($resourceIdPrefixesToIgnore | ForEach-Object { [regex]::escape($_) }) -join '|') + ')' if ($resourcesToIgnore = $resourcesToRemove | Where-Object { $_.resourceId -in $resourceIdsToIgnore -or $_.resourceId -match $ignorePrefix_regex }) { diff --git a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 index e418beaaf5..c4b00c352b 100644 --- a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 @@ -40,7 +40,7 @@ function Remove-ResourceListInner { if ($alreadyProcessed) { # Skipping - Write-Verbose ('[/] Skipping resource [{0}] of type [{1}] as a parent resource was already processed' -f $resourceName, $resource.type) -Verbose + Write-Verbose ('[/] Skipping resource [{0}] of type [{1}]. Reason: Its parent resource was already processed' -f $resourceName, $resource.type) -Verbose [array]$processedResources += $resource.resourceId [array]$resourcesToRetry = $resourcesToRetry | Where-Object { $_.resourceId -notmatch $resource.resourceId } } else { @@ -54,7 +54,7 @@ function Remove-ResourceListInner { [array]$processedResources += $resource.resourceId [array]$resourcesToRetry = $resourcesToRetry | Where-Object { $_.resourceId -notmatch $resource.resourceId } } catch { - Write-Warning ('Removal moved back for retry. Reason: [{0}]' -f $_.Exception.Message) + Write-Warning ('[!] Removal moved back for retry. Reason: [{0}]' -f $_.Exception.Message) [array]$resourcesToRetry += $resource } } From 91c3f5352ff8cf7fe0d41c20270c3f0cd8af2c84 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Tue, 22 Aug 2023 00:04:35 +0200 Subject: [PATCH 09/26] Reset files --- .../jobs.validateModuleDeployment.yml | 1 + .../templates/setEnvironment/action.yml | 1 + .../helper/Invoke-ResourceRemoval.ps1 | 50 ++++++++++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml b/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml index 3efdb4152c..b804f7dd75 100644 --- a/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml +++ b/.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml @@ -105,6 +105,7 @@ jobs: @{ Name = 'Az.CognitiveServices' }, @{ Name = 'Az.Compute' }, @{ Name = 'Az.KeyVault' }, + @{ Name = 'Az.MachineLearningServices' }, @{ Name = 'Az.Monitor' }, @{ Name = 'Az.OperationalInsights' }, @{ Name = 'Az.RecoveryServices' } diff --git a/.github/actions/templates/setEnvironment/action.yml b/.github/actions/templates/setEnvironment/action.yml index 573d99ccb9..4469424d0e 100644 --- a/.github/actions/templates/setEnvironment/action.yml +++ b/.github/actions/templates/setEnvironment/action.yml @@ -62,6 +62,7 @@ runs: @{ Name = 'Az.CognitiveServices' }, @{ Name = 'Az.Compute' }, @{ Name = 'Az.KeyVault' }, + @{ Name = 'Az.MachineLearningServices' }, @{ Name = 'Az.Monitor' }, @{ Name = 'Az.OperationalInsights' }, @{ Name = 'Az.RecoveryServices' } diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 49a971908c..2a82b37b3b 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -1,12 +1,12 @@ <# .SYNOPSIS -Remove unhandled delete locks from a resource. +Remove unhandled resource locks from a resource. .DESCRIPTION -Remove unhandled delete locks from a resource. If the resource is locked for deletion, the lock is removed. +Remove unhandled resource locks from a resource. If the resource is locked, the lock is removed. .PARAMETER ResourceId -Mandatory. The resourceID of the resource to check, and remove the lock from if it is locked for deletion. +Mandatory. The resourceID of the resource to check, and remove the lock from if it is locked. .PARAMETER RetryLimit The number of times to retry checking if the lock is removed. @@ -15,11 +15,11 @@ The number of times to retry checking if the lock is removed. The number of seconds to wait between each retry. .EXAMPLE -Remove-UnhandledDeleteLock -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' +Remove-UnhandledResourceLock -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' -Check if the resource 'resource-name' is locked for deletion. If it is, remove the lock. +Check if the resource 'resource-name' is locked. If it is, remove the lock. #> -function Remove-UnhandledDeleteLock { +function Remove-UnhandledResourceLock { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -30,28 +30,30 @@ function Remove-UnhandledDeleteLock { [int] $RetryInterval = 10 ) - $deleteLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue | Where-Object { $_.Properties.level -eq 'CanNotDelete' } - $isDeleteLocked = $deleteLock.count -gt 0 + $resourceLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue + $isLocked = $resourceLock.count -gt 0 - if (-not $isDeleteLocked) { + if (-not $isLocked) { return } - Write-Warning (' [-] 🔒 Unhandled delete lock detected. Removing lock.' -f $ResourceId) - $null = $deleteLocks | Remove-AzResourceLock -Force + Write-Warning (' [-] 🔒 Unhandled resource lock detected. Removing lock.' -f $ResourceId) + if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { + $null = $resourceLock | Remove-AzResourceLock -Force - $retryCount = 0 - do { - $retryCount++ - if ($retryCount -ge $RetryLimit) { - Write-Warning (' [!] Lock was not removed after {1} seconds. Continuing with resource removal.' -f ($retryCount * $RetryInterval)) - return - } - Write-Verbose ' [⏱️] Waiting for lock to be removed.' -Verbose - Start-Sleep -Seconds $RetryInterval - $deleteLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue | Where-Object { $_.Properties.level -eq 'CanNotDelete' } - $isDeleteLocked = $deleteLock.count -gt 0 - } while ($isDeleteLocked) + $retryCount = 0 + do { + $retryCount++ + if ($retryCount -ge $RetryLimit) { + Write-Warning (' [!] Lock was not removed after {1} seconds. Continuing with resource removal.' -f ($retryCount * $RetryInterval)) + return + } + Write-Verbose ' [⏱️] Waiting for lock to be removed.' -Verbose + Start-Sleep -Seconds $RetryInterval + $resourceLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue + $isLocked = $resourceLock.count -gt 0 + } while ($isLocked) + } return } @@ -85,7 +87,7 @@ function Invoke-ResourceRemoval { [string] $Type ) - Remove-UnhandledDeleteLock -ResourceId $ResourceId + Remove-UnhandledResourceLock -ResourceId $ResourceId switch ($Type) { 'Microsoft.Insights/diagnosticSettings' { From d4de9cd4be77cbe2b505a160843fedf863a590d6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 31 Aug 2023 20:57:47 +0200 Subject: [PATCH 10/26] Update utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 Co-authored-by: Alexander Sehr --- .../pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 index c4b00c352b..cc35460c8a 100644 --- a/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Remove-ResourceList.ps1 @@ -40,7 +40,7 @@ function Remove-ResourceListInner { if ($alreadyProcessed) { # Skipping - Write-Verbose ('[/] Skipping resource [{0}] of type [{1}]. Reason: Its parent resource was already processed' -f $resourceName, $resource.type) -Verbose + Write-Verbose ('[/] Skipping resource [{0}] of type [{1}]. Reason: Its parent resource was already processed' -f $resourceName, $resource.type) -Verbose [array]$processedResources += $resource.resourceId [array]$resourcesToRetry = $resourcesToRetry | Where-Object { $_.resourceId -notmatch $resource.resourceId } } else { From 686e595d6f4b7fe1e4ce0b103f0f6752b318ef74 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 31 Aug 2023 20:58:04 +0200 Subject: [PATCH 11/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 Co-authored-by: Alexander Sehr --- .../pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 2a82b37b3b..9ae63db384 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -150,8 +150,6 @@ function Invoke-ResourceRemoval { $keyVaultName = Split-Path $keyVaultResourceId -Leaf $objectId = $diskEncryptionSet.Identity.PrincipalId - Write-Verbose (' keyVaultResourceId [{0}]' -f $keyVaultResourceId) -Verbose - Write-Verbose (' objectId [{0}]' -f $objectId) -Verbose if ($PSCmdlet.ShouldProcess(('Access policy [{0}] from key vault [{1}]' -f $objectId, $keyVaultName), 'Remove')) { $null = Remove-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $objectId } From 9f5d68c235cefe0ea81848a1ecd337655c17db28 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 09:56:45 +0200 Subject: [PATCH 12/26] Restructure --- .../helper/Invoke-ResourceLockRemoval.ps1 | 74 ++++++++++++++++ .../helper/Invoke-ResourceLockRetrieval.ps1 | 45 ++++++++++ .../helper/Invoke-ResourceRemoval.ps1 | 88 ++----------------- 3 files changed, 126 insertions(+), 81 deletions(-) create mode 100644 utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 create mode 100644 utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRetrieval.ps1 diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 new file mode 100644 index 0000000000..63fe9675c0 --- /dev/null +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS +Remove resource locks from a resource or a specific resource lock. + +.DESCRIPTION +Remove resource locks from a resource or a specific resource lock. + +.PARAMETER ResourceId +Mandatory. The resourceID of the resource to check, and remove a resource lock from. + +.PARAMETER Type +Optional. The type of the resource. If the resource is a lock, the lock itself will be removed. If the resource is a resource, all locks on the resource will be removed. If not specified, the resource will be checked for locks, and if any are found, all locks will be removed. + +.PARAMETER RetryLimit +Optional. The number of times to retry checking if the lock is removed. + +.PARAMETER RetryInterval +Optional. The number of seconds to wait between each retry. + +.EXAMPLE +Invoke-ResourceLockRemoval -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' + +Check if the resource 'resource-name' is locked. If it is, remove the lock. +#> +function Invoke-ResourceLockRemoval { + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true)] + [string] $ResourceId, + + [Parameter(Mandatory = $false)] + [string] $Type, + + [Parameter(Mandatory = $false)] + [int] $RetryLimit = 10, + + [Parameter(Mandatory = $false)] + [int] $RetryInterval = 10 + ) + # Load functions + . (Join-Path $PSScriptRoot 'Invoke-ResourceLockRetrieval.ps1') + + $resourceLock = Invoke-ResourceLockRetrieval -ResourceId $ResourceId -Type $Type + + $isLocked = $resourceLock.count -gt 0 + if (-not $isLocked) { + return + } + + if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { + if ($Type -ne 'Microsoft.Authorization/locks') { + Write-Warning (' [-] 🔒 Unmanaged resource lock detected. Removing.' -f $ResourceId) + } + $null = $resourceLock | Remove-AzResourceLock -Force + } + + $retryCount = 0 + do { + $retryCount++ + if ($retryCount -ge $RetryLimit) { + Write-Warning (' [!] Lock was not removed after {1} seconds. Continuing with resource removal.' -f ($retryCount * $RetryInterval)) + break + } + Write-Verbose ' [⏱️] Waiting for lock to be removed.' -Verbose + Start-Sleep -Seconds $RetryInterval + + # Rechecking the resource locks to see if they have been removed. + $resourceLock = Invoke-ResourceLockRetrieval -ResourceId $ResourceId -Type $Type + $isLocked = $resourceLock.count -gt 0 + } while ($isLocked) + Write-Verbose ' [✅] Resource lock removed.' -Verbose + + return +} diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRetrieval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRetrieval.ps1 new file mode 100644 index 0000000000..90c35dd598 --- /dev/null +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRetrieval.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS +Gets resource locks on a resource or a specific resource lock. + +.DESCRIPTION +Gets resource locks on a resource or a specific resource lock. + +.PARAMETER ResourceId +Mandatory. The resourceID of the resource to check or the resource lock to check. + +.PARAMETER Type +Optional. The type of the resource. +If the resource is a lock, the lock itself will be returned. +If the resource is not a lock, all locks on the resource will be returned. + +.EXAMPLE +Invoke-ResourceLockRetrieval -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' + +Check if the resource 'resource-name' is locked. If it is, return the lock. + +.EXAMPLE +Invoke-ResourceLockRetrieval -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name/providers/Microsoft.Authorization/locks/lock-name' -Type 'Microsoft.Authorization/locks' + +Return the lock 'lock-name' on the resource 'resource-name'. + +.NOTES +Needed as the AzPwsh cmdlet Get-AzResourceLock does not support getting a specific lock by LockId. +#> +function Invoke-ResourceLockRetrieval { + [OutputType([System.Management.Automation.PSCustomObject])] + param ( + [Parameter(Mandatory = $true)] + [string] $ResourceId, + + [Parameter(Mandatory = $false)] + [string] $Type = '' + ) + if ($Type -eq 'Microsoft.Authorization/locks') { + $lockName = ($ResourceId -split '/')[-1] + $lockScope = ($ResourceId -split '/providers/Microsoft.Authorization/locks')[0] + return Get-AzResourceLock -LockName $lockName -Scope $lockScope -ErrorAction SilentlyContinue + } else { + return Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue + } +} diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 9ae63db384..58aa89226a 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -1,65 +1,5 @@ <# .SYNOPSIS -Remove unhandled resource locks from a resource. - -.DESCRIPTION -Remove unhandled resource locks from a resource. If the resource is locked, the lock is removed. - -.PARAMETER ResourceId -Mandatory. The resourceID of the resource to check, and remove the lock from if it is locked. - -.PARAMETER RetryLimit -The number of times to retry checking if the lock is removed. - -.PARAMETER RetryInterval -The number of seconds to wait between each retry. - -.EXAMPLE -Remove-UnhandledResourceLock -ResourceId '/subscriptions/.../resourceGroups/validation-rg/.../resource-name' - -Check if the resource 'resource-name' is locked. If it is, remove the lock. -#> -function Remove-UnhandledResourceLock { - [CmdletBinding(SupportsShouldProcess)] - param ( - [Parameter(Mandatory = $true)] - [string] $ResourceId, - - [int] $RetryLimit = 10, - - [int] $RetryInterval = 10 - ) - - $resourceLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue - $isLocked = $resourceLock.count -gt 0 - - if (-not $isLocked) { - return - } - - Write-Warning (' [-] 🔒 Unhandled resource lock detected. Removing lock.' -f $ResourceId) - if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { - $null = $resourceLock | Remove-AzResourceLock -Force - - $retryCount = 0 - do { - $retryCount++ - if ($retryCount -ge $RetryLimit) { - Write-Warning (' [!] Lock was not removed after {1} seconds. Continuing with resource removal.' -f ($retryCount * $RetryInterval)) - return - } - Write-Verbose ' [⏱️] Waiting for lock to be removed.' -Verbose - Start-Sleep -Seconds $RetryInterval - $resourceLock = Get-AzResourceLock -Scope $ResourceId -ErrorAction SilentlyContinue - $isLocked = $resourceLock.count -gt 0 - } while ($isLocked) - } - return -} - - -<# -.SYNOPSIS Remove a specific resource .DESCRIPTION @@ -86,8 +26,8 @@ function Invoke-ResourceRemoval { [Parameter(Mandatory = $true)] [string] $Type ) - - Remove-UnhandledResourceLock -ResourceId $ResourceId + # Load functions + . (Join-Path $PSScriptRoot 'Invoke-ResourceLockRemoval.ps1') switch ($Type) { 'Microsoft.Insights/diagnosticSettings' { @@ -99,25 +39,7 @@ function Invoke-ResourceRemoval { break } 'Microsoft.Authorization/locks' { - $lockName = ($ResourceId -split '/')[-1] - $lockScope = ($ResourceId -split '/providers/Microsoft.Authorization/locks')[0] - - $null = Remove-AzResourceLock -LockName $lockName -Scope $lockScope -Force - - $retryCount = 0 - $retryLimit = 10 - $retryInterval = 10 - do { - $retryCount++ - if ($retryCount -ge $retryLimit) { - Write-Warning (' [!] Lock [{0}] was not removed after {1} seconds. Continuing with resource removal.' -f $lockName, ($retryCount * $retryInterval)) - break - } - Write-Verbose (' [⏱️] Waiting for lock [{0}] to be removed.' -f $lockName) -Verbose - Start-Sleep -Seconds $retryInterval - $lockExists = Get-AzResourceLock -LockName $lockName -Scope $lockScope -ErrorAction SilentlyContinue - } while ($lockExists) - break + Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type } 'Microsoft.KeyVault/vaults/keys' { $resourceName = Split-Path $ResourceId -Leaf @@ -265,6 +187,10 @@ function Invoke-ResourceRemoval { } ### CODE LOCATION: Add custom removal action here Default { + # Remove unhandled resource locks, for cases when the resource + # collection is incomplete, usually due to previous removal failing. + Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' } } From 1ba52bc429f39fab391f5651a0d821522d144caf Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:19:37 +0200 Subject: [PATCH 13/26] added break again --- .../pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 58aa89226a..867b4d2072 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -40,6 +40,7 @@ function Invoke-ResourceRemoval { } 'Microsoft.Authorization/locks' { Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + break } 'Microsoft.KeyVault/vaults/keys' { $resourceName = Split-Path $ResourceId -Leaf From e667e341172cf54580bc79a2adc66446025ca003 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:23:57 +0200 Subject: [PATCH 14/26] align outputs/logs --- .../resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index 63fe9675c0..c53e0c028a 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -68,7 +68,7 @@ function Invoke-ResourceLockRemoval { $resourceLock = Invoke-ResourceLockRetrieval -ResourceId $ResourceId -Type $Type $isLocked = $resourceLock.count -gt 0 } while ($isLocked) - Write-Verbose ' [✅] Resource lock removed.' -Verbose + Write-Verbose ' [-] Resource lock removed.' -Verbose return } From 361bf7438157c10186cfe9a88fa37d13b829f80f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:36:24 +0200 Subject: [PATCH 15/26] Moved out of default to before switch --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 867b4d2072..3e15d82643 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -29,6 +29,10 @@ function Invoke-ResourceRemoval { # 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. + Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + switch ($Type) { 'Microsoft.Insights/diagnosticSettings' { $parentResourceId = $ResourceId.Split('/providers/{0}' -f $Type)[0] @@ -188,10 +192,6 @@ function Invoke-ResourceRemoval { } ### CODE LOCATION: Add custom removal action here Default { - # Remove unhandled resource locks, for cases when the resource - # collection is incomplete, usually due to previous removal failing. - Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' } } From 6b184a9fec53856295ac3fd5a66e2e5730457c98 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:43:20 +0200 Subject: [PATCH 16/26] Apply suggestions from code review Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 | 6 +++--- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index c53e0c028a..740554ef16 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -47,12 +47,12 @@ function Invoke-ResourceLockRemoval { return } +if ($Type -ne 'Microsoft.Authorization/locks') { + Write-Warning (' [-] 🔒 Unmanaged resource lock detected. Removing.' -f $ResourceId) if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { - if ($Type -ne 'Microsoft.Authorization/locks') { - Write-Warning (' [-] 🔒 Unmanaged resource lock detected. Removing.' -f $ResourceId) - } $null = $resourceLock | Remove-AzResourceLock -Force } +} $retryCount = 0 do { diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 3e15d82643..a344094d43 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -31,7 +31,9 @@ function Invoke-ResourceRemoval { # Remove unhandled resource locks, for cases when the resource # collection is incomplete, usually due to previous removal failing. - Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + if ($PSCmdlet.ShouldProcess("Possible locks on resource with ID [$ResourceId]", 'Handle')) { + Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + } switch ($Type) { 'Microsoft.Insights/diagnosticSettings' { @@ -43,7 +45,9 @@ function Invoke-ResourceRemoval { break } 'Microsoft.Authorization/locks' { - Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + if ($PSCmdlet.ShouldProcess("Lock with ID [$ResourceId]", 'Handle')) { + Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type + } break } 'Microsoft.KeyVault/vaults/keys' { From 6f30f0c1178d1e320e39245f159173bc31974db5 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:46:41 +0200 Subject: [PATCH 17/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index a344094d43..c48fefbe5a 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -87,7 +87,9 @@ function Invoke-ResourceRemoval { # Actual removal # -------------- - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + } break } 'Microsoft.RecoveryServices/vaults/backupstorageconfig' { From 576199b3922f246638f6fdfed62ba3ecdd6fe899 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:47:08 +0200 Subject: [PATCH 18/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index c48fefbe5a..4d751f684b 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -130,7 +130,9 @@ function Invoke-ResourceRemoval { # Actual removal # -------------- - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + } break } 'Microsoft.OperationalInsights/workspaces' { From 6f451093855fd5efaee7bb81287d072a11688d32 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:47:48 +0200 Subject: [PATCH 19/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 4d751f684b..66b5c27867 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -156,7 +156,9 @@ function Invoke-ResourceRemoval { Write-Verbose " [⏱️] Removed lock [$($PSItem.Name)] created by the DevTest Lab [$resourceName] on resource [$($PSItem.ResourceName)]. Waiting 10 seconds for propagation." -Verbose Start-Sleep -Seconds 10 } - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + } } break } From c15d3311d452c3640b676e56ac4bdbba4b0c51ab Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 10:47:59 +0200 Subject: [PATCH 20/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 66b5c27867..e5a98440b1 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -202,7 +202,9 @@ function Invoke-ResourceRemoval { } ### CODE LOCATION: Add custom removal action here Default { - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + } } } } From 7b886082a51f716fa75acb81825366094aa54d76 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 11:30:33 +0200 Subject: [PATCH 21/26] Remove DevTestLab as logic is covered with regular flow now --- .../helper/Invoke-ResourceRemoval.ps1 | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index e5a98440b1..5dd29aca9e 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -45,7 +45,7 @@ function Invoke-ResourceRemoval { break } 'Microsoft.Authorization/locks' { - if ($PSCmdlet.ShouldProcess("Lock with ID [$ResourceId]", 'Handle')) { + if ($PSCmdlet.ShouldProcess("Lock with ID [$ResourceId]", 'Remove')) { Invoke-ResourceLockRemoval -ResourceId $ResourceId -Type $Type } break @@ -145,23 +145,6 @@ function Invoke-ResourceRemoval { } break } - 'Microsoft.DevTestLab/labs' { - $resourceGroupName = $ResourceId.Split('/')[4] - $resourceName = Split-Path $ResourceId -Leaf - if ($PSCmdlet.ShouldProcess("DevTestLab Lab [$resourceName]", 'Remove')) { - Get-AzResourceLock -ResourceGroupName $resourceGroupName | - Where-Object -FilterScript { $PSItem.properties.notes -eq "Reserved resource locked by '$resourceName' lab." } | - ForEach-Object { - $null = Remove-AzResourceLock -LockId $PSItem.LockId -Force - Write-Verbose " [⏱️] Removed lock [$($PSItem.Name)] created by the DevTest Lab [$resourceName] on resource [$($PSItem.ResourceName)]. Waiting 10 seconds for propagation." -Verbose - Start-Sleep -Seconds 10 - } - if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' - } - } - break - } 'Microsoft.MachineLearningServices/workspaces' { $subscriptionId = $ResourceId.Split('/')[2] $resourceGroupName = $ResourceId.Split('/')[4] From 12953cb637696f959ffd820b7bc1986dd5b69949 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 12:12:52 +0200 Subject: [PATCH 22/26] Change logging and removal loop slightly --- .../helper/Invoke-ResourceLockRemoval.ps1 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index 740554ef16..5380d73fdb 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -47,12 +47,14 @@ function Invoke-ResourceLockRemoval { return } -if ($Type -ne 'Microsoft.Authorization/locks') { - Write-Warning (' [-] 🔒 Unmanaged resource lock detected. Removing.' -f $ResourceId) - if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { - $null = $resourceLock | Remove-AzResourceLock -Force + if ($Type -ne 'Microsoft.Authorization/locks') { + $resourceLock | ForEach-Object { + Write-Warning (' [-] Removing lock [{0}] on [{1}] of type [{2}].' -f $_.Name, $_.ResourceName) + if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { + $null = $_ | Remove-AzResourceLock -Force + } + } } -} $retryCount = 0 do { From 95f67dfe17cd66188c3af61f9baab533c93758fa Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 12:51:15 +0200 Subject: [PATCH 23/26] Fixing logic --- .../helper/Invoke-ResourceLockRemoval.ps1 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index 5380d73fdb..8f06bab103 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -47,12 +47,10 @@ function Invoke-ResourceLockRemoval { return } - if ($Type -ne 'Microsoft.Authorization/locks') { - $resourceLock | ForEach-Object { - Write-Warning (' [-] Removing lock [{0}] on [{1}] of type [{2}].' -f $_.Name, $_.ResourceName) - if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}]' -f $resourceLock.Name, $resourceLock.ResourceName ), 'Remove')) { - $null = $_ | Remove-AzResourceLock -Force - } + $resourceLock | ForEach-Object { + Write-Warning (' [-] Removing lock [{0}] on [{1}] of type [{2}].' -f $_.Name, $_.ResourceName, $_.ResourceType) + if ($PSCmdlet.ShouldProcess(('Lock [{0}] on resource [{1}] of type [{2}].' -f $_.Name, $_.ResourceName, $_.ResourceType ), 'Remove')) { + $null = $_ | Remove-AzResourceLock -Force } } From 70b57e8b0f2236ccf832b6d1306328738fa3bb43 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Mon, 4 Sep 2023 12:53:50 +0200 Subject: [PATCH 24/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 --- .../resourceRemoval/helper/Invoke-ResourceRemoval.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index 5dd29aca9e..b0065c67a4 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -65,7 +65,9 @@ function Invoke-ResourceRemoval { 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 { - $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + if ($PSCmdlet.ShouldProcess("Resource with ID [$ResourceId]", 'Remove')) { + $null = Remove-AzResource -ResourceId $ResourceId -Force -ErrorAction 'Stop' + } } break } From 27d0f2a9f1102157279322b5cf3de946bb61641c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 13:06:39 +0200 Subject: [PATCH 25/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index 8f06bab103..225a3f7eb0 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -68,7 +68,6 @@ function Invoke-ResourceLockRemoval { $resourceLock = Invoke-ResourceLockRetrieval -ResourceId $ResourceId -Type $Type $isLocked = $resourceLock.count -gt 0 } while ($isLocked) - Write-Verbose ' [-] Resource lock removed.' -Verbose - - return + + Write-Verbose ' [-] Resource lock(s) removed.' -Verbose } From c7095ef9af8f9d95f9100f6d6595e6961b31c128 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 4 Sep 2023 13:09:06 +0200 Subject: [PATCH 26/26] Update utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 Co-authored-by: Alexander Sehr --- .../resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 index 225a3f7eb0..ccd41cbceb 100644 --- a/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 +++ b/utilities/pipelines/resourceRemoval/helper/Invoke-ResourceLockRemoval.ps1 @@ -69,5 +69,5 @@ function Invoke-ResourceLockRemoval { $isLocked = $resourceLock.count -gt 0 } while ($isLocked) - Write-Verbose ' [-] Resource lock(s) removed.' -Verbose + Write-Verbose (' [-] [{0}] resource lock(s) removed.' -f $resourceLock.count) -Verbose }