Skip to content

Commit

Permalink
Update to latest
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderSehr committed Aug 8, 2024
1 parent 5914e17 commit f22fa07
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function Initialize-DeploymentRemoval {
# The initial sequence is a general order-recommendation
$RemoveFirstSequence = @(
'Microsoft.Authorization/locks',
'Microsoft.VirtualMachineImages/imageTemplates', # Must be removed before their MSI & should be removed before its entities permissions are removed
'Microsoft.Authorization/roleAssignments',
'Microsoft.Insights/diagnosticSettings',
'Microsoft.Network/privateEndpoints/privateDnsZoneGroups',
Expand All @@ -91,7 +92,6 @@ function Initialize-DeploymentRemoval {
'Microsoft.MachineLearningServices/workspaces',
'Microsoft.Compute/virtualMachines',
'Microsoft.ContainerInstance/containerGroups' # Must be removed before their MSI
'Microsoft.VirtualMachineImages/imageTemplates', # Must be removed before their MSI
'Microsoft.ManagedIdentity/userAssignedIdentities',
'Microsoft.Databricks/workspaces'
'Microsoft.Resources/resourceGroups'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,108 @@
#region helper
<#
.SYNOPSIS
Get all deployment operations at a given scope
.DESCRIPTION
Get all deployment oeprations at a given scope. By default, the results are filtered down to 'create' operations (i.e., excluding 'read' operations that would correspond to 'existing' resources).
.PARAMETER Name
Mandatory. The deployment name to search for
.PARAMETER ResourceGroupName
Optional. The name of the resource group for scope 'resourcegroup'. Relevant for resource-group-level deployments.
.PARAMETER SubscriptionId
Optional. The ID of the subscription to fetch deployments from. Relevant for subscription- & resource-group-level deployments.
.PARAMETER ManagementGroupId
Optional. The ID of the management group to fetch deployments from. Relevant for management-group-level deployments.
.PARAMETER Scope
Mandatory. The scope to search in
.PARAMETER ProvisioningOperationsToInclude
Optional. The provisioning operations to include in the result set. By default, only 'create' operations are included.
.EXAMPLE
Get-DeploymentOperationAtScope -Scope 'subscription' -Name 'v73rhp24d7jya-test-apvmiaiboaai'
Get all deployment operations for a deployment with name 'v73rhp24d7jya-test-apvmiaiboaai' at scope 'subscription'
.NOTES
This function is a standin for the Get-AzDeploymentOperation cmdlet, which does not provide the ability to filter by provisioning operation.
As such, it was also returning 'existing' resources (i.e., with provisioningOperation=Read).
#>
function Get-DeploymentOperationAtScope {

[CmdletBinding()]
param (
[Parameter(Mandatory)]
[Alias('DeploymentName')]
[string] $Name,

[Parameter(Mandatory = $false)]
[string] $ResourceGroupName,

[Parameter(Mandatory = $false)]
[string] $SubscriptionId,

[Parameter(Mandatory = $false)]
[string] $ManagementGroupId,

[Parameter(Mandatory = $false)]
[ValidateSet(
'Create', # any resource creation
'Read' # E.g., 'existing' resources
)]
[string[]] $ProvisioningOperationsToInclude = @('Create'),

[Parameter(Mandatory)]
[ValidateSet(
'resourcegroup',
'subscription',
'managementgroup',
'tenant'
)]
[string] $Scope
)


switch ($Scope) {
'resourcegroup' {
$path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Resources/deployments/{2}/operations?api-version=2021-04-01' -f $SubscriptionId, $ResourceGroupName, $name
break
}
'subscription' {
$path = '/subscriptions/{0}/providers/Microsoft.Resources/deployments/{1}/operations?api-version=2021-04-01' -f $SubscriptionId, $name
break
}
'managementgroup' {
$path = '/providers/Microsoft.Management/managementGroups/{0}/providers/Microsoft.Resources/deployments/{1}/operations?api-version=2021-04-01' -f $ManagementGroupId, $name
break
}
'tenant' {
$path = '/providers/Microsoft.Resources/deployments/{0}/operations?api-version=2021-04-01' -f $name
break
}
}

##############################################
# Get all deployment children based on scope #
##############################################

$response = Invoke-AzRestMethod -Method 'GET' -Path $path

if ($response.StatusCode -ne 200) {
Write-Error ('Failed to fetch deployment operations for deployment [{0}] in scope [{1}]' -f $name, $scope)
return
} else {
$deploymentOperations = ($response.content | ConvertFrom-Json).value.properties
$deploymentOperationsFiltered = $deploymentOperations | Where-Object { $_.provisioningOperation -in $ProvisioningOperationsToInclude }
return $deploymentOperationsFiltered
}
}

<#
.SYNOPSIS
Get all deployments that match a given deployment name in a given scope
Expand Down Expand Up @@ -63,10 +167,14 @@ function Get-DeploymentTargetResourceListInner {
##############################################
# Get all deployment children based on scope #
##############################################
$baseInputObject = @{
Scope = $Scope
DeploymentName = $Name
}
switch ($Scope) {
'resourcegroup' {
if (Get-AzResourceGroup -Name $resourceGroupName -ErrorAction 'SilentlyContinue') {
[array]$deploymentTargets = (Get-AzResourceGroupDeploymentOperation -DeploymentName $name -ResourceGroupName $resourceGroupName).TargetResource | Where-Object { $_ -ne $null } | Select-Object -Unique
[array]$deploymentTargets = (Get-DeploymentOperationAtScope @baseInputObject -ResourceGroupName $resourceGroupName -SubscriptionId $currentContext.Subscription.Id).targetResource.id | Where-Object { $_ -ne $null } | Select-Object -Unique
} else {
# In case the resource group itself was already deleted, there is no need to try and fetch deployments from it
# In case we already have any such resources in the list, we should remove them
Expand All @@ -75,15 +183,15 @@ function Get-DeploymentTargetResourceListInner {
break
}
'subscription' {
[array]$deploymentTargets = (Get-AzDeploymentOperation -DeploymentName $name).TargetResource | Where-Object { $_ -ne $null } | Select-Object -Unique
[array]$deploymentTargets = (Get-DeploymentOperationAtScope @baseInputObject -SubscriptionId $currentContext.Subscription.Id).targetResource.id | Where-Object { $_ -ne $null } | Select-Object -Unique
break
}
'managementgroup' {
[array]$deploymentTargets = (Get-AzManagementGroupDeploymentOperation -DeploymentName $name -ManagementGroupId $ManagementGroupId).TargetResource | Where-Object { $_ -ne $null } | Select-Object -Unique
[array]$deploymentTargets = (Get-DeploymentOperationAtScope @baseInputObject -ManagementGroupId $ManagementGroupId).targetResource.id | Where-Object { $_ -ne $null } | Select-Object -Unique
break
}
'tenant' {
[array]$deploymentTargets = (Get-AzTenantDeploymentOperation -DeploymentName $name).TargetResource | Where-Object { $_ -ne $null } | Select-Object -Unique
[array]$deploymentTargets = (Get-DeploymentOperationAtScope @baseInputObject).targetResource.id | Where-Object { $_ -ne $null } | Select-Object -Unique
break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,58 @@ function Invoke-ResourceRemoval {
}
break
}
'Microsoft.VirtualMachineImages/imageTemplates' {
$resourceGroupName = $ResourceId.Split('/')[4]
$resourceName = Split-Path $ResourceId -Leaf

# Remove resource
if ($PSCmdlet.ShouldProcess("Image Template [$resourceName]", 'Remove')) {

$removeRequestInputObject = @{
Method = 'DELETE'
Path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2022-07-01' -f $subscriptionId, $resourceGroupName, $resourceName
}
$removalResponse = Invoke-AzRestMethod @removeRequestInputObject
if ($removalResponse.StatusCode -notlike '2*') {
$responseContent = $removalResponse.Content | ConvertFrom-Json
throw ('{0} : {1}' -f $responseContent.error.code, $responseContent.error.message)
}

# Wait for template to be removed. If we don't wait, it can happen that its MSI is removed too soon, locking the resource from deletion
$retryCount = 1
$retryLimit = 240
$retryInterval = 15
do {
$getRequestInputObject = @{
Method = 'GET'
Path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2022-07-01' -f $subscriptionId, $resourceGroupName, $resourceName
}
$getReponse = Invoke-AzRestMethod @getRequestInputObject

if ($getReponse.StatusCode -eq 400) {
# Invalid request
throw ($imageTgetReponseemplate.Content | ConvertFrom-Json).error.message
} elseif ($getReponse.StatusCode -eq 404) {
# Resource not found, removal was successful
$templateExists = $false
} elseif ($getReponse.StatusCode -eq '200') {
# Resource still around - try again
$templateExists = $true
Write-Verbose (' [⏱️] Waiting {0} seconds for Image Template to be removed. [{1}/{2}]' -f $retryInterval, $retryCount, $retryLimit) -Verbose
Start-Sleep -Seconds $retryInterval
$retryCount++
} else {
throw ('Failed request. Response: [{0}]' -f ($getReponse | Out-String))
}
} while ($templateExists -and $retryCount -lt $retryLimit)

if ($retryCount -ge $retryLimit) {
Write-Warning (' [!] Image Template [{0}] was not removed after {1} seconds. Continuing with resource removal.' -f $resourceName, ($retryCount * $retryInterval))
break
}
}
break
}
'Microsoft.MachineLearningServices/workspaces' {
$subscriptionId = $ResourceId.Split('/')[2]
$resourceGroupName = $ResourceId.Split('/')[4]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function Remove-ResourceListInner {

foreach ($resource in $resourcesToRemove) {
$resourceName = Split-Path $resource.resourceId -Leaf
$alreadyProcessed = $processedResources.count -gt 0 ? (($processedResources | Where-Object { $resource.resourceId -like ('{0}*' -f $_) }).Count -gt 0) : $false
$alreadyProcessed = $processedResources.count -gt 0 ? (($processedResources | Where-Object { $resource.resourceId -like ('{0}/*' -f $_) }).Count -gt 0) : $false

if ($alreadyProcessed) {
# Skipping
Expand Down

0 comments on commit f22fa07

Please sign in to comment.