From c2e14431da6baab23fb06dba4401f3e4b76b2c95 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Wed, 17 Jul 2024 07:51:23 +0000 Subject: [PATCH 01/10] UpdateBase --- src/AzOps.psd1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AzOps.psd1 b/src/AzOps.psd1 index cbc6166c..163eef7a 100644 --- a/src/AzOps.psd1 +++ b/src/AzOps.psd1 @@ -3,7 +3,7 @@ # # Generated by: Customer Architecture Team (CAT) # -# Generated on: 06/26/2024 +# Generated on: 7/17/2024 # @{ @@ -51,11 +51,11 @@ PowerShellVersion = '7.2' # ProcessorArchitecture = '' # 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.19.0'; }, - @{ModuleName = 'Az.Billing'; RequiredVersion = '2.0.3'; }, - @{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '0.13.1'; }, - @{ModuleName = 'Az.Resources'; RequiredVersion = '6.16.2'; }) +RequiredModules = @(@{ModuleName = 'PSFramework'; RequiredVersion = '1.11.341'; }, + @{ModuleName = 'Az.Accounts'; RequiredVersion = '3.0.2'; }, + @{ModuleName = 'Az.Billing'; RequiredVersion = '2.0.4'; }, + @{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '1.0.0'; }, + @{ModuleName = 'Az.Resources'; RequiredVersion = '7.2.0'; }) # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() From f902fce2b01cb7a3fe12484ab85127c85ff85c9c Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Wed, 17 Jul 2024 08:31:13 +0000 Subject: [PATCH 02/10] Update --- src/internal/functions/Get-AzOpsPolicyAssignment.ps1 | 2 +- src/internal/functions/Get-AzOpsPolicyDefinition.ps1 | 2 +- src/internal/functions/Get-AzOpsPolicyExemption.ps1 | 2 +- src/internal/functions/Get-AzOpsPolicySetDefinition.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/internal/functions/Get-AzOpsPolicyAssignment.ps1 b/src/internal/functions/Get-AzOpsPolicyAssignment.ps1 index 3327d6cd..0f7acd1c 100644 --- a/src/internal/functions/Get-AzOpsPolicyAssignment.ps1 +++ b/src/internal/functions/Get-AzOpsPolicyAssignment.ps1 @@ -18,7 +18,7 @@ Discover all custom policy assignments deployed at Management Group scope #> - [OutputType([Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.Policy.PsPolicyAssignment])] + [OutputType([Microsoft.Azure.PowerShell.Cmdlets.Policy.Models.IPolicyAssignment])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] diff --git a/src/internal/functions/Get-AzOpsPolicyDefinition.ps1 b/src/internal/functions/Get-AzOpsPolicyDefinition.ps1 index dece890c..616d315e 100644 --- a/src/internal/functions/Get-AzOpsPolicyDefinition.ps1 +++ b/src/internal/functions/Get-AzOpsPolicyDefinition.ps1 @@ -14,7 +14,7 @@ Discover all custom policy definitions deployed at Management Group scope #> - [OutputType([Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.Policy.PsPolicyDefinition])] + [OutputType([Microsoft.Azure.PowerShell.Cmdlets.Policy.Models.IPolicyDefinition])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] diff --git a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 index a32e3cb0..2be241d6 100644 --- a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 +++ b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 @@ -12,7 +12,7 @@ Discover all custom policy exemptions deployed at Management Group scope #> - [OutputType([Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.Policy.PsPolicyExemption])] + [OutputType([Microsoft.Azure.PowerShell.Cmdlets.Policy.Models.IPolicyExemption])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] diff --git a/src/internal/functions/Get-AzOpsPolicySetDefinition.ps1 b/src/internal/functions/Get-AzOpsPolicySetDefinition.ps1 index dc568e05..ea1a7491 100644 --- a/src/internal/functions/Get-AzOpsPolicySetDefinition.ps1 +++ b/src/internal/functions/Get-AzOpsPolicySetDefinition.ps1 @@ -14,7 +14,7 @@ Discover all custom policyset definitions deployed at Management Group scope #> - [OutputType([Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.Policy.PsPolicySetDefinition])] + [OutputType([Microsoft.Azure.PowerShell.Cmdlets.Policy.Models.IPolicySetDefinition])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] From 79eb047d6c4e271794e47ad673e262e814080c1b Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Tue, 13 Aug 2024 07:05:44 +0000 Subject: [PATCH 03/10] Update --- src/AzOps.psd1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AzOps.psd1 b/src/AzOps.psd1 index 163eef7a..68006c87 100644 --- a/src/AzOps.psd1 +++ b/src/AzOps.psd1 @@ -3,7 +3,7 @@ # # Generated by: Customer Architecture Team (CAT) # -# Generated on: 7/17/2024 +# Generated on: 8/13/2024 # @{ @@ -51,11 +51,11 @@ PowerShellVersion = '7.2' # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module -RequiredModules = @(@{ModuleName = 'PSFramework'; RequiredVersion = '1.11.341'; }, - @{ModuleName = 'Az.Accounts'; RequiredVersion = '3.0.2'; }, +RequiredModules = @(@{ModuleName = 'PSFramework'; RequiredVersion = '1.11.343'; }, + @{ModuleName = 'Az.Accounts'; RequiredVersion = '3.0.3'; }, @{ModuleName = 'Az.Billing'; RequiredVersion = '2.0.4'; }, @{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '1.0.0'; }, - @{ModuleName = 'Az.Resources'; RequiredVersion = '7.2.0'; }) + @{ModuleName = 'Az.Resources'; RequiredVersion = '7.3.0'; }) # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() From 35b53c5e0a9314977e5f5e22e4f0f7a4b4146094 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Wed, 14 Aug 2024 17:37:32 +0000 Subject: [PATCH 04/10] Update --- .../policyExemptions.jq | 2 +- .../policyExemptions.template.jq | 18 +++++++++++++++--- .../functions/Get-AzOpsPolicyExemption.ps1 | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/data/template/Microsoft.Authorization/policyExemptions.jq b/src/data/template/Microsoft.Authorization/policyExemptions.jq index 0272c482..ce6d0c74 100644 --- a/src/data/template/Microsoft.Authorization/policyExemptions.jq +++ b/src/data/template/Microsoft.Authorization/policyExemptions.jq @@ -1 +1 @@ -del(.ResourceId, .ResourceGroupName, .SubscriptionId, .SystemData, .Properties.CreatedOn, .Properties.UpdatedOn, .Properties.CreatedBy, .Properties.UpdatedBy) \ No newline at end of file +del(.Id, .SystemDataCreatedAt, .SystemDataCreatedBy, .SystemDataCreatedByType, .SystemDataLastModifiedAt, .SystemDataLastModifiedBy, .SystemDataLastModifiedByType) \ No newline at end of file diff --git a/src/data/template/Microsoft.Authorization/policyExemptions.template.jq b/src/data/template/Microsoft.Authorization/policyExemptions.template.jq index 9b0138a6..3a3912c7 100644 --- a/src/data/template/Microsoft.Authorization/policyExemptions.template.jq +++ b/src/data/template/Microsoft.Authorization/policyExemptions.template.jq @@ -10,11 +10,23 @@ "variables": {}, "resources": [ { - "type": .ResourceType, + "type": .Type, "name": .Name, "apiVersion": "0000-00-00", - "properties": .Properties + "properties": { + "assignmentScopeValidation": .AssignmentScopeValidation, + "description": .Description, + "displayName": .DisplayName, + "exemptionCategory": .ExemptionCategory, + "expiresOn": .ExpiresOn, + "metadata": .Metadata, + "policyAssignmentId": .PolicyAssignmentId, + "policyDefinitionReferenceIds": .PolicyDefinitionReferenceIds, + "resourceSelector": .ResourceSelector, + } } ], "outputs": {} -} \ No newline at end of file +} +| del(.. | select(. == null)) +| del(.. | select(. == "")) \ No newline at end of file diff --git a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 index 2be241d6..c381469f 100644 --- a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 +++ b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 @@ -42,7 +42,7 @@ } # Gather policyExemption with retry and backoff support from Invoke-AzOpsScriptBlock Invoke-AzOpsScriptBlock -ArgumentList $parameters -ScriptBlock { - Get-AzPolicyExemption @parameters -WarningAction SilentlyContinue -ErrorAction Stop | Where-Object ResourceId -match $parameters.Scope + Get-AzPolicyExemption @parameters -WarningAction SilentlyContinue -ErrorAction Stop | Where-Object Id -match $parameters.Scope } -RetryCount 3 -RetryWait 5 -RetryType Exponential -ErrorAction Stop } catch { From 0537e152cf4875e51aa23e29763a1a08e9ecbdc3 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Wed, 14 Aug 2024 20:14:52 +0000 Subject: [PATCH 05/10] Update --- .../policyExemptions.jq | 2 +- .../policyExemptions.template.jq | 32 ------------ src/internal/functions/Get-AzOpsPolicy.ps1 | 4 ++ .../functions/Get-AzOpsPolicyExemption.ps1 | 51 ++++++++++++------- .../functions/Get-AzOpsResourceDefinition.ps1 | 30 +++-------- 5 files changed, 44 insertions(+), 75 deletions(-) delete mode 100644 src/data/template/Microsoft.Authorization/policyExemptions.template.jq diff --git a/src/data/template/Microsoft.Authorization/policyExemptions.jq b/src/data/template/Microsoft.Authorization/policyExemptions.jq index ce6d0c74..1a566688 100644 --- a/src/data/template/Microsoft.Authorization/policyExemptions.jq +++ b/src/data/template/Microsoft.Authorization/policyExemptions.jq @@ -1 +1 @@ -del(.Id, .SystemDataCreatedAt, .SystemDataCreatedBy, .SystemDataCreatedByType, .SystemDataLastModifiedAt, .SystemDataLastModifiedBy, .SystemDataLastModifiedByType) \ No newline at end of file +del(.ResourceId, .resourceGroup, .subscriptionId, .properties.metadata.createdOn, .properties.metadata.updatedOn, .properties.metadata.createdBy, .properties.metadata.updatedBy, .properties.metadata.assignedBy) \ No newline at end of file diff --git a/src/data/template/Microsoft.Authorization/policyExemptions.template.jq b/src/data/template/Microsoft.Authorization/policyExemptions.template.jq deleted file mode 100644 index 3a3912c7..00000000 --- a/src/data/template/Microsoft.Authorization/policyExemptions.template.jq +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "AzOps" - } - }, - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": .Type, - "name": .Name, - "apiVersion": "0000-00-00", - "properties": { - "assignmentScopeValidation": .AssignmentScopeValidation, - "description": .Description, - "displayName": .DisplayName, - "exemptionCategory": .ExemptionCategory, - "expiresOn": .ExpiresOn, - "metadata": .Metadata, - "policyAssignmentId": .PolicyAssignmentId, - "policyDefinitionReferenceIds": .PolicyDefinitionReferenceIds, - "resourceSelector": .ResourceSelector, - } - } - ], - "outputs": {} -} -| del(.. | select(. == null)) -| del(.. | select(. == "")) \ No newline at end of file diff --git a/src/internal/functions/Get-AzOpsPolicy.ps1 b/src/internal/functions/Get-AzOpsPolicy.ps1 index 15ae5ab8..82f32773 100644 --- a/src/internal/functions/Get-AzOpsPolicy.ps1 +++ b/src/internal/functions/Get-AzOpsPolicy.ps1 @@ -54,6 +54,10 @@ Write-AzOpsMessage -LogLevel Verbose -LogString 'Get-AzOpsResourceDefinition.Processing.Detail' -LogStringValues 'Policy Assignments', $ScopeObject.Scope $policyAssignments = Get-AzOpsPolicyAssignment -ScopeObject $ScopeObject -Subscription $Subscription -SubscriptionsToIncludeResourceGroups $SubscriptionsToIncludeResourceGroups -ResourceGroup $ResourceGroup $policyAssignments | ConvertTo-AzOpsState -StatePath $StatePath + # Process policy exemptions + Write-AzOpsMessage -LogLevel Verbose -LogString 'Get-AzOpsResourceDefinition.Processing.Detail' -LogStringValues 'Policy Exemptions', $ScopeObject.Scope + $policyExemptions = Get-AzOpsPolicyExemption -ScopeObject $ScopeObject -Subscription $Subscription -SubscriptionsToIncludeResourceGroups $SubscriptionsToIncludeResourceGroups -ResourceGroup $ResourceGroup + $policyExemptions | ConvertTo-AzOpsState -StatePath $StatePath } } \ No newline at end of file diff --git a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 index c381469f..6e5a8bb5 100644 --- a/src/internal/functions/Get-AzOpsPolicyExemption.ps1 +++ b/src/internal/functions/Get-AzOpsPolicyExemption.ps1 @@ -7,6 +7,12 @@ Discover all custom policy exemptions at the provided scope (Management Groups, subscriptions or resource groups) .PARAMETER ScopeObject The scope object representing the azure entity to retrieve excemptions for. + .PARAMETER Subscription + Complete Subscription list + .PARAMETER SubscriptionsToIncludeResourceGroups + Scoped Subscription list + .PARAMETER ResourceGroup + ResourceGroup switch indicating desired scope condition .EXAMPLE > Get-AzOpsPolicyExemption -ScopeObject (New-AzOpsScope -Scope /providers/Microsoft.Management/managementGroups/contoso -StatePath $StatePath) Discover all custom policy exemptions deployed at Management Group scope @@ -17,36 +23,45 @@ param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Object] - $ScopeObject + $ScopeObject, + [Parameter(Mandatory = $false)] + [object] + $Subscription, + [Parameter(Mandatory = $false)] + [object] + $SubscriptionsToIncludeResourceGroups, + [Parameter(Mandatory = $false)] + [bool] + $ResourceGroup ) process { if ($ScopeObject.Type -notin 'resourceGroups', 'subscriptions', 'managementGroups') { return } - - switch ($ScopeObject.Type) { - managementGroups { - Write-AzOpsMessage -LogLevel Debug -LogString 'Get-AzOpsPolicyExemption.ManagementGroup' -LogStringValues $ScopeObject.ManagementGroupDisplayName, $ScopeObject.ManagementGroup -Target $ScopeObject + if ($ScopeObject.Type -eq 'managementGroups') { + Write-AzOpsMessage -LogLevel Debug -LogString 'Get-AzOpsPolicyExemption.ManagementGroup' -LogStringValues $ScopeObject.ManagementGroupDisplayName, $ScopeObject.ManagementGroup -Target $ScopeObject + if ((-not $SubscriptionsToIncludeResourceGroups) -or (-not $ResourceGroups)) { + $query = "policyresources | where type == 'microsoft.authorization/policyexemptions' and resourceGroup == '' and subscriptionId == '' | order by ['id'] asc" + Search-AzOpsAzGraph -ManagementGroupName $ScopeObject.Name -Query $query -ErrorAction Stop } - subscriptions { + } + if ($Subscription) { + if ($SubscriptionsToIncludeResourceGroups -and $ResourceGroup) { Write-AzOpsMessage -LogLevel Debug -LogString 'Get-AzOpsPolicyExemption.Subscription' -LogStringValues $ScopeObject.SubscriptionDisplayName, $ScopeObject.Subscription -Target $ScopeObject + $query = "policyresources | where type == 'microsoft.authorization/policyexemptions' and resourceGroup != '' | order by ['id'] asc" + Search-AzOpsAzGraph -Subscription $SubscriptionsToIncludeResourceGroups -Query $query -ErrorAction Stop } - resourcegroups { + elseif ($ResourceGroup) { Write-AzOpsMessage -LogLevel Debug -LogString 'Get-AzOpsPolicyExemption.ResourceGroup' -LogStringValues $ScopeObject.ResourceGroup -Target $ScopeObject + $query = "policyresources | where type == 'microsoft.authorization/policyexemptions' and resourceGroup != '' | order by ['id'] asc" + Search-AzOpsAzGraph -Subscription $Subscription -Query $query -ErrorAction Stop } - } - try { - $parameters = @{ - Scope = $ScopeObject.Scope + else { + Write-AzOpsMessage -LogLevel Debug -LogString 'Get-AzOpsPolicyExemption.Subscription' -LogStringValues $ScopeObject.SubscriptionDisplayName, $ScopeObject.Subscription -Target $ScopeObject + $query = "policyresources | where type == 'microsoft.authorization/policyexemptions' and resourceGroup == '' | order by ['id'] asc" + Search-AzOpsAzGraph -Subscription $Subscription -Query $query -ErrorAction Stop } - # Gather policyExemption with retry and backoff support from Invoke-AzOpsScriptBlock - Invoke-AzOpsScriptBlock -ArgumentList $parameters -ScriptBlock { - Get-AzPolicyExemption @parameters -WarningAction SilentlyContinue -ErrorAction Stop | Where-Object Id -match $parameters.Scope - } -RetryCount 3 -RetryWait 5 -RetryType Exponential -ErrorAction Stop - } - catch { - Write-AzOpsMessage -LogLevel Warning -LogString 'Get-AzOpsPolicyExemption.Failed' -LogStringValues $ScopeObject.Scope } } diff --git a/src/internal/functions/Get-AzOpsResourceDefinition.ps1 b/src/internal/functions/Get-AzOpsResourceDefinition.ps1 index 59b67af4..b1f88d39 100644 --- a/src/internal/functions/Get-AzOpsResourceDefinition.ps1 +++ b/src/internal/functions/Get-AzOpsResourceDefinition.ps1 @@ -152,19 +152,13 @@ $script:AzOpsPartialRoot = $runspaceData.runspace_AzOpsPartialRoot $script:AzOpsResourceProvider = $runspaceData.runspace_AzOpsResourceProvider } - # Process Privileged Identity Management resources, Policies and Roles at managementGroup scope - if ((-not $using:SkipPim) -or (-not $using:SkipPolicy) -or (-not $using:SkipRole)) { + # Process Privileged Identity Management resources and Roles at managementGroup scope + if ((-not $using:SkipPim) -or (-not $using:SkipRole)) { & $azOps { $ScopeObject = New-AzOpsScope -Scope $managementgroup.id -StatePath $runspaceData.Statepath -ErrorAction Stop if (-not $using:SkipPim) { Get-AzOpsPim -ScopeObject $ScopeObject -StatePath $runspaceData.Statepath } - if (-not $using:SkipPolicy) { - $policyExemptions = Get-AzOpsPolicyExemption -ScopeObject $ScopeObject - if ($policyExemptions) { - $policyExemptions | ConvertTo-AzOpsState -StatePath $runspaceData.Statepath - } - } if (-not $using:SkipRole) { Get-AzOpsRole -ScopeObject $ScopeObject -StatePath $runspaceData.Statepath } @@ -196,19 +190,13 @@ $script:AzOpsPartialRoot = $runspaceData.runspace_AzOpsPartialRoot $script:AzOpsResourceProvider = $runspaceData.runspace_AzOpsResourceProvider } - # Process Privileged Identity Management resources, Policies, Locks and Roles at subscription scope - if ((-not $using:SkipPim) -or (-not $using:SkipPolicy) -or (-not $using:SkipLock) -or (-not $using:SkipRole)) { + # Process Privileged Identity Management resources, Locks and Roles at subscription scope + if ((-not $using:SkipPim) -or (-not $using:SkipLock) -or (-not $using:SkipRole)) { & $azOps { $scopeObject = New-AzOpsScope -Scope ($subscription.Type + '/' + $subscription.Id) -StatePath $runspaceData.Statepath -ErrorAction Stop if (-not $using:SkipPim) { Get-AzOpsPim -ScopeObject $scopeObject -StatePath $runspaceData.Statepath } - if (-not $using:SkipPolicy) { - $policyExemptions = Get-AzOpsPolicyExemption -ScopeObject $scopeObject - if ($policyExemptions) { - $policyExemptions | ConvertTo-AzOpsState -StatePath $runspaceData.Statepath - } - } if (-not $using:SkipLock) { Get-AzOpsResourceLock -ScopeObject $scopeObject -StatePath $runspaceData.Statepath } @@ -264,8 +252,8 @@ & $azOps { ConvertTo-AzOpsState -Resource $resourceGroup -StatePath $runspaceData.Statepath } - # Process Privileged Identity Management resources, Policies, Locks and Roles at resource group scope - if ((-not $using:SkipPim) -or (-not $using:SkipPolicy) -or (-not $using:SkipRole) -or (-not $using:SkipLock)) { + # Process Privileged Identity Management resources, Locks and Roles at resource group scope + if ((-not $using:SkipPim) -or (-not $using:SkipRole) -or (-not $using:SkipLock)) { & $azOps { $rgScopeObject = New-AzOpsScope -Scope $resourceGroup.id -StatePath $runspaceData.Statepath -ErrorAction Stop if (-not $using:SkipLock) { @@ -274,12 +262,6 @@ if (-not $using:SkipPim) { Get-AzOpsPim -ScopeObject $rgScopeObject -StatePath $runspaceData.Statepath } - if (-not $using:SkipPolicy) { - $policyExemptions = Get-AzOpsPolicyExemption -ScopeObject $rgScopeObject - if ($policyExemptions) { - $policyExemptions | ConvertTo-AzOpsState -StatePath $runspaceData.Statepath - } - } if (-not $using:SkipRole) { Get-AzOpsRole -ScopeObject $rgScopeObject -StatePath $runspaceData.Statepath } From 0199b22ae6aad33f79d4152669fb4096c0927b02 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Thu, 15 Aug 2024 18:05:37 +0000 Subject: [PATCH 06/10] Update --- src/tests/integration/Repository.Tests.ps1 | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/tests/integration/Repository.Tests.ps1 b/src/tests/integration/Repository.Tests.ps1 index 6eeb777e..577aec67 100644 --- a/src/tests/integration/Repository.Tests.ps1 +++ b/src/tests/integration/Repository.Tests.ps1 @@ -664,7 +664,7 @@ Describe "Repository" { $policyAssignmentDeployment.ProvisioningState | Should -Be "Succeeded" } It "Policy Assignments deletion should be successful" { - $policyAssignmentDeletion = Get-AzPolicyAssignment -Id $script:policyAssignments.PolicyAssignmentId -ErrorAction SilentlyContinue + $policyAssignmentDeletion = Get-AzPolicyAssignment -Id $script:policyAssignments.Id -ErrorAction SilentlyContinue $policyAssignmentDeletion | Should -Be $Null } #endregion @@ -701,7 +701,7 @@ Describe "Repository" { $fileContents.resources[0].identity.userAssignedIdentities | Should -BeTrue } It "Policy Assignments with UAM deployment should be successful" { - $script:policyAssignmentUamDeployment = Get-AzResourceGroupDeployment -Name $script:policyAssignmentsUamDeploymentName -ResourceGroupName $script:policyAssignmentsUam.ResourceGroupName + $script:policyAssignmentUamDeployment = Get-AzResourceGroupDeployment -Name $script:policyAssignmentsUamDeploymentName -ResourceGroupName ($script:policyAssignmentsUam.Scope).Split('/')[-1] $policyAssignmentUamDeployment.ProvisioningState | Should -Be "Succeeded" } #endregion @@ -734,12 +734,14 @@ Describe "Repository" { $policyDefinitionDeployment.ProvisioningState | Should -Be "Succeeded" } It "Policy Definitions deletion should be successful" { - $policyDefinitionDeletion = Get-AzPolicyDefinition -Id $script:policyDefinitions.PolicyDefinitionId -ErrorAction SilentlyContinue - if ($policyDefinitionDeletion) { - $policyDefinitionDeletion.PolicyDefinitionId[0] | Should -Not -Be $script:policyDefinitions.PolicyDefinitionId + try { + $policyDefinitionDeletion = Get-AzPolicyDefinition -Id $script:policyDefinitions.Id -ErrorAction Stop + if ($policyDefinitionDeletion) { + $policyDefinitionDeletion.Id[0] | Should -Not -Be $script:policyDefinitions.Id + } } - else { - $policyDefinitionDeletion | Should -Be $Null + catch { + $_.Exception.Message | Should -Match "\[PolicyDefinitionNotFound\] : The policy definition '.*' could not be found." } } #endregion @@ -772,12 +774,14 @@ Describe "Repository" { $policySetDefinitionDeployment.ProvisioningState | Should -Be "Succeeded" } It "PolicySetDefinitions deletion should be successful" { - $policySetDefinitionDeletion = Get-AzPolicySetDefinition -Id $script:policySetDefinitions.PolicySetDefinitionId -ErrorAction SilentlyContinue - if ($policySetDefinitionDeletion) { - $policySetDefinitionDeletion.PolicySetDefinitionId[0] | Should -Not -Be $script:policySetDefinitions.PolicySetDefinitionId + try { + $policySetDefinitionDeletion = Get-AzPolicySetDefinition -Id $script:policySetDefinitions.Id -ErrorAction Stop + if ($policySetDefinitionDeletion) { + $policySetDefinitionDeletion.Id[0] | Should -Not -Be $script:policySetDefinitions.Id + } } - else { - $policySetDefinitionDeletion | Should -Be $Null + catch { + $_.Exception.Message | Should -Match "\[PolicySetDefinitionNotFound\] : The policy set definition '.*' could not be found." } } #endregion @@ -1318,7 +1322,7 @@ Describe "Repository" { Set-PSFConfig -FullName AzOps.Core.CustomTemplateResourceDeletion -Value $false Start-Sleep -Seconds 30 (Get-AzResource -ResourceGroupName $script:resourceGroupCustomDeletion.ResourceGroupName).Count | Should -Be 0 - Get-AzPolicyAssignment -Id $script:policyAssignmentsDeletion.ResourceId -ErrorAction SilentlyContinue | Should -Be $Null + Get-AzPolicyAssignment -Id $script:policyAssignmentsDeletion.Id -ErrorAction SilentlyContinue | Should -Be $Null } #endregion } From 19e5e151845ce10b0ee59d343c11dfaa7994d7c2 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Thu, 22 Aug 2024 08:53:07 +0000 Subject: [PATCH 07/10] Update --- .../functions/Remove-AzOpsDeployment.ps1 | 72 ++++++++++--------- src/localized/en-us/Strings.psd1 | 2 +- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/internal/functions/Remove-AzOpsDeployment.ps1 b/src/internal/functions/Remove-AzOpsDeployment.ps1 index b969fd6b..372a8796 100644 --- a/src/internal/functions/Remove-AzOpsDeployment.ps1 +++ b/src/internal/functions/Remove-AzOpsDeployment.ps1 @@ -59,31 +59,34 @@ $resourceToDelete ) $dependency = @() - if ($resourceToDelete.ResourceType -in $DeletionSupportedResourceType) { - if ($resourceToDelete.SubscriptionId) { - $depLock = Get-AzResourceLock -Scope "/subscriptions/$($resourceToDelete.SubscriptionId)" + if ($resourceToDelete.Type -in $DeletionSupportedResourceType) { + $subPattern = '^/subscriptions/([0-9a-fA-F-]{36})' + $rgPattern = '^/subscriptions/([0-9a-fA-F-]{36})/resourceGroups/([^/]+)' + if ($resourceToDelete.Id -match $subPattern) { + $deletionScope = $matches[0] + $depLock = Get-AzResourceLock -Scope $deletionScope if ($depLock) { foreach ($lock in $depLock) { - if ($lock.ResourceGroupName -eq $resourceToDelete.ResourceGroupName) { - #Filter through each return and validate resource is not at child resource scope - if ($lock.ResourceId -notlike '*/resourcegroups/*/providers/*/providers/*') { + #Filter through each return and validate if resource has rg and is not at child resource scope + if ($lock.ResourceId -match $rgPattern) { + if ($resourceToDelete.Id.StartsWith($matches[0]) -and $lock.ResourceId -notlike '*/resourcegroups/*/providers/*/providers/*') { $dependency += [PSCustomObject]@{ - ResourceType = 'locks' - ResourceId = $lock.ResourceId + Type = 'locks' + Id = $lock.ResourceId } } } elseif ($lock.ResourceId -notlike '*/resourcegroups/*') { $dependency += [PSCustomObject]@{ - ResourceType = 'locks' - ResourceId = $lock.ResourceId + Type = 'locks' + Id = $lock.ResourceId } } } - } - if ($dependency) { - $dependency = $dependency | Sort-Object ResourceId -Unique - return $dependency + if ($dependency) { + $dependency = $dependency | Sort-Object Id -Unique | Where-Object {$_.Id -ne $resourceToDelete.Id} + return $dependency + } } } } @@ -93,16 +96,16 @@ $resourceToDelete ) $dependency = @() - if ($resourceToDelete.ResourceType -in $DeletionSupportedResourceType) { - switch ($resourceToDelete.ResourceType) { + if ($resourceToDelete.Type -in $DeletionSupportedResourceType) { + switch ($resourceToDelete.Type) { 'Microsoft.Authorization/policyAssignments' { $depPolicyAssignment = $resourceToDelete } 'Microsoft.Authorization/policyDefinitions' { - $depPolicyAssignment = Get-AzPolicyAssignment -PolicyDefinitionId $resourceToDelete.PolicyDefinitionId -ErrorAction SilentlyContinue + $depPolicyAssignment = Get-AzPolicyAssignment -PolicyDefinitionId $resourceToDelete.Id -ErrorAction SilentlyContinue } 'Microsoft.Authorization/policySetDefinitions' { - $query = "PolicyResources | where type == 'microsoft.authorization/policyassignments' and properties.policyDefinitionId == '$($resourceToDelete.PolicySetDefinitionId)' | order by id asc" + $query = "PolicyResources | where type == 'microsoft.authorization/policyassignments' and properties.policyDefinitionId == '$($resourceToDelete.Id)' | order by id asc" $depPolicyAssignment = Search-AzGraphDeletionDependency -query $query if ($depPolicyAssignment) { #Loop through each return from graph cache and validate resource is still present in Azure @@ -114,23 +117,23 @@ if ($depPolicyAssignment) { foreach ($policyAssignment in $depPolicyAssignment) { $dependency += [PSCustomObject]@{ - ResourceType = $policyAssignment.ResourceType - ResourceId = $policyAssignment.ResourceId + Type = $policyAssignment.Type + Id = $policyAssignment.Id } - if ($policyAssignment.Identity.IdentityType -eq 'SystemAssigned') { + if ($policyAssignment.IdentityType -eq 'SystemAssigned') { $depSystemAssignedRoleAssignment = $null - $depSystemAssignedRoleAssignment = Get-AzRoleAssignment -ObjectId $policyAssignment.Identity.PrincipalId -Scope $policyAssignment.Properties.Scope + $depSystemAssignedRoleAssignment = Get-AzRoleAssignment -ObjectId $policyAssignment.IdentityPrincipalId -Scope $policyAssignment.Scope if ($depSystemAssignedRoleAssignment) { foreach ($roleAssignmentId in $depSystemAssignedRoleAssignment.RoleAssignmentId) { #Filter through each return and validate resource is not at child resource scope if ($roleAssignmentId -notlike '*/resourcegroups/*/providers/*/providers/*') { $dependency += [PSCustomObject]@{ - ResourceType = 'roleAssignments' - ResourceId = $roleAssignmentId + Type = 'roleAssignments' + Id = $roleAssignmentId } } else { - Write-AzOpsMessage -LogLevel Warning -LogString 'Remove-AzOpsDeployment.ResourceDependencyNested' -LogStringValues $roleAssignmentId, $policyAssignment.ResourceId + Write-AzOpsMessage -LogLevel Warning -LogString 'Remove-AzOpsDeployment.ResourceDependencyNested' -LogStringValues $roleAssignmentId, $policyAssignment.Id } } } @@ -138,7 +141,7 @@ } } if ($dependency) { - $dependency = $dependency | Sort-Object ResourceId -Unique | Where-Object {$_.ResourceId -ne $resourceToDelete.ResourceId} + $dependency = $dependency | Sort-Object Id -Unique | Where-Object {$_.Id -ne $resourceToDelete.Id} return $dependency } } @@ -146,7 +149,7 @@ param ( $resourceToDelete ) - if ($resourceToDelete.ResourceType -eq 'Microsoft.Authorization/policyDefinitions') { + if ($resourceToDelete.Type -eq 'Microsoft.Authorization/policyDefinitions') { $dependency = @() $query = "PolicyResources | where type == 'microsoft.authorization/policysetdefinitions' and properties.policyType == 'Custom' | project id, type, policyDefinitions = (properties.policyDefinitions) | mv-expand policyDefinitions | project id, type, policyDefinitionId = tostring(policyDefinitions.policyDefinitionId) | where policyDefinitionId == '$($resourceToDelete.PolicyDefinitionId)' | order by policyDefinitionId asc | order by id asc" $depPolicySetDefinition = Search-AzGraphDeletionDependency -query $query @@ -156,8 +159,8 @@ $policy = Get-AzPolicySetDefinition -Id $policySetDefinition.Id -ErrorAction SilentlyContinue if ($policy) { $dependency += [PSCustomObject]@{ - ResourceType = $policy.ResourceType - ResourceId = $policy.PolicySetDefinitionId + Type = $policy.Type + Id = $policy.Id } $dependency += Get-AzPolicyAssignmentDeletionDependency -resourceToDelete $policy } @@ -280,7 +283,7 @@ } } '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 }).Content | ConvertFrom-Json -Depth 100 if ($resourceToDelete) { $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } @@ -288,8 +291,9 @@ 'resourceGroups' { $resourceToDelete = Get-AzResourceGroup -Id $scopeObject.Scope -ErrorAction SilentlyContinue if ($resourceToDelete) { - $resourceToDelete | Add-Member -MemberType NoteProperty -Name "ResourceType" -Value "$($scopeObject.Type)" + $resourceToDelete | Add-Member -MemberType NoteProperty -Name "Type" -Value "$($scopeObject.Type)" $resourceToDelete | Add-Member -MemberType NoteProperty -Name "SubscriptionId" -Value "$($scopeObject.Subscription)" + $resourceToDelete | Add-Member -MemberType NoteProperty -Name "Id" -Value "$($resourceToDelete.ResourceId)" $dependency += Get-AzLocksDeletionDependency -resourceToDelete $resourceToDelete } } @@ -303,9 +307,9 @@ } if ($dependency) { foreach ($resource in $dependency) { - if ($resource.ResourceId -notin $deletionList.ScopeObject.Scope) { - Write-AzOpsMessage -LogLevel Critical -LogString 'Remove-AzOpsDeployment.ResourceDependencyNotFound' -LogStringValues $resource.ResourceId, $scopeObject.Scope - $results = 'Missing resource dependency:{2}{0} for successful deletion of {1}.{2}{2}Please add dependent resource to pull request and retry.' -f $resource.ResourceId, $scopeObject.scope, [environment]::NewLine + if ($resource.Id -notin $deletionList.ScopeObject.Scope) { + Write-AzOpsMessage -LogLevel Critical -LogString 'Remove-AzOpsDeployment.ResourceDependencyNotFound' -LogStringValues $resource.Id, $scopeObject.Scope + $results = 'Missing resource dependency:{2}{0} for successful deletion of {1}.{2}{2}Please add dependent resource to pull request and retry.' -f $resource.Id, $scopeObject.scope, [environment]::NewLine Set-AzOpsWhatIfOutput -FilePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true $dependencyMissing = [PSCustomObject]@{ dependencyMissing = $true diff --git a/src/localized/en-us/Strings.psd1 b/src/localized/en-us/Strings.psd1 index 11b19e16..8571b4d4 100644 --- a/src/localized/en-us/Strings.psd1 +++ b/src/localized/en-us/Strings.psd1 @@ -286,7 +286,7 @@ 'Remove-AzOpsDeployment.Scope.Empty' = 'Unable to determine the scope of template {0}' # $TemplateFilePath 'Remove-AzOpsDeployment.SkipDueToWhatIf' = 'Skipping removal of resource due to WhatIf' # '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.ResourceDependencyNotFound' = 'Missing resource dependency {0} for successfull deletion of {1}. Please add missing resource and retry.'# $resource.Id, $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.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 From fab74432538b1c573c68067123d9a30d3cdccce6 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Thu, 22 Aug 2024 10:54:34 +0000 Subject: [PATCH 08/10] Update --- src/localized/en-us/Strings.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/localized/en-us/Strings.psd1 b/src/localized/en-us/Strings.psd1 index 8571b4d4..aa992207 100644 --- a/src/localized/en-us/Strings.psd1 +++ b/src/localized/en-us/Strings.psd1 @@ -97,7 +97,6 @@ 'Get-AzOpsPolicyExemption.ManagementGroup' = 'Retrieving Policy Exemption for Management Group {0} ({1})' # $ScopeObject.ManagementGroupDisplayName, $ScopeObject.ManagementGroup 'Get-AzOpsPolicyExemption.ResourceGroup' = 'Retrieving Policy Exemption for Resource Group {0}' # $ScopeObject.ResourceGroup 'Get-AzOpsPolicyExemption.Subscription' = 'Retrieving Policy Exemption for Subscription {0} ({1})' # $ScopeObject.SubscriptionDisplayName, $ScopeObject.Subscription - 'Get-AzOpsPolicyExemption.Failed' = 'Retrieving Policy Exemption failed at {0}' # $ScopeObject.Scope 'Get-AzOpsResourceLock.ResourceGroup' = 'Retrieving Resource Locks for Resource Group {0}' # $ScopeObject.ResourceGroup 'Get-AzOpsResourceLock.Failed' = 'Failed retrieving Resource Locks {0}' # $_ From ec096bc434180dc714768cb64d1cf789e3ecf7f4 Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Thu, 22 Aug 2024 15:08:21 +0000 Subject: [PATCH 09/10] Update --- scripts/Remove-AzOpsTestsDeployment.ps1 | 1 + src/internal/functions/Remove-AzOpsDeployment.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/Remove-AzOpsTestsDeployment.ps1 b/scripts/Remove-AzOpsTestsDeployment.ps1 index b19f365b..ad289212 100644 --- a/scripts/Remove-AzOpsTestsDeployment.ps1 +++ b/scripts/Remove-AzOpsTestsDeployment.ps1 @@ -69,6 +69,7 @@ foreach ($subscription in $cleanupSub) { $null = Set-AzContext -SubscriptionId $subscription.Id $null = Get-AzResourceLock | Remove-AzResourceLock -Force + Start-Sleep -Seconds 15 $script:resourceGroups = Get-AzResourceGroup | Where-Object {$_.ResourceGroupName -like "*-azopsrg"} $script:roleAssignmentsCleanBase = Get-AzRoleAssignment | Where-Object {$_.Scope -ne "/"} $script:roleAssignments = foreach ($roleAssignment in $script:roleAssignmentsCleanBase) { diff --git a/src/internal/functions/Remove-AzOpsDeployment.ps1 b/src/internal/functions/Remove-AzOpsDeployment.ps1 index 372a8796..ff3d9271 100644 --- a/src/internal/functions/Remove-AzOpsDeployment.ps1 +++ b/src/internal/functions/Remove-AzOpsDeployment.ps1 @@ -151,7 +151,7 @@ ) if ($resourceToDelete.Type -eq 'Microsoft.Authorization/policyDefinitions') { $dependency = @() - $query = "PolicyResources | where type == 'microsoft.authorization/policysetdefinitions' and properties.policyType == 'Custom' | project id, type, policyDefinitions = (properties.policyDefinitions) | mv-expand policyDefinitions | project id, type, policyDefinitionId = tostring(policyDefinitions.policyDefinitionId) | where policyDefinitionId == '$($resourceToDelete.PolicyDefinitionId)' | order by policyDefinitionId asc | order by id asc" + $query = "PolicyResources | where type == 'microsoft.authorization/policysetdefinitions' and properties.policyType == 'Custom' | project id, type, policyDefinitions = (properties.policyDefinitions) | mv-expand policyDefinitions | project id, type, policyDefinitionId = tostring(policyDefinitions.policyDefinitionId) | where policyDefinitionId == '$($resourceToDelete.Id)' | order by policyDefinitionId asc | order by id asc" $depPolicySetDefinition = Search-AzGraphDeletionDependency -query $query if ($depPolicySetDefinition) { $depPolicySetDefinition = foreach ($policySetDefinition in $depPolicySetDefinition) { @@ -167,7 +167,7 @@ } } if ($dependency) { - $dependency = $dependency | Sort-Object ResourceId -Unique | Where-Object {$_.ResourceId -ne $resourceToDelete.ResourceId} + $dependency = $dependency | Sort-Object Id -Unique | Where-Object {$_.Id -ne $resourceToDelete.Id} return $dependency } } From 561cc7b193053a74e72777d62897ff946592858d Mon Sep 17 00:00:00 2001 From: Jesper Fajers Date: Tue, 3 Sep 2024 14:57:53 +0000 Subject: [PATCH 10/10] Update --- src/AzOps.psd1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AzOps.psd1 b/src/AzOps.psd1 index 68006c87..a0c10dcb 100644 --- a/src/AzOps.psd1 +++ b/src/AzOps.psd1 @@ -3,7 +3,7 @@ # # Generated by: Customer Architecture Team (CAT) # -# Generated on: 8/13/2024 +# Generated on: 9/3/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.11.343'; }, - @{ModuleName = 'Az.Accounts'; RequiredVersion = '3.0.3'; }, + @{ModuleName = 'Az.Accounts'; RequiredVersion = '3.0.4'; }, @{ModuleName = 'Az.Billing'; RequiredVersion = '2.0.4'; }, @{ModuleName = 'Az.ResourceGraph'; RequiredVersion = '1.0.0'; }, - @{ModuleName = 'Az.Resources'; RequiredVersion = '7.3.0'; }) + @{ModuleName = 'Az.Resources'; RequiredVersion = '7.4.0'; }) # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @()