From 7191fee19dffe1bfa61c621fd1090a0c85711f2c Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Tue, 5 Mar 2024 14:11:27 +0000 Subject: [PATCH] Update --- src/AzOps.psd1 | 6 +- src/functions/Invoke-AzOpsPush.ps1 | 2 +- .../functions/Remove-AzOpsDeployment.ps1 | 3 +- .../functions/Remove-AzResourceRaw.ps1 | 183 ++++++++++++++---- .../Remove-AzResourceRawRecursive.ps1 | 76 -------- src/localized/en-us/Strings.psd1 | 2 + 6 files changed, 156 insertions(+), 116 deletions(-) delete mode 100644 src/internal/functions/Remove-AzResourceRawRecursive.ps1 diff --git a/src/AzOps.psd1 b/src/AzOps.psd1 index 2d6fbbd3..1197070d 100644 --- a/src/AzOps.psd1 +++ b/src/AzOps.psd1 @@ -3,7 +3,7 @@ # # Generated by: Customer Architecture Team (CAT) # -# Generated on: 2/20/2024 +# Generated on: 3/5/2024 # @{ @@ -52,10 +52,10 @@ PowerShellVersion = '7.2' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @(@{ModuleName = 'PSFramework'; RequiredVersion = '1.10.318'; }, - @{ModuleName = 'Az.Accounts'; RequiredVersion = '2.15.1'; }, + @{ModuleName = 'Az.Accounts'; RequiredVersion = '2.16.0'; }, @{ModuleName = 'Az.Billing'; RequiredVersion = '2.0.3'; }, @{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '0.13.0'; }, - @{ModuleName = 'Az.Resources'; RequiredVersion = '6.15.1'; }) + @{ModuleName = 'Az.Resources'; RequiredVersion = '6.16.0'; }) # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() diff --git a/src/functions/Invoke-AzOpsPush.ps1 b/src/functions/Invoke-AzOpsPush.ps1 index 5525c61f..a75c1866 100644 --- a/src/functions/Invoke-AzOpsPush.ps1 +++ b/src/functions/Invoke-AzOpsPush.ps1 @@ -559,7 +559,7 @@ Start-Sleep -Seconds 30 # Reset the status of failed attempts and perform recursive removal foreach ($try in $retry) { $try.Status = $null } - $removeActionRecursive = Remove-AzResourceRawRecursive -InputObject $retry + $removeActionRecursive = Remove-AzResourceRaw -InputObject $retry -Recursive $removeActionFail = $removeActionRecursive | Where-Object { $_.Status -eq 'failed' } # If removal fails, log and attempt to fetch the resource causing the failure if ($removeActionFail) { diff --git a/src/internal/functions/Remove-AzOpsDeployment.ps1 b/src/internal/functions/Remove-AzOpsDeployment.ps1 index 1d71ad54..c27d209d 100644 --- a/src/internal/functions/Remove-AzOpsDeployment.ps1 +++ b/src/internal/functions/Remove-AzOpsDeployment.ps1 @@ -350,6 +350,7 @@ foreach ($change in $removalJobChanges) { $resource = $null $resourceScopeObject = $null + $removeAction = $null # Check if the resource exists $resourceScopeObject = New-AzOpsScope -Scope $change.FullyQualifiedResourceId -WhatIf:$false $resource = Get-AzOpsResource -ScopeObject $resourceScopeObject -ErrorAction SilentlyContinue @@ -411,7 +412,7 @@ # Retry failed removals recursively Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzOpsDeployment.Resource.RetryCount' -LogStringValues $retry.Count foreach ($try in $retry) { $try.Status = $null } - $removeActionRecursive = Remove-AzResourceRawRecursive -InputObject $retry + $removeActionRecursive = Remove-AzResourceRaw -InputObject $retry -Recursive $removeActionRecursiveRemaining = $removeActionRecursive | Where-Object { $_.Status -eq 'failed' } return $removeActionRecursiveRemaining } diff --git a/src/internal/functions/Remove-AzResourceRaw.ps1 b/src/internal/functions/Remove-AzResourceRaw.ps1 index 74c3253d..e47e42c9 100644 --- a/src/internal/functions/Remove-AzResourceRaw.ps1 +++ b/src/internal/functions/Remove-AzResourceRaw.ps1 @@ -11,6 +11,10 @@ Path where the parameters of the ARM templates can be found. .PARAMETER ScopeObject Resource to delete. + .PARAMETER InputObject + Object containing items for processing, used in combination with parameter Recursive. + .PARAMETER Recursive + If specified, performs recursive resource deletion and requires use of parameter InputObject. .EXAMPLE > Remove-AzResourceRaw -ScopeObject $ScopeObject -TemplateFilePath $TemplateFilePath -TemplateParameterFilePath $TemplateParameterFilePath Name Value @@ -19,6 +23,14 @@ TemplateParameterFilePath /root/managementgroup/subscription/resourcegroup/template.parameters.json ScopeObject ScopeObject Status success + + > Remove-AzResourceRaw -InputObject $retry -Recursive + Name Value + ---- ----- + TemplateFilePath /root/managementgroup/subscription/resourcegroup/template.json + TemplateParameterFilePath /root/managementgroup/subscription/resourcegroup/template.parameters.json + ScopeObject ScopeObject + Status success #> [CmdletBinding()] @@ -27,51 +39,152 @@ $TemplateFilePath, [string] $TemplateParameterFilePath, - [Parameter(Mandatory = $true)] [AzOpsScope] - $ScopeObject + $ScopeObject, + [array] + $InputObject, + [switch] + $Recursive ) process { - # Construct result object - $result = [PSCustomObject]@{ - TemplateFilePath = $TemplateFilePath - TemplateParameterFilePath = $TemplateParameterFilePath - ScopeObject = $ScopeObject - Status = 'success' - } - # Check if the resource exists - $resource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue - # Remove the resource if it exists - if ($resource) { - try { - # Set Azure context for removal operation - Set-AzOpsContext -ScopeObject $ScopeObject - $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 $ScopeObject.Scope - Start-Sleep -Seconds 10 - $tryResource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue - if (-not $tryResource) { - $gone = $true + function Remove-AzResourceRawRecursive { + + <# + .SYNOPSIS + Performs recursive resource deletion in Azure at any scope. + .DESCRIPTION + Takes $InputObject and performs recursive resource deletion in Azure and exhaust any permutation. + .PARAMETER InputObject + Parameter containing items for processing. + .PARAMETER CurrentOrder + Internal parameter to track recursive progress. + .PARAMETER OutputObject + Track item processing and return result. + .EXAMPLE + > $successFullItems, $failedItems = Remove-AzResourceRawRecursive -InputObject $retry + Example of a $retry array with 6 items, the number of permutations will be 6×5×4×3×2×1=720 + #> + + [CmdletBinding()] + param ( + [array] + $InputObject, + [array] + $CurrentOrder = @(), + [array] + $OutputObject = @() + ) + + process { + if ($InputObject.Count -eq 0) { + # 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.ScopeObject.Scope + # Attempt to remove the resource + $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 + } + } + } + # Return the final result + return $OutputObject + } + else { + if ($InputObject -and $OutputObject) { + # Filter out items already processed successfully + $filteredOutputObject = @() + foreach ($item in $InputObject) { + if ($item.ScopeObject.Scope -in $OutputObject.ScopeObject.Scope) { + foreach ($output in $OutputObject) { + if ($output.ScopeObject.Scope -eq $item.ScopeObject.Scope -and $output.Status -eq 'failed') { + # Add previously failed item to the filtered output + $filteredOutputObject += $output + continue + } + } + } + } + if ($filteredOutputObject) { + $InputObject = $filteredOutputObject + } + } + # Recursive case: Try each item in the current position and recurse with the remaining items + foreach ($item in $InputObject) { + $remainingItems = $InputObject -ne $item + $newOrder = $CurrentOrder + $item + # Recursively call Remove-AzResourceRawRecursive + $OutputObject = Remove-AzResourceRawRecursive -InputObject $remainingItems -CurrentOrder $newOrder -OutputObject $OutputObject } - $attempt++ + # Return the output after all permutations + return $OutputObject } } - catch { - # Log failure message - Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.Failed' -LogStringValues $ScopeObject.Resource, $ScopeObject.Scope - $result.Status = 'failed' + } + if ($null -ne $InputObject -and $Recursive) { + # Perform recursive resource deletion + $result = Remove-AzResourceRawRecursive -InputObject $InputObject + if ($result) { + return $result + } + else { + return } } + elseif ($null -eq $InputObject -and $Recursive) { + # Recursive resource deletion missing input + Write-AzOpsMessage -LogLevel Error -LogString 'Remove-AzResourceRaw.Resource.Recursive.Missing' + return + } else { - # Log not found message - $result.Status = 'notfound' + if (-not $ScopeObject) { + # Resource deletion missing input + Write-AzOpsMessage -LogLevel Error -LogString 'Remove-AzResourceRaw.Resource.Missing' + return + } + # Construct result object + $result = [PSCustomObject]@{ + TemplateFilePath = $TemplateFilePath + TemplateParameterFilePath = $TemplateParameterFilePath + ScopeObject = $ScopeObject + Status = 'success' + } + # Check if the resource exists + $resource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue + # Remove the resource if it exists + if ($resource) { + try { + # Set Azure context for removal operation + Set-AzOpsContext -ScopeObject $ScopeObject + $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 $ScopeObject.Scope + Start-Sleep -Seconds 10 + $tryResource = Get-AzOpsResource -ScopeObject $ScopeObject -ErrorAction SilentlyContinue + if (-not $tryResource) { + $gone = $true + } + $attempt++ + } + } + catch { + # Log failure message + Write-AzOpsMessage -LogLevel InternalComment -LogString 'Remove-AzResourceRaw.Resource.Failed' -LogStringValues $ScopeObject.Resource, $ScopeObject.Scope + $result.Status = 'failed' + } + } + else { + # Log not found message + $result.Status = 'notfound' + } + # Return result object + return $result } - # Return result object - return $result } } \ No newline at end of file diff --git a/src/internal/functions/Remove-AzResourceRawRecursive.ps1 b/src/internal/functions/Remove-AzResourceRawRecursive.ps1 deleted file mode 100644 index aaaacb1b..00000000 --- a/src/internal/functions/Remove-AzResourceRawRecursive.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -function Remove-AzResourceRawRecursive { - - <# - .SYNOPSIS - Performs recursive resource deletion in Azure at any scope. - .DESCRIPTION - Takes $InputObject and performs recursive resource deletion in Azure and exhaust any permutation. - .PARAMETER InputObject - Parameter containing items for processing. - .PARAMETER CurrentOrder - Internal parameter to track recursive progress. - .PARAMETER OutputObject - Parameter to track item processing and return result. - .EXAMPLE - > $successFullItems, $failedItems = Remove-AzResourceRawRecursive -InputObject $retry - Example of a $retry array with 6 items, the number of permutations will be 6×5×4×3×2×1=720 - #> - - [CmdletBinding()] - param ( - [array] - $InputObject, - [array] - $CurrentOrder = @(), - [array] - $OutputObject = @() - ) - - process { - if ($InputObject.Count -eq 0) { - # 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.ScopeObject.Scope - # Attempt to remove the resource - $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 - } - } - } - # Return the final result - return $OutputObject - } - else { - if ($InputObject -and $OutputObject) { - # Filter out items already processed successfully - $filteredOutputObject = @() - foreach ($item in $InputObject) { - if ($item.ScopeObject.Scope -in $OutputObject.ScopeObject.Scope) { - foreach ($output in $OutputObject) { - if ($output.ScopeObject.Scope -eq $item.ScopeObject.Scope -and $output.Status -eq 'failed') { - # Add previously failed item to the filtered output - $filteredOutputObject += $output - continue - } - } - } - } - if ($filteredOutputObject) { - $InputObject = $filteredOutputObject - } - } - # Recursive case: Try each item in the current position and recurse with the remaining items - foreach ($item in $InputObject) { - $remainingItems = $InputObject -ne $item - $newOrder = $CurrentOrder + $item - # Recursively call Remove-AzResourceRawRecursive - $OutputObject = Remove-AzResourceRawRecursive -InputObject $remainingItems -CurrentOrder $newOrder -OutputObject $OutputObject - } - # Return the output after all permutations - return $OutputObject - } - } -} \ No newline at end of file diff --git a/src/localized/en-us/Strings.psd1 b/src/localized/en-us/Strings.psd1 index 61cba749..9eac2e32 100644 --- a/src/localized/en-us/Strings.psd1 +++ b/src/localized/en-us/Strings.psd1 @@ -289,6 +289,8 @@ '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.Recursive.Missing' = 'Missing required parameter InputObject, when running Recursive'# + 'Remove-AzResourceRaw.Resource.Missing' = 'Missing required parameter ScopeObject'# '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.Resource, $ScopeObject.Scope 'Remove-AzResourceRawRecursive.Processing' = 'Recursive retry processing to delete resource of type {0} with id {1}'# $item.ScopeObject.Resource, $item.ScopeObject.Scope