GitHub Icon Image
GitHub

Get Azure AD (Entra ID) Guest Users with Elevated Permissions (M365)

Summary

This script audits and identifies Azure AD (Entra ID) guest (B2B) users who have been granted elevated permissions across Microsoft 365 services, including Entra ID directory roles, Microsoft Teams ownership, and SharePoint Online site collection administration. It generates a consolidated report to support security reviews, least-privilege enforcement, and external access governance.

Why It Matters

  • Risk reduction: Identifies external accounts with elevated access before they become a security issue.
  • Compliance support: Provides evidence for audits and regulatory requirements.
  • Operational efficiency: Consolidates checks across Entra ID, Teams, and SharePoint into one report.
  • Governance enforcement: Helps enforce least‑privilege and external access policies consistently.
  • PnP PowerShell

# ------------------------------------------------------------
# Configuration
# ------------------------------------------------------------
$TenantAdminUrl = "https://contoso-admin.sharepoint.com"
$OutputPath = "C:\Temp\GuestUsersWithExcessivePermissions.csv"

# ------------------------------------------------------------
# Connect
# ------------------------------------------------------------
if (-not $conn) {
    $conn = Connect-PnPOnline -Url $TenantAdminUrl -Interactive -ReturnConnection
}

# ------------------------------------------------------------
# Helper: Get all guest users
# ------------------------------------------------------------
function Get-GuestUsers {
    param([Parameter(Mandatory)] $Connection)

    Invoke-PnPGraphMethod -Url "users?`$filter=userType eq 'Guest'&`$select=id,displayName,userPrincipalName,userType" `
        -Connection $Connection
}

# ------------------------------------------------------------
# Helper: Get Entra ID directory roles for guests
# ------------------------------------------------------------
function Get-GuestDirectoryRoles {
    param(
        [Parameter(Mandatory)] $Connection,
        [Parameter(Mandatory)] $Guests
    )

    $results = @()

    $roles = Invoke-PnPGraphMethod -Url "directoryRoles" -Connection $Connection

    foreach ($role in $roles.value) {
        $members = Invoke-PnPGraphMethod -Url "directoryRoles/$($role.id)/members" -Connection $Connection

        foreach ($member in $members.value) {
            if ($member.userType -eq "Guest") {
                $results += [PSCustomObject]@{
                    DisplayName        = $member.displayName
                    UserPrincipalName  = $member.userPrincipalName
                    PermissionType     = "AAD Role"
                    AssignedRole       = $role.displayName
                    Resource           = "Tenant"
                }
            }
        }
    }

    return $results
}

# ------------------------------------------------------------
# Helper: Get Teams owned by guests
# ------------------------------------------------------------
function Get-GuestTeamOwners {
    param([Parameter(Mandatory)] $Connection)

    $results = @()
    $groups = Invoke-PnPGraphMethod -Url "groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')" `
        -Connection $Connection

    foreach ($group in $groups.value) {
        $owners = Invoke-PnPGraphMethod -Url "groups/$($group.id)/owners" -Connection $Connection

        foreach ($owner in $owners.value) {
            if ($owner.userType -eq "Guest") {
                $results += [PSCustomObject]@{
                    DisplayName        = $owner.displayName
                    UserPrincipalName  = $owner.userPrincipalName
                    PermissionType     = "Teams"
                    AssignedRole       = "Owner"
                    Resource           = $group.displayName
                }
            }
        }
    }

    return $results
}

# ------------------------------------------------------------
# Helper: Get SharePoint Site Collection Admins (guests only)
# ------------------------------------------------------------
function Get-GuestSPOAdmins {
    param([Parameter(Mandatory)] $Connection)

    $results = @()
    $sites = Get-PnPTenantSite -Connection $Connection

    foreach ($site in $sites) {
        try {
            Connect-PnPOnline -Url $site.Url -Connection $Connection
            $admins = Get-PnPSiteCollectionAdmin

            foreach ($admin in $admins) {
                if ($admin.PrincipalType -eq "User" -and $admin.LoginName -like "*#EXT#*") {
                    $results += [PSCustomObject]@{
                        DisplayName        = $admin.Title
                        UserPrincipalName  = $admin.Email
                        PermissionType     = "SharePoint"
                        AssignedRole       = "Site Collection Administrator"
                        Resource           = $site.Url
                    }
                }
            }
        }
        catch {
            Write-Warning "Failed to process site $($site.Url)"
        }
    }

    return $results
}

# ------------------------------------------------------------
# Main Execution
# ------------------------------------------------------------
Write-Host "Retrieving guest users..." -ForegroundColor Cyan
$guestUsers = Get-GuestUsers -Connection $conn

Write-Host "Checking Entra ID directory roles..." -ForegroundColor Cyan
$aadRoles = Get-GuestDirectoryRoles -Connection $conn -Guests $guestUsers.value

Write-Host "Checking Microsoft Teams ownership..." -ForegroundColor Cyan
$teamsOwners = Get-GuestTeamOwners -Connection $conn

Write-Host "Checking SharePoint site collection administrators..." -ForegroundColor Cyan
$spoAdmins = Get-GuestSPOAdmins -Connection $conn

# ------------------------------------------------------------
# Consolidate Results
# ------------------------------------------------------------
$finalResults = @()
$finalResults += $aadRoles
$finalResults += $teamsOwners
$finalResults += $spoAdmins

# ------------------------------------------------------------
# Export
# ------------------------------------------------------------
$finalResults |
    Sort-Object UserPrincipalName, PermissionType |
    Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8

Write-Host "Completed. Results exported to $OutputPath" -ForegroundColor Green

Check out the PnP PowerShell to learn more at: https://aka.ms/pnp/powershell

The way you login into PnP PowerShell has changed please read PnP Management Shell EntraID app is deleted : what should I do ?

Contributors

Author(s)
Josiah Opiyo

Disclaimer

THESE SAMPLES ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

Back to top Script Samples
Generated by DocFX with Material UI