Public/Remove-NLBaselineCAOrphanedObjects.ps1

function Remove-NLBaselineCAOrphanedObjects {
    <#
    .SYNOPSIS
    Remove orphaned groups and users from Conditional Access policies
     
    .DESCRIPTION
    Scans all Conditional Access policies and removes references to deleted (orphaned) groups or users.
    This helps clean up policies after groups or users have been deleted.
     
    .EXAMPLE
    Remove-NLBaselineCAOrphanedObjects -ObjectType Group -ObjectId "guid-here"
    Remove-NLBaselineCAOrphanedObjects -ObjectType User -ObjectId "guid-here"
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet("Group", "User")]
        [string]$ObjectType,
        
        [Parameter(Mandatory = $true)]
        [string]$ObjectId
    )
    
    try {
        # Check connection
        $context = Get-MgContext -ErrorAction SilentlyContinue
        if (-not $context -or -not $context.TenantId) {
            Write-Host "Not connected to Microsoft 365. Connecting..." -ForegroundColor Yellow
            Write-Host ""
            $connection = Connect-NLBaselineCA
            if (-not $connection) {
                Write-Error "Cannot connect to Microsoft 365"
                return
            }
            $context = Get-MgContext
        }
        
        Write-Host "========================================" -ForegroundColor Cyan
        Write-Host " REMOVE ORPHANED $ObjectType" -ForegroundColor Cyan
        Write-Host "========================================" -ForegroundColor Cyan
        Write-Host ""
        Write-Host "Searching for orphaned $ObjectType : $ObjectId" -ForegroundColor Yellow
        Write-Host ""
        
        # Get all policies
        Write-Host "Retrieving all Conditional Access policies..." -ForegroundColor Gray
        $policies = Get-AllConditionalAccessPolicies
        
        Write-Host "Found $($policies.Count) policies to check" -ForegroundColor Green
        Write-Host ""
        
        $updatedCount = 0
        $foundCount = 0
        $errors = @()
        
        foreach ($policy in $policies) {
            try {
                $needsUpdate = $false
                $policyUpdate = @{}
                
                # Ensure conditions structure exists
                if (-not $policy.conditions -or -not $policy.conditions.users) {
                    continue
                }
                
                $policyUpdate.conditions = $policy.conditions
                
                if ($ObjectType -eq "Group") {
                    # Check excludeGroups
                    if ($policy.conditions.users.excludeGroups -and $policy.conditions.users.excludeGroups -contains $ObjectId) {
                        $foundCount++
                        $updatedExcludeGroups = $policy.conditions.users.excludeGroups | Where-Object { $_ -ne $ObjectId }
                        $policyUpdate.conditions.users.excludeGroups = $updatedExcludeGroups
                        $needsUpdate = $true
                        Write-Host " Found in excludeGroups: $($policy.displayName)" -ForegroundColor Yellow
                    }
                    
                    # Check includeGroups
                    if ($policy.conditions.users.includeGroups -and $policy.conditions.users.includeGroups -contains $ObjectId) {
                        $foundCount++
                        $updatedIncludeGroups = $policy.conditions.users.includeGroups | Where-Object { $_ -ne $ObjectId }
                        $policyUpdate.conditions.users.includeGroups = $updatedIncludeGroups
                        $needsUpdate = $true
                        Write-Host " Found in includeGroups: $($policy.displayName)" -ForegroundColor Yellow
                    }
                }
                elseif ($ObjectType -eq "User") {
                    # Check excludeUsers
                    if ($policy.conditions.users.excludeUsers -and $policy.conditions.users.excludeUsers -contains $ObjectId) {
                        $foundCount++
                        $updatedExcludeUsers = $policy.conditions.users.excludeUsers | Where-Object { $_ -ne $ObjectId }
                        $policyUpdate.conditions.users.excludeUsers = $updatedExcludeUsers
                        $needsUpdate = $true
                        Write-Host " Found in excludeUsers: $($policy.displayName)" -ForegroundColor Yellow
                    }
                    
                    # Check includeUsers
                    if ($policy.conditions.users.includeUsers -and $policy.conditions.users.includeUsers -contains $ObjectId) {
                        $foundCount++
                        $updatedIncludeUsers = $policy.conditions.users.includeUsers | Where-Object { $_ -ne $ObjectId }
                        $policyUpdate.conditions.users.includeUsers = $updatedIncludeUsers
                        $needsUpdate = $true
                        Write-Host " Found in includeUsers: $($policy.displayName)" -ForegroundColor Yellow
                    }
                }
                
                if ($needsUpdate) {
                    # Update policy using REST API
                    $body = @{
                        conditions = $policyUpdate.conditions
                    } | ConvertTo-Json -Depth 10
                    
                    $invokeCmd = Get-Command Invoke-MgGraphRequest -ErrorAction SilentlyContinue
                    if ($invokeCmd) {
                        Invoke-MgGraphRequest -Method PATCH `
                            -Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($policy.Id)" `
                            -Body $body `
                            -ContentType "application/json" `
                            -ErrorAction Stop
                        
                        $updatedCount++
                        Write-Host " Updated successfully" -ForegroundColor Green
                    }
                    else {
                        throw "Invoke-MgGraphRequest not available"
                    }
                }
            }
            catch {
                $errors += "Error updating $($policy.displayName): $_"
                Write-Host " Error: $_" -ForegroundColor Red
            }
        }
        
        Write-Host ""
        Write-Host "========================================" -ForegroundColor Green
        Write-Host " SUMMARY" -ForegroundColor Green
        Write-Host "========================================" -ForegroundColor Green
        Write-Host "Found in: $foundCount policies" -ForegroundColor White
        Write-Host "Updated: $updatedCount policies" -ForegroundColor White
        if ($errors.Count -gt 0) {
            Write-Host "Errors: $($errors.Count)" -ForegroundColor Red
            foreach ($error in $errors) {
                Write-Host " - $error" -ForegroundColor Yellow
            }
        }
        Write-Host ""
    }
    catch {
        Write-Error "Error removing orphaned objects: $_"
    }
}