Introduction

As Microsoft Purview continues to expand across compliance, governance, and risk workloads, understanding who has access to what has never been more important. Role-based access control (RBAC) is at the heart of Purview’s security model, yet reviewing these roles is often more complicated than it should be.

Administrators frequently encounter duplicate role groups, inconsistent member listings, and nested Entra ID groups that obscure the real users behind the permissions. These challenges make Purview role reviews time‑consuming and error‑prone, especially during audits or quarterly access reviews.

To simplify this process, I’ve created a lightweight PowerShell script that provides a clean and accurate export of all Purview role groups and their members or groups, if you are using PIM, as well as the members of those groups.

See It in Action
The script I’ve built is designed to be simple, fast, and reliable. Here’s how it works behind the scenes:

Why Is This Important?

RBAC reviews are not optional; they’re a core requirement for maintaining a secure and compliant environment. Whether you’re preparing for an audit, validating least‑privilege access, or simply trying to understand your Purview configuration, you need reliable visibility into:

  • Who is assigned to each Purview role group
  • Which role groups contain Entra ID groups
  • When each role group was last updated
  • Whether any roles contain unexpected or stale assignments

The challenge is that Purview RBAC is built on top of Exchange Online and the Compliance PowerShell endpoint. This leads to:

  • Duplicate display names
  • Backend “multiple matches” errors
  • Group members who don’t expand into actual users
  • Slow or incomplete member resolution

A practical, repeatable method for exporting RBAC data is essential for maintaining governance and reducing operational overhead.

You need a clean, accurate export, not a troubleshooting session or manual review.

This is where a practical, automated approach becomes invaluable.

Steps:

1. Connects to the required services

  • Exchange Online
  • Microsoft Graph (optional but recommended)
  • Compliance PowerShell

If the Graph isn’t available, the script still runs; it simply skips group expansion.

Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
Connect-ExchangeOnline -ShowBanner:$false

$GraphAvailable = $false
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
try {
    Connect-MgGraph -Scopes "Group.Read.All" -ErrorAction Stop | Out-Null
    $GraphAvailable = $true
} catch {
    Write-Warning "Could not connect to Microsoft Graph. Group expansion will be skipped."
}

Write-Host "Connecting to Microsoft Purview / Compliance PowerShell..." -ForegroundColor Cyan
try {
    Connect-IPPSSession -ErrorAction Stop
} catch {
    Write-Warning "Could not connect to Compliance PowerShell."
}

2. Retrieves all Purview / Compliance role groups

Using Get-RoleGroup -ResultSize Unlimited, ensuring nothing is missed.

Write-Host "Retrieving Purview / Compliance role groups..." -ForegroundColor Yellow
[array]$RoleGroups = Get-RoleGroup -ResultSize Unlimited
$Report = [System.Collections.Generic.List[Object]]::new()

3. Safely retrieves members

A custom wrapper function prevents backend “multiple matches” errors from interrupting the script.

function Get-SafeRoleGroupMember {
    param([string]$Identity)

    try {
        return Get-RoleGroupMember -Identity $Identity -ResultSize Unlimited `
            -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
    } catch {
        return @()
    }
}

4. Expands Entra ID groups

If a role group contains an Entra ID group, for example, if you use it for more structure or are involved in PIM, the script uses Microsoft Graph to expand it into actual user names, giving you full visibility.

function Expand-GroupMembers {
    param([object]$Member)

    $expanded = @()
    if (-not $GraphAvailable) { return $expanded }

    try {
        $groupId = $null

        if ($Member.ExternalDirectoryObjectId) {
            $groupId = $Member.ExternalDirectoryObjectId
        }
        elseif ($Member.Name) {
            $group = Get-MgGroup -Filter "displayName eq '$($Member.Name)'" `
                     -ErrorAction SilentlyContinue | Select-Object -First 1
            if ($group) { $groupId = $group.Id }
        }

        if ($groupId) {
            $members = Get-MgGroupMemberAsUser -GroupId $groupId -All `
                       -ErrorAction SilentlyContinue | Select-Object DisplayName

            foreach ($m in $members) {
                $expanded += $m.DisplayName
            }
        }
    } catch {}

    return $expanded
}

5. Cleans and dedupes the results

The script:

  • Removes duplicates
  • Sorts members alphabetically
  • Merges role groups with identical display names
  • Adds a “Last Updated” timestamp
# Inside the main loop
$allNames = $allNames | Sort-Object -Unique
if ($allNames.Count -eq 0) { $allNames = @("(none)") }

# Last updated timestamp
$lastUpdated = if ($RoleGroup.WhenChanged -and `
    ($RoleGroup.WhenChanged -ne "Wednesday 1 January 2020 00:00:00")) {
    Get-Date($RoleGroup.WhenChanged) -Format g
} else { "Never" }

# Add to report
$Report += [PSCustomObject]@{
    "Role Group"   = $rgDisplay
    "Groups"       = if ($groupNames.Count -gt 0) { $groupNames -join ", " } else { "(none)" }
    "Members"      = $allNames -join ", "
    "Last Updated" = $lastUpdated
}

6. Exports the final report

You get:

  • An Out‑GridView preview
  • A timestamped CSV file ready for audit or review
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$outFile = ".\Purview_RoleGroups_Members_$timestamp.csv"

$Report | Sort-Object "Role Group" | Out-GridView -Title "Microsoft Purview Role Group Memberships"
$Report | Sort-Object "Role Group" | Export-Csv -Path $outFile -NoTypeInformation -Encoding UTF8

Write-Host "Export complete! File saved as: $outFile" -ForegroundColor Yellow
Write-Host "Purview RBAC checks completed. The role review report is now available for validation." -ForegroundColor Cyan

# Close all sessions
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph | Out-Null
Get-PSSession | Remove-PSSession -ErrorAction SilentlyContinue

A typical output row looks like:

Role Group, Groups, Members, Last Updated eDiscovery Manager, SecurityTeam, John Doe, Jane Smith, 12/10/2025 14:22 Insider Risk Management, (none), Alex Brown, Never

Clean.
Readable.
Audit‑ready.

Conclusion

Purview role reviews don’t have to be complicated. With a practical, automated approach, you can generate accurate RBAC reports in seconds without the usual PowerShell frustrations.

This script gives you:

  • A reliable method for quarterly access reviews
  • A clear view of nested group membership
  • A faster way to validate least privilege access
  • A repeatable process you can automate or schedule

As Purview continues to grow, having a dependable way to review RBAC assignments becomes essential for maintaining a strong compliance posture.


Discover more from Blogs | Saied Taki

Subscribe to get the latest posts sent to your email.

By Taki

Leave a Reply

Your email address will not be published. Required fields are marked *