internal/functions/EPO_Invoke-EasyPIMCleanup.ps1
|
function Invoke-EasyPIMCleanup { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] [pscustomobject]$Config, [Parameter(Mandatory=$true)] [ValidateSet('delta','initial')] [string]$Mode, [Parameter(Mandatory=$false)] [string]$TenantId, [Parameter(Mandatory=$false)] [string]$SubscriptionId, [Parameter(Mandatory=$false)] [string]$WouldRemoveExportPath ) Write-Verbose "[Cleanup] Starting cleanup (mode=$Mode)" if ($PSCmdlet.ShouldProcess('Cleanup operations', 'Invoke EasyPIM cleanup')) { # Orchestrator-only cleanup fallback (no dependency on core module) $results = [pscustomobject]@{ Kept=0; Removed=0; Skipped=0; Protected=0; WouldRemoveCount=0 AnalysisCompleted=$false; DesiredAssignments=0; Mode=$Mode CleanupStatus="Cleanup not performed" } $desired = @{} if ($Config.Assignments) { if ($Config.Assignments.EntraRoles) { foreach ($r in $Config.Assignments.EntraRoles) { foreach ($a in @($r.assignments)) { if ($a) { $key = "entra::$($r.roleName)::/::$($a.principalId)"; $desired[$key] = $true } } } } if ($Config.Assignments.AzureRoles) { foreach ($r in $Config.Assignments.AzureRoles) { $rn = $r.RoleName; if (-not $rn) { $rn=$r.roleName } $sc = $r.Scope; if (-not $sc) { $sc=$r.scope } foreach ($a in @($r.assignments)) { if ($a) { $key = "azure::$rn::$sc::$($a.principalId)"; $desired[$key] = $true } } } } if ($Config.Assignments.Groups) { foreach ($g in $Config.Assignments.Groups) { foreach ($a in @($g.assignments)) { if ($a) { $key = "group::member::$($g.groupId)::$($a.principalId)"; $desired[$key] = $true } } } } } Write-Verbose "[Cleanup] Fallback: analyzed desired set size=$($desired.Keys.Count). No removals executed." # Update analysis details in results for better user feedback $results.AnalysisCompleted = $true $results.DesiredAssignments = $desired.Keys.Count if ($Mode -eq 'delta') { $results.CleanupStatus = "Analyzed $($desired.Keys.Count) desired assignments. Cleanup operations require core EasyPIM module." } else { $results.CleanupStatus = "Initial mode - no cleanup analysis performed in orchestrator-only mode." } return $results } # Orchestrator-only, non-destructive cleanup summary (no cross-module calls) $results = [pscustomobject]@{ Kept=0; Removed=0; Skipped=0; Protected=0; WouldRemoveCount=0 AnalysisCompleted=$false; DesiredAssignments=0; Mode=$Mode CleanupStatus="Cleanup not performed" } # Analyze in both delta and initial mode if (($Mode -eq 'delta' -or $Mode -eq 'initial') -and $Config -and $Config.PSObject.Properties.Name -contains 'Assignments' -and $Config.Assignments) { $desired = @{} if ($Config.Assignments.EntraRoles) { foreach ($r in $Config.Assignments.EntraRoles) { foreach ($a in @($r.assignments)) { if ($a) { $key = "entra::$($r.roleName)::/::$($a.principalId)"; $desired[$key] = $true } } } } if ($Config.Assignments.AzureRoles) { foreach ($r in $Config.Assignments.AzureRoles) { $rn = $r.RoleName; if (-not $rn) { $rn=$r.roleName } $sc = $r.Scope; if (-not $sc) { $sc=$r.scope } foreach ($a in @($r.assignments)) { if ($a) { $key = "azure::$rn::$sc::$($a.principalId)"; $desired[$key] = $true } } } } if ($Config.Assignments.Groups) { foreach ($g in $Config.Assignments.Groups) { foreach ($a in @($g.assignments)) { if ($a) { $key = "group::member::$($g.groupId)::$($a.principalId)"; $desired[$key] = $true } } } } $results.AnalysisCompleted = $true $results.DesiredAssignments = $desired.Keys.Count if ($Mode -eq 'initial') { Write-Verbose "[Cleanup] Initial mode: Fetching current assignments for reconciliation..." $removals = @() # Entra if ($Config.Assignments.EntraRoles) { try { $entraElig = Get-PIMEntraRoleEligibleAssignment -TenantId $TenantId -ErrorAction SilentlyContinue if ($entraElig) { foreach ($a in $entraElig) { $key = "entra::$($a.RoleName)::/::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Entra"; Role = $a.RoleName; Principal = $a.PrincipalId; AssignmentType = "Eligible"; Scope = "/" } } } } $entraAct = Get-PIMEntraRoleActiveAssignment -TenantId $TenantId -ErrorAction SilentlyContinue if ($entraAct) { foreach ($a in $entraAct) { $key = "entra::$($a.RoleName)::/::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Entra"; Role = $a.RoleName; Principal = $a.PrincipalId; AssignmentType = "Active"; Scope = "/" } } } } } catch { Write-Warning "Failed to fetch Entra assignments for cleanup: $_" } } # Azure if ($Config.Assignments.AzureRoles) { try { $azureElig = Get-PIMAzureResourceEligibleAssignment -TenantId $TenantId -SubscriptionId $SubscriptionId -ErrorAction SilentlyContinue if ($azureElig) { foreach ($a in $azureElig) { $key = "azure::$($a.RoleName)::$($a.Scope)::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Azure"; Role = $a.RoleName; Principal = $a.PrincipalId; AssignmentType = "Eligible"; Scope = $a.Scope } } } } $azureAct = Get-PIMAzureResourceActiveAssignment -TenantId $TenantId -SubscriptionId $SubscriptionId -ErrorAction SilentlyContinue if ($azureAct) { foreach ($a in $azureAct) { $key = "azure::$($a.RoleName)::$($a.Scope)::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Azure"; Role = $a.RoleName; Principal = $a.PrincipalId; AssignmentType = "Active"; Scope = $a.Scope } } } } } catch { Write-Warning "Failed to fetch Azure assignments for cleanup: $_" } } # Groups if ($Config.Assignments.Groups) { foreach ($g in $Config.Assignments.Groups) { try { $grpElig = Get-PIMGroupEligibleAssignment -TenantId $TenantId -GroupId $g.GroupId -ErrorAction SilentlyContinue if ($grpElig) { foreach ($a in $grpElig) { $key = "group::member::$($g.GroupId)::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Group"; Role = "Member"; Principal = $a.PrincipalId; AssignmentType = "Eligible"; Scope = $g.GroupId } } } } $grpAct = Get-PIMGroupActiveAssignment -TenantId $TenantId -GroupId $g.GroupId -ErrorAction SilentlyContinue if ($grpAct) { foreach ($a in $grpAct) { $key = "group::member::$($g.GroupId)::$($a.PrincipalId)" if (-not $desired.ContainsKey($key)) { $removals += [pscustomobject]@{ Type = "Group"; Role = "Member"; Principal = $a.PrincipalId; AssignmentType = "Active"; Scope = $g.GroupId } } } } } catch { Write-Warning "Failed to fetch Group assignments for cleanup (Group $($g.GroupId)): $_" } } } $results.WouldRemoveCount = $removals.Count $results.Removed = $removals if ($removals.Count -gt 0) { Write-Host "⚠️ [CLEANUP] Found $($removals.Count) assignments to remove:" -ForegroundColor Yellow $removals | Format-Table Type, Role, Principal, AssignmentType, Scope | Out-String | Write-Host if (-not $PSCmdlet.ShouldProcess("Cleanup operations", "Remove $($removals.Count) assignments")) { Write-Host "[INFO] Cleanup skipped (WhatIf)" -ForegroundColor Cyan } else { Write-Warning "Automatic removal is not yet fully implemented in Orchestrator-only mode. Please remove these assignments manually or use the core EasyPIM module." } } else { Write-Host "✅ [CLEANUP] No extra assignments found." -ForegroundColor Green } $results.CleanupStatus = "Analyzed $($desired.Keys.Count) desired assignments. Found $($removals.Count) to remove." } else { $results.CleanupStatus = "Analyzed $($desired.Keys.Count) desired assignments. Full cleanup requires core EasyPIM module." } Write-Verbose "[Cleanup] Orchestrator analysis complete. Desired entries=$($desired.Keys.Count)." } else { $results.CleanupStatus = "No assignments configured for cleanup analysis." } return $results } |