From b56ba7b889a8601d80ae4ded7def13bc37533801 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Thu, 29 Feb 2024 22:08:15 +0000 Subject: [PATCH] Update --- src/functions/Invoke-AzOpsPush.ps1 | 6 +-- src/internal/functions/Get-AzOpsResource.ps1 | 41 +++++++++++++------ .../functions/Remove-AzOpsDeployment.ps1 | 33 +++++++-------- .../functions/Remove-AzResourceRaw.ps1 | 23 ++++------- .../Remove-AzResourceRawRecursive.ps1 | 10 ++--- src/localized/en-us/Strings.psd1 | 8 ++-- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/functions/Invoke-AzOpsPush.ps1 b/src/functions/Invoke-AzOpsPush.ps1 index 47443ec6..d48c044d 100644 --- a/src/functions/Invoke-AzOpsPush.ps1 +++ b/src/functions/Invoke-AzOpsPush.ps1 @@ -549,7 +549,7 @@ #Removal of Supported resourceTypes and Custom Templates $deletionList = Set-AzOpsRemoveOrder -DeletionList $deletionList -Index { $_.ScopeObject.Resource } $removalJob = $deletionList | Select-Object $uniqueProperties -Unique | Remove-AzOpsDeployment -WhatIf:$WhatIfPreference -DeleteSet (Resolve-Path -Path $deleteSet).Path - if ($removalJob.FullyQualifiedResourceId.Count -gt 0) { + if ($removalJob.ScopeObject.Scope.Count -gt 0) { Clear-PSFMessage # Identify failed removal attempts for potential retries $retry = $removalJob | Where-Object { $_.Status -eq 'failed' } @@ -568,11 +568,11 @@ # Check each failed removal and attempt to get the associated resource foreach ($fail in $removeActionFail) { $resource = $null - $resource = Get-AzOpsResource -ResourceId $fail.FullyQualifiedResourceId -ScopeObject $fail.ScopeObject + $resource = Get-AzOpsResource -ScopeObject $fail.ScopeObject # If the resource is found, log the failure if ($resource) { $throwFail = $true - Write-AzOpsMessage -LogLevel Critical -LogString 'Invoke-AzOpsPush.Deletion.Failed' -LogStringValues $fail.FullyQualifiedResourceId, $fail.TemplateFilePath, $fail.TemplateParameterFilePath + Write-AzOpsMessage -LogLevel Critical -LogString 'Invoke-AzOpsPush.Deletion.Failed' -LogStringValues $fail.ScopeObject.Scope, $fail.TemplateFilePath, $fail.TemplateParameterFilePath } } # If any failures occurred, throw an exception diff --git a/src/internal/functions/Get-AzOpsResource.ps1 b/src/internal/functions/Get-AzOpsResource.ps1 index d9b33d20..d1e26963 100644 --- a/src/internal/functions/Get-AzOpsResource.ps1 +++ b/src/internal/functions/Get-AzOpsResource.ps1 @@ -5,19 +5,14 @@ Check if the Azure resource exists. .DESCRIPTION Check if the Azure resource exists. - .PARAMETER ResourceId - The ResourceId to check. .PARAMETER ScopeObject - Object used to set Azure context for operation. + The Resource to check. .EXAMPLE - > Get-AzOpsResource -ResourceId /subscriptions/6a35d1cc-ae17-4c0c-9a66-1d9a25647b19/resourceGroups/NetworkWatcherRG -ScopeObject $ScopeObject + > Get-AzOpsResource -ScopeObject $ScopeObject #> [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] - [string] - $ResourceId, [Parameter(Mandatory = $true)] [AzOpsScope] $ScopeObject @@ -25,12 +20,32 @@ process { Set-AzOpsContext -ScopeObject $ScopeObject - # Check if the resource exists - if ($ResourceId -match '^/subscriptions/.*/providers/Microsoft.Authorization/locks' -or $ResourceId -match '^/subscriptions/.*/resourceGroups/.*/providers/Microsoft.Authorization/locks') { - $resource = Get-AzResourceLock | Where-Object { $_.ResourceId -eq $ResourceId } -ErrorAction SilentlyContinue - } - else { - $resource = Get-AzResource -ResourceId $ResourceId -ErrorAction SilentlyContinue + switch ($ScopeObject.Resource) { + # Check if the resource exist + 'locks' { + $resource = Get-AzResourceLock -Scope "/subscriptions/$($ScopeObject.Subscription)" -ErrorAction SilentlyContinue | Where-Object { $_.ResourceID -eq $ScopeObject.Scope } + } + 'policyAssignments' { + $resource = Get-AzPolicyAssignment -Id $scopeObject.Scope -ErrorAction SilentlyContinue + } + 'policyDefinitions' { + $resource = Get-AzPolicyDefinition -Id $scopeObject.Scope -ErrorAction SilentlyContinue + } + 'policyExemptions' { + $resource = Get-AzPolicyExemption -Id $scopeObject.Scope -ErrorAction SilentlyContinue + } + 'policySetDefinitions' { + $resource = Get-AzPolicySetDefinition -Id $scopeObject.Scope -ErrorAction SilentlyContinue + } + 'roleAssignments' { + $resource = Invoke-AzRestMethod -Path "$($scopeObject.Scope)?api-version=2022-04-01" | Where-Object { $_.StatusCode -eq 200 } + } + 'resourceGroups' { + $resource = Get-AzResourceGroup -Id $scopeObject.Scope -ErrorAction SilentlyContinue + } + default { + $resource = Get-AzResource -ResourceId $ScopeObject.Scope -ErrorAction SilentlyContinue + } } if ($resource) { return $resource diff --git a/src/internal/functions/Remove-AzOpsDeployment.ps1 b/src/internal/functions/Remove-AzOpsDeployment.ps1 index dde41045..82639a1a 100644 --- a/src/internal/functions/Remove-AzOpsDeployment.ps1 +++ b/src/internal/functions/Remove-AzOpsDeployment.ps1 @@ -249,17 +249,17 @@ switch ($scopeObject.Resource) { # Check resource existance through optimal path 'locks' { - $resourceToDelete = Get-AzResourceLock -Scope "/subscriptions/$($ScopeObject.Subscription)" -ErrorAction SilentlyContinue | Where-Object {$_.ResourceID -eq $ScopeObject.scope} + $resourceToDelete = Get-AzResourceLock -Scope "/subscriptions/$($ScopeObject.Subscription)" -ErrorAction SilentlyContinue | Where-Object { $_.ResourceID -eq $ScopeObject.Scope } } 'policyAssignments' { - $resourceToDelete = Get-AzPolicyAssignment -Id $scopeObject.scope -ErrorAction SilentlyContinue + $resourceToDelete = Get-AzPolicyAssignment -Id $scopeObject.Scope -ErrorAction SilentlyContinue if ($resourceToDelete) { $dependency += Get-AzPolicyAssignmentDeletionDependency -resourceToDelete $resourceToDelete $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } } 'policyDefinitions' { - $resourceToDelete = Get-AzPolicyDefinition -Id $scopeObject.scope -ErrorAction SilentlyContinue + $resourceToDelete = Get-AzPolicyDefinition -Id $scopeObject.Scope -ErrorAction SilentlyContinue if ($resourceToDelete) { $dependency += Get-AzPolicyAssignmentDeletionDependency -resourceToDelete $resourceToDelete $dependency += Get-AzPolicyDefinitionDeletionDependency -resourceToDelete $resourceToDelete @@ -267,20 +267,20 @@ } } 'policyExemptions' { - $resourceToDelete = Get-AzPolicyExemption -Id $scopeObject.scope -ErrorAction SilentlyContinue + $resourceToDelete = Get-AzPolicyExemption -Id $scopeObject.Scope -ErrorAction SilentlyContinue if ($resourceToDelete) { $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } } 'policySetDefinitions' { - $resourceToDelete = Get-AzPolicySetDefinition -Id $scopeObject.scope -ErrorAction SilentlyContinue + $resourceToDelete = Get-AzPolicySetDefinition -Id $scopeObject.Scope -ErrorAction SilentlyContinue if ($resourceToDelete) { $dependency += Get-AzPolicyAssignmentDeletionDependency -resourceToDelete $resourceToDelete $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } } 'roleAssignments' { - $resourceToDelete = Invoke-AzRestMethod -Path "$($scopeObject.scope)?api-version=2022-04-01" | Where-Object { $_.StatusCode -eq 200 } + $resourceToDelete = Invoke-AzRestMethod -Path "$($scopeObject.Scope)?api-version=2022-04-01" | Where-Object { $_.StatusCode -eq 200 } if ($resourceToDelete) { $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } @@ -329,7 +329,7 @@ Set-AzOpsWhatIfOutput -FilePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true } if ($PSCmdlet.ShouldProcess("Remove $($scopeObject.Scope)?")) { - $null = Remove-AzResourceRaw -FullyQualifiedResourceId $scopeObject.Scope -ScopeObject $scopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath + $null = Remove-AzResourceRaw -ScopeObject $scopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath } else { Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzOpsDeployment.SkipDueToWhatIf' @@ -349,21 +349,18 @@ $allResults = @() foreach ($change in $removalJobChanges) { $resource = $null + $resourceScopeObject = $null # Check if the resource exists - if ($change.RelativeResourceId.StartsWith('Microsoft.Authorization/locks/')) { - $resource = Get-AzResourceLock | Where-Object { $_.ResourceId -eq $change.FullyQualifiedResourceId } -ErrorAction SilentlyContinue - } - else { - $resource = Get-AzResource -ResourceId $change.FullyQualifiedResourceId -ErrorAction SilentlyContinue - } + $resourceScopeObject = New-AzOpsScope -Scope $change.FullyQualifiedResourceId -WhatIf:$false + $resource = Get-AzOpsResource -ScopeObject $resourceScopeObject if ($resource) { - $results = 'What if successful:{1}Performing the operation:{1}Deletion of target resource {0}.' -f $change.FullyQualifiedResourceId, [environment]::NewLine + $results = 'What if successful:{1}Performing the operation:{1}Deletion of target resource {0}.' -f $resourceScopeObject.Scope, [environment]::NewLine $allResults += $results Write-AzOpsMessage -LogLevel Verbose -LogString 'Set-AzOpsWhatIfOutput.WhatIfResults' -LogStringValues $results Write-AzOpsMessage -LogLevel InternalComment -LogString 'Set-AzOpsWhatIfOutput.WhatIfFile' # Check if the removal should be performed - if ($PSCmdlet.ShouldProcess("Remove $($change.FullyQualifiedResourceId)?")) { - $removeAction = Remove-AzResourceRaw -FullyQualifiedResourceId $change.FullyQualifiedResourceId -ScopeObject $ScopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath + if ($PSCmdlet.ShouldProcess("Remove $($resourceScopeObject.Scope)?")) { + $removeAction = Remove-AzResourceRaw -ScopeObject $resourceScopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath # If removal failed, add to retry if ($removeAction.Status -eq 'failed') { $retry += $removeAction @@ -375,7 +372,7 @@ } else { # Log warning if resource not found - Write-AzOpsMessage -LogLevel Warning -LogString 'Remove-AzOpsDeployment.ResourceNotFound' -LogStringValues $scopeObject.resource, $change.FullyQualifiedResourceId + Write-AzOpsMessage -LogLevel Warning -LogString 'Remove-AzOpsDeployment.ResourceNotFound' -LogStringValues $ScopeObject.Resource, $change.FullyQualifiedResourceId $results = 'What if operation failed:{1}Deletion of target resource {0}.{1}Resource could not be found' -f $change.FullyQualifiedResourceId, [environment]::NewLine $allResults += $results } @@ -422,7 +419,7 @@ else { # No resource to remove was found Write-AzOpsMessage -LogLevel Warning -LogString 'Remove-AzOpsDeployment.ResourceNotFound' -LogStringValues $scopeObject.Resource, $scopeObject.Scope - $results = 'What if operation failed:{1}Deletion of target resource {0}.{1}Resource could not be found' -f $scopeObject.scope, [environment]::NewLine + $results = 'What if operation failed:{1}Deletion of target resource {0}.{1}Resource could not be found' -f $scopeObject.Scope, [environment]::NewLine Set-AzOpsWhatIfOutput -FilePath $TemplateFilePath -ParameterFilePath $TemplateParameterFilePath -Results $results -RemoveAzOpsFlag $true return } diff --git a/src/internal/functions/Remove-AzResourceRaw.ps1 b/src/internal/functions/Remove-AzResourceRaw.ps1 index e04cd252..1a8304ea 100644 --- a/src/internal/functions/Remove-AzResourceRaw.ps1 +++ b/src/internal/functions/Remove-AzResourceRaw.ps1 @@ -5,19 +5,16 @@ Performs resource deletion in Azure at any scope. .DESCRIPTION Performs resource deletion in Azure with FullyQualifiedResourceId and ScopeObject. - .PARAMETER FullyQualifiedResourceId - Parameter containing FullyQualifiedResourceId of resource to delete. .PARAMETER TemplateFilePath Path where the ARM templates can be found. .PARAMETER TemplateParameterFilePath Path where the parameters of the ARM templates can be found. .PARAMETER ScopeObject - Object used to set Azure context for removal operation. + Resource to delete. .EXAMPLE - > Remove-AzResourceRaw -FullyQualifiedResourceId '/subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults/' -ScopeObject $ScopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath + > Remove-AzResourceRaw -ScopeObject $ScopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath Name Value ---- ----- - FullyQualifiedResourceId /subscriptions//resourceGroups//providers/Microsoft.KeyVault/vaults/ TemplateFilePath /root/managementgroup/subscription/resourcegroup/template.json TemplateParameterFilePath /root/managementgroup/subscription/resourcegroup/template.parameters.json ScopeObject ScopeObject @@ -26,9 +23,6 @@ [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] - [string] - $FullyQualifiedResourceId, [string] $TemplateFilePath, [string] @@ -41,27 +35,26 @@ process { # Construct result object $result = [PSCustomObject]@{ - FullyQualifiedResourceId = $FullyQualifiedResourceId TemplateFilePath = $TemplateFilePath TemplateParameterFilePath = $TemplateParameterFilePath - ScopeObject = $scopeObject + ScopeObject = $ScopeObject Status = 'success' } # Check if the resource exists - $resource = Get-AzOpsResource -ResourceId $FullyQualifiedResourceId -ScopeObject $ScopeObject + $resource = Get-AzOpsResource -ScopeObject $ScopeObject # Remove the resource if it exists if ($resource) { try { # Set Azure context for removal operation Set-AzOpsContext -ScopeObject $ScopeObject - $null = Remove-AzResource -ResourceId $FullyQualifiedResourceId -Force -ErrorAction Stop + $null = Remove-AzResource -ResourceId $ScopeObject.Scope -Force -ErrorAction Stop $maxAttempts = 4 $attempt = 1 $gone = $false while ($gone -eq $false -and $attempt -le $maxAttempts) { - Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.CheckExistence' -LogStringValues $FullyQualifiedResourceId + Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.CheckExistence' -LogStringValues $ScopeObject.Scope Start-Sleep -Seconds 10 - $tryResource = Get-AzOpsResource -ResourceId $FullyQualifiedResourceId -ScopeObject $ScopeObject + $tryResource = Get-AzOpsResource -ScopeObject $ScopeObject if (-not $tryResource) { $gone = $true } @@ -70,7 +63,7 @@ } catch { # Log failure message - Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.Failed' -LogStringValues $ScopeObject.resource, $FullyQualifiedResourceId + Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.Failed' -LogStringValues $ScopeObject.Resource, $ScopeObject.Scope $result.Status = 'failed' } } diff --git a/src/internal/functions/Remove-AzResourceRawRecursive.ps1 b/src/internal/functions/Remove-AzResourceRawRecursive.ps1 index 8762e7fa..aaaacb1b 100644 --- a/src/internal/functions/Remove-AzResourceRawRecursive.ps1 +++ b/src/internal/functions/Remove-AzResourceRawRecursive.ps1 @@ -31,10 +31,10 @@ # Base case: All items have been used, perform action on the current order foreach ($item in $CurrentOrder) { if ($item.Status -eq 'failed' -or $null -eq $item.Status) { - Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRawRecursive.Processing' -LogStringValues $item.ScopeObject.resource, $item.FullyQualifiedResourceId + Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRawRecursive.Processing' -LogStringValues $item.ScopeObject.Resource, $item.ScopeObject.Scope # Attempt to remove the resource - $result = Remove-AzResourceRaw -FullyQualifiedResourceId $item.FullyQualifiedResourceId -ScopeObject $item.ScopeObject -TemplateFilePath $item.TemplateFilePath -TemplateParameterFilePath $item.TemplateParameterFilePath - if ($result.Status -eq 'failed' -and $result.FullyQualifiedResourceId -notin $OutputObject.FullyQualifiedResourceId){ + $result = Remove-AzResourceRaw -ScopeObject $item.ScopeObject -TemplateFilePath $item.TemplateFilePath -TemplateParameterFilePath $item.TemplateParameterFilePath + if ($result.Status -eq 'failed' -and $result.ScopeObject.Scope -notin $OutputObject.ScopeObject.Scope){ # Add failed result to the output object $OutputObject += $result } @@ -48,9 +48,9 @@ # Filter out items already processed successfully $filteredOutputObject = @() foreach ($item in $InputObject) { - if ($item.FullyQualifiedResourceId -in $OutputObject.FullyQualifiedResourceId) { + if ($item.ScopeObject.Scope -in $OutputObject.ScopeObject.Scope) { foreach ($output in $OutputObject) { - if ($output.FullyQualifiedResourceId -eq $item.FullyQualifiedResourceId -and $output.Status -eq 'failed') { + if ($output.ScopeObject.Scope -eq $item.ScopeObject.Scope -and $output.Status -eq 'failed') { # Add previously failed item to the filtered output $filteredOutputObject += $output continue diff --git a/src/localized/en-us/Strings.psd1 b/src/localized/en-us/Strings.psd1 index 2cab349f..61cba749 100644 --- a/src/localized/en-us/Strings.psd1 +++ b/src/localized/en-us/Strings.psd1 @@ -196,7 +196,7 @@ 'Invoke-AzOpsPush.Change.Delete.TempFile' = 'Creating temporary file dir for deletion processing: {0}' # $fileName 'Invoke-AzOpsPush.Change.Delete.NextTempFile' = 'Exiting while loop, file detected in $DeleteSetContents for deletion processing based on this content line: [{0}]' # $currentLine 'Invoke-AzOpsPush.Change.Delete.SetTempFileContent' = 'Set temporary file content: [{1}], in [{0}]' # $fileName, $jsonValue - 'Invoke-AzOpsPush.Deletion.Failed' = 'Deletion of resources {0}, has failed using templates: {1}, {2}, this could be due to delayed deletion acceptance from Azure, please investigate and take action.' # $fail.FullyQualifiedResourceId, $fail.TemplateFilePath, $fail.TemplateParameterFilePath + 'Invoke-AzOpsPush.Deletion.Failed' = 'Deletion of resources {0}, has failed using templates: {1}, {2}, this could be due to delayed deletion acceptance from Azure, please investigate and take action.' # $fail.ScopeObject.Scope, $fail.TemplateFilePath, $fail.TemplateParameterFilePath 'Invoke-AzOpsPush.Deletion.Retry' = 'Deletion of {0} resources unsuccessful, initiate final retry combination.' # $retry.Count 'Invoke-AzOpsPush.Deploy.ProviderFeature' = 'Invoking new state deployment - *.providerfeatures.json for a file {0}' # $addition 'Invoke-AzOpsPush.Deploy.ResourceProvider' = 'Invoking new state deployment - *.resourceproviders.json for a file {0}' # $addition @@ -286,12 +286,12 @@ 'Remove-AzOpsDeployment.ResourceDependencyNested' = 'resource dependency {0} for complete deletion of {1} is outside of supported AzOps scope. Please remove this dependency in Azure without AzOps.'# $roleAssignmentId, $policyAssignment.ResourceId 'Remove-AzOpsDeployment.ResourceDependencyNotFound' = 'Missing resource dependency {0} for successfull deletion of {1}. Please add missing resource and retry.'# $resource.ResourceId, $scopeObject.Scope 'Remove-AzOpsDeployment.Resource.RetryCount' = 'Retry deletion of {0} resources in different order'# $retry.Count - 'Remove-AzOpsDeployment.ResourceNotFound' = 'Unable to find resource of type {0} with id {1}.'# $scopeObject.resource, $scopeObject.scope, $resultsError + 'Remove-AzOpsDeployment.ResourceNotFound' = 'Unable to find resource of type {0} with id {1}.'# $scopeObject.Resource, $scopeObject.Scope, $resultsError 'Remove-AzOpsDeployment.SkipUnsupportedResource' = 'Deletion of AzOps generated file resources is only supported for locks, policyAssignments, policyDefinitions, policyExemptions, policySetDefinitions and roleAssignments. Will NOT proceed with deletion of resource in file {0}'# $TemplateFilePath 'Remove-AzResourceRaw.Resource.CheckExistence' = 'Checking existence after deletion of: [{0}]'# $FullyQualifiedResourceId - 'Remove-AzResourceRaw.Resource.Failed' = 'Unable to delete resource of type {0} with id {1}'# $scopeObject.scope, $FullyQualifiedResourceId - 'Remove-AzResourceRawRecursive.Processing' = 'Recursive retry processing to delete resource of type {0} with id {1}'# $item.ScopeObject.resource, $item.FullyQualifiedResourceId + 'Remove-AzResourceRaw.Resource.Failed' = 'Unable to delete resource of type {0} with id {1}'# $ScopeObject.Resource, $ScopeObject.Scope + 'Remove-AzResourceRawRecursive.Processing' = 'Recursive retry processing to delete resource of type {0} with id {1}'# $item.ScopeObject.Resource, $item.ScopeObject.Scope 'Remove-AzOpsInvalidCharacter.Completed' = 'Valid string: {0}'# $String 'Remove-AzOpsInvalidCharacter.Invalid' = 'Invalid character detected in string: {0}, further processing initiated'# $String