Public/Compare-IntuneAdminAccess.ps1

function Compare-IntuneAdminAccess {
    <#
    .SYNOPSIS
    Compares two Intune administrator access results.
    .DESCRIPTION
    Compares assignments, permissions, Admin Groups, Scope Groups and Scope Tags.
    A shared permission is marked DifferentEvidence when its granting assignments differ.
    .PARAMETER ReferenceObject
    The reference result returned by Get-IntuneAdminAccess.
    .PARAMETER DifferenceObject
    The result to compare with the reference result.
    .PARAMETER ReferenceUser
    The reference administrator's user principal name.
    .PARAMETER DifferenceUser
    The comparison administrator's user principal name.
    .PARAMETER IncludeUnchanged
    Includes records that are identical in both results.
    .EXAMPLE
    Compare-IntuneAdminAccess -ReferenceUser 'senior@contoso.com' -DifferenceUser 'helpdesk@contoso.com'
    .EXAMPLE
    Compare-IntuneAdminAccess -ReferenceObject $senior -DifferenceObject $helpdesk
    #>

    [CmdletBinding(DefaultParameterSetName = 'ByUser')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ByObject')] [object] $ReferenceObject,
        [Parameter(Mandatory, ParameterSetName = 'ByObject')] [object] $DifferenceObject,
        [Parameter(Mandatory, ParameterSetName = 'ByUser')] [ValidateNotNullOrEmpty()] [string] $ReferenceUser,
        [Parameter(Mandatory, ParameterSetName = 'ByUser')] [ValidateNotNullOrEmpty()] [string] $DifferenceUser,
        [switch] $IncludeUnchanged
    )

    $reference = if ($PSCmdlet.ParameterSetName -eq 'ByUser') {
        Get-IntuneAdminAccess -UserPrincipalName $ReferenceUser
    }
    else { $ReferenceObject }
    $difference = if ($PSCmdlet.ParameterSetName -eq 'ByUser') {
        Get-IntuneAdminAccess -UserPrincipalName $DifferenceUser
    }
    else { $DifferenceObject }

    foreach ($accessResult in @($reference, $difference)) {
        if ('IntuneAccess.AdminAccess' -notin $accessResult.PSObject.TypeNames) {
            throw 'ReferenceObject and DifferenceObject must be results returned by Get-IntuneAdminAccess.'
        }
    }

    $changes = [System.Collections.Generic.List[object]]::new()
    $collections = @(
        [PSCustomObject] @{ Category = 'RoleAssignment'; Reference = @($reference.RoleAssignments); Difference = @($difference.RoleAssignments); Key = 'Id'; Display = 'Name' }
        [PSCustomObject] @{ Category = 'AdminGroup'; Reference = @($reference.AdminGroups); Difference = @($difference.AdminGroups); Key = 'Id'; Display = 'DisplayName' }
        [PSCustomObject] @{ Category = 'ScopeGroup'; Reference = @($reference.ScopeGroups); Difference = @($difference.ScopeGroups); Key = 'Id'; Display = 'DisplayName' }
        [PSCustomObject] @{ Category = 'ScopeTag'; Reference = @($reference.ScopeTags); Difference = @($difference.ScopeTags); Key = 'Id'; Display = 'DisplayName' }
    )

    foreach ($collection in $collections) {
        $referenceMap = @{}
        $differenceMap = @{}
        foreach ($item in $collection.Reference) {
            $key = [string] (Get-IntuneAccessProperty $item $collection.Key)
            if (-not [string]::IsNullOrWhiteSpace($key)) { $referenceMap[$key] = $item }
        }
        foreach ($item in $collection.Difference) {
            $key = [string] (Get-IntuneAccessProperty $item $collection.Key)
            if (-not [string]::IsNullOrWhiteSpace($key)) { $differenceMap[$key] = $item }
        }
        foreach ($key in @($referenceMap.Keys + $differenceMap.Keys | Select-Object -Unique | Sort-Object)) {
            $status = if (-not $differenceMap.ContainsKey($key)) { 'ReferenceOnly' }
                elseif (-not $referenceMap.ContainsKey($key)) { 'DifferenceOnly' }
                else { 'Unchanged' }
            if ($status -eq 'Unchanged' -and -not $IncludeUnchanged) { continue }
            $item = if ($referenceMap.ContainsKey($key)) { $referenceMap[$key] } else { $differenceMap[$key] }
            $changes.Add([PSCustomObject] @{
                Category        = $collection.Category
                Key             = $key
                DisplayName     = [string] (Get-IntuneAccessProperty $item $collection.Display $key)
                Status          = $status
                ReferenceValue  = if ($referenceMap.ContainsKey($key)) { $referenceMap[$key] } else { $null }
                DifferenceValue = if ($differenceMap.ContainsKey($key)) { $differenceMap[$key] } else { $null }
            })
        }
    }

    $referencePermissions = @{}
    $differencePermissions = @{}
    foreach ($permission in @($reference.EffectivePermissions)) { $referencePermissions[[string] $permission.RawAction] = $permission }
    foreach ($permission in @($difference.EffectivePermissions)) { $differencePermissions[[string] $permission.RawAction] = $permission }
    foreach ($key in @($referencePermissions.Keys + $differencePermissions.Keys | Select-Object -Unique | Sort-Object)) {
        if (-not $differencePermissions.ContainsKey($key)) {
            $status = 'ReferenceOnly'
        }
        elseif (-not $referencePermissions.ContainsKey($key)) {
            $status = 'DifferenceOnly'
        }
        else {
            $referenceSources = @($referencePermissions[$key].GrantedBy | ForEach-Object RoleAssignmentId | Sort-Object -Unique)
            $differenceSources = @($differencePermissions[$key].GrantedBy | ForEach-Object RoleAssignmentId | Sort-Object -Unique)
            $status = if (($referenceSources -join '|') -eq ($differenceSources -join '|')) { 'Unchanged' } else { 'DifferentEvidence' }
        }
        if ($status -eq 'Unchanged' -and -not $IncludeUnchanged) { continue }
        $permission = if ($referencePermissions.ContainsKey($key)) { $referencePermissions[$key] } else { $differencePermissions[$key] }
        $changes.Add([PSCustomObject] @{
            Category        = 'Permission'
            Key             = $key
            DisplayName     = "$($permission.Resource) / $($permission.Operation)"
            Status          = $status
            ReferenceValue  = if ($referencePermissions.ContainsKey($key)) { $referencePermissions[$key] } else { $null }
            DifferenceValue = if ($differencePermissions.ContainsKey($key)) { $differencePermissions[$key] } else { $null }
        })
    }

    $changeArray = @($changes | Sort-Object Category, DisplayName)
    [PSCustomObject] @{
        PSTypeName       = 'IntuneAccess.AdminAccessComparison'
        ReferenceUser    = $reference.User
        DifferenceUser   = $difference.User
        Changes          = $changeArray
        Summary          = [PSCustomObject] @{
            TotalChanges      = @($changeArray | Where-Object Status -NE 'Unchanged').Count
            ReferenceOnly     = @($changeArray | Where-Object Status -EQ 'ReferenceOnly').Count
            DifferenceOnly    = @($changeArray | Where-Object Status -EQ 'DifferenceOnly').Count
            DifferentEvidence = @($changeArray | Where-Object Status -EQ 'DifferentEvidence').Count
            Unchanged         = @($changeArray | Where-Object Status -EQ 'Unchanged').Count
        }
        ReferenceAccess  = $reference
        DifferenceAccess = $difference
        GeneratedAt      = [DateTimeOffset]::Now
        ToolVersion      = $script:IntuneAccessVersion
    }
}