Public/Update-AzRoleAccessGuard.ps1
|
function Update-AzRoleAccessGuard { <# .SYNOPSIS Reconciles Azure role assignments using a drift report. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # Drift report produced by Invoke-AzRoleAccessGuardDrifft. [ValidateNotNullOrEmpty()] [string]$DriftFile = (Join-Path (Split-Path -Parent $PSScriptRoot) 'output_diff.json'), # Suppress confirmation prompts for unattended remediation. -WhatIf remains supported. [switch]$Force ) if ($Force) { $ConfirmPreference = 'None' } if (-not (Test-Path -LiteralPath $DriftFile -PathType Leaf)) { throw "Drift report '$DriftFile' was not found." } try { $drift_report = Get-Content -LiteralPath $DriftFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { throw "Unable to read drift report '$DriftFile': $($_.Exception.Message)" } if ($drift_report -is [System.Array] -or $drift_report.PSObject.Properties.Name -contains 'resources') { $normalized_report = [ordered]@{ ManagementGroup = [ordered]@{} Subscription = [ordered]@{} } foreach ($document in @($drift_report)) { $section_name = switch ($document.displayName) { 'ManagementGroups' { 'ManagementGroup'; break } 'Subscriptions' { 'Subscription'; break } default { continue } } foreach ($resource in @($document.resources)) { $properties = $resource.properties if (-not $properties -or -not $properties.ScopeId -or -not $properties.Status) { Write-Warning "Skipping invalid role assignment in document '$($document.displayName)'." continue } $scope_id = [string]$properties.ScopeId if (-not $normalized_report[$section_name].Contains($scope_id)) { $normalized_report[$section_name][$scope_id] = @() } $normalized_report[$section_name][$scope_id] = @($normalized_report[$section_name][$scope_id]) + [PSCustomObject]@{ Status = $properties.Status SuggestedAction = $properties.SuggestedAction Ensure = $properties.Ensure Assignment = [PSCustomObject]@{ Scope = if ($section_name -eq 'Subscription' -and -not ([string]$properties.ScopeId).StartsWith('/')) { "/subscriptions/$($properties.ScopeId)" } else { $properties.ScopeId } ObjectId = $properties.ObjectId RoleDefinitionName = $properties.RoleDefinitionName } } } } $drift_report = [PSCustomObject]@{ ManagementGroup = [PSCustomObject]$normalized_report.ManagementGroup Subscription = [PSCustomObject]$normalized_report.Subscription } } $results = @() foreach ($section_name in 'ManagementGroup', 'Subscription') { $section = $drift_report.$section_name if (-not $section) { continue } foreach ($scope_property in $section.PSObject.Properties) { foreach ($entry in @($scope_property.Value)) { $assignment = $entry.Assignment if (-not $assignment -or -not $assignment.ObjectId -or -not $assignment.RoleDefinitionName -or -not $assignment.Scope) { Write-Warning "Skipping invalid $section_name drift entry at '$($scope_property.Name)'." continue } $target = "$($assignment.ObjectId) on $($assignment.Scope) ($($assignment.RoleDefinitionName))" $ensure = if ($entry.Ensure) { [string]$entry.Ensure } else { switch ($entry.Status) { 'Added' { 'Absent'; break } 'Removed' { 'Present'; break } } } $action = switch ($ensure) { 'Absent' { 'Remove role assignment'; break } 'Present' { 'Add role assignment'; break } default { Write-Warning "Skipping '$target' because Ensure '$ensure' is not supported." continue } } if ($PSCmdlet.ShouldProcess($target, $action)) { if ($ensure -eq 'Absent') { Remove-AzRoleAssignment -ObjectId $assignment.ObjectId -Scope $assignment.Scope -RoleDefinitionName $assignment.RoleDefinitionName -ErrorAction Stop } elseif ($ensure -eq 'Present') { New-AzRoleAssignment -ObjectId $assignment.ObjectId -Scope $assignment.Scope -RoleDefinitionName $assignment.RoleDefinitionName -ErrorAction Stop } } $results += [PSCustomObject]@{ Section = $section_name ScopeId = $scope_property.Name Status = $entry.Status Action = $action ObjectId = $assignment.ObjectId RoleDefinitionName = $assignment.RoleDefinitionName Scope = $assignment.Scope } } } } return $results } |