Private/AssignmentConflicts.ps1

function Show-InTUIAssignmentConflictView {
    <#
    .SYNOPSIS
        Detects groups targeted by multiple configuration or compliance policies (potential conflicts).
    #>

    [CmdletBinding()]
    param()

    $exitView = $false

    while (-not $exitView) {
        Clear-Host
        Show-InTUIHeader
        Show-InTUIBreadcrumb -Path @('Home', 'Assignment Conflicts')

        $choices = @('Configuration Profiles', 'Compliance Policies', '─────────────', 'Back to Home')
        $selection = Show-InTUIMenu -Title "[yellow]Assignment Conflict Check[/]" -Choices $choices

        switch ($selection) {
            'Configuration Profiles' {
                Show-InTUIConfigConflicts
            }
            'Compliance Policies' {
                Show-InTUIComplianceConflicts
            }
            'Back to Home' {
                $exitView = $true
            }
            default { continue }
        }
    }
}

function Show-InTUIConfigConflicts {
    <#
    .SYNOPSIS
        Finds groups targeted by multiple configuration profiles.
    #>

    [CmdletBinding()]
    param()

    Clear-Host
    Show-InTUIHeader
    Show-InTUIBreadcrumb -Path @('Home', 'Assignment Conflicts', 'Configuration Profiles')

    $data = Show-InTUILoading -Title "[yellow]Loading configuration profiles...[/]" -ScriptBlock {
        $catalog = Invoke-InTUIGraphRequest -Uri '/deviceManagement/configurationPolicies?$expand=assignments&$top=200' -Beta
        $legacy = Invoke-InTUIGraphRequest -Uri '/deviceManagement/deviceConfigurations?$expand=assignments&$top=200' -Beta
        @{
            Catalog = $catalog
            Legacy  = $legacy
        }
    }

    $allPolicies = @()
    foreach ($source in @($data.Catalog, $data.Legacy)) {
        $items = if ($source.value) { @($source.value) } else { @() }
        foreach ($policy in $items) {
            $allPolicies += $policy
        }
    }

    $conflicts = Find-InTUIAssignmentOverlaps -Policies $allPolicies

    if ($conflicts.Count -eq 0) {
        Show-InTUISuccess "No assignment conflicts detected among $($allPolicies.Count) configuration profiles."
        Read-InTUIKey
        return
    }

    $rows = @()
    foreach ($conflict in $conflicts) {
        $rows += , @(
            $conflict.GroupName,
            (ConvertTo-InTUISafeMarkup -Text $conflict.PolicyA),
            (ConvertTo-InTUISafeMarkup -Text $conflict.PolicyB),
            ($conflict.Platform ?? 'N/A')
        )
    }

    Show-InTUIWarning "Found $($conflicts.Count) potential conflict(s)."
    Write-InTUIText ""

    Render-InTUITable -Title "Configuration Profile Conflicts" -Columns @('Group', 'Policy A', 'Policy B', 'Platform') -Rows $rows -BorderColor Yellow

    Read-InTUIKey
}

function Show-InTUIComplianceConflicts {
    <#
    .SYNOPSIS
        Finds groups targeted by multiple compliance policies.
    #>

    [CmdletBinding()]
    param()

    Clear-Host
    Show-InTUIHeader
    Show-InTUIBreadcrumb -Path @('Home', 'Assignment Conflicts', 'Compliance Policies')

    $data = Show-InTUILoading -Title "[yellow]Loading compliance policies...[/]" -ScriptBlock {
        Invoke-InTUIGraphRequest -Uri '/deviceManagement/deviceCompliancePolicies?$expand=assignments&$top=200' -Beta
    }

    $allPolicies = if ($data.value) { @($data.value) } else { @() }

    $conflicts = Find-InTUIAssignmentOverlaps -Policies $allPolicies

    if ($conflicts.Count -eq 0) {
        Show-InTUISuccess "No assignment conflicts detected among $($allPolicies.Count) compliance policies."
        Read-InTUIKey
        return
    }

    $rows = @()
    foreach ($conflict in $conflicts) {
        $rows += , @(
            $conflict.GroupName,
            (ConvertTo-InTUISafeMarkup -Text $conflict.PolicyA),
            (ConvertTo-InTUISafeMarkup -Text $conflict.PolicyB),
            ($conflict.Platform ?? 'N/A')
        )
    }

    Show-InTUIWarning "Found $($conflicts.Count) potential conflict(s)."
    Write-InTUIText ""

    Render-InTUITable -Title "Compliance Policy Conflicts" -Columns @('Group', 'Policy A', 'Policy B', 'Platform') -Rows $rows -BorderColor Yellow

    Read-InTUIKey
}

function Find-InTUIAssignmentOverlaps {
    <#
    .SYNOPSIS
        Finds groups targeted by 2+ policies. Returns conflict entries.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [array]$Policies
    )

    # Build map: GroupId -> list of policy names
    $groupPolicyMap = @{}
    $groupNameCache = @{}

    foreach ($policy in $Policies) {
        $policyName = $policy.displayName ?? $policy.name ?? 'Unknown'
        $platform = $policy.'@odata.type' -replace '#microsoft\.graph\.', '' -replace 'Configuration$', ''

        $assignments = if ($policy.assignments) { @($policy.assignments) } else { @() }
        foreach ($assignment in $assignments) {
            $target = $assignment.target
            $groupId = $target.groupId
            if (-not $groupId) { continue }

            if (-not $groupPolicyMap.ContainsKey($groupId)) {
                $groupPolicyMap[$groupId] = @()
            }
            $groupPolicyMap[$groupId] += @{ Name = $policyName; Platform = $platform }
        }
    }

    # Find groups with 2+ policies
    $conflicts = @()
    foreach ($groupId in $groupPolicyMap.Keys) {
        $policies = $groupPolicyMap[$groupId]
        if ($policies.Count -lt 2) { continue }

        # Resolve group name (batch-cached)
        if (-not $groupNameCache.ContainsKey($groupId)) {
            $group = Invoke-InTUIGraphRequest -Uri "/groups/${groupId}?`$select=displayName"
            $groupNameCache[$groupId] = if ($group.displayName) { $group.displayName } else { $groupId }
        }
        $groupName = $groupNameCache[$groupId]

        # Report pairs
        for ($i = 0; $i -lt $policies.Count; $i++) {
            for ($j = $i + 1; $j -lt $policies.Count; $j++) {
                $conflicts += [PSCustomObject]@{
                    GroupName = $groupName
                    PolicyA   = $policies[$i].Name
                    PolicyB   = $policies[$j].Name
                    Platform  = $policies[$i].Platform
                }
            }
        }
    }

    return $conflicts
}