public/maester/entra/Test-MtSpExchangeAppAccessPolicy.ps1

function Test-MtSpExchangeAppAccessPolicy {
    <#
    .SYNOPSIS
    Check if service principals with Exchange permissions have application access policies configured.

    .DESCRIPTION
    Service principals with Exchange permissions can access all mailboxes by default. This test verifies that proper access policies are in place.

    .EXAMPLE
    Test-MtSpExchangeAppAccessPolicy

    Returns true if all service principals with Exchange permissions have access policies configured

    .LINK
    https://maester.dev/docs/commands/Test-MtSpExchangeAppAccessPolicy
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param()

    Write-Verbose 'Running Test-MtSpExchangeAppAccessPolicy'

    if (-not (Test-MtConnection ExchangeOnline)) {
        Add-MtTestResultDetail -SkippedBecause NotConnectedExchange
        return $null
    }

    try {
        # Note: If you make any changes to this list, please keep it in sync
        # with the markdown file Test-MtSpExchangeAppAccessPolicy.md
        $exchangePermissions = @(
            'Mail.Read', 'Mail.ReadBasic', 'Mail.ReadBasic.All', 'Mail.ReadWrite', 'Mail.Send',
            'MailboxSettings.Read', 'MailboxSettings.ReadWrite',
            'Calendars.Read', 'Calendars.ReadWrite',
            'Contacts.Read', 'Contacts.ReadWrite'
        )

        # Get service principals with Exchange permissions
        $msGraph = Invoke-MtGraphRequest -RelativeUri 'servicePrincipals' -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
        $availablePermissions = $msGraph.AppRoles | Select-Object Id, Value

        $servicePrincipals = Invoke-MtGraphRequest -RelativeUri 'servicePrincipals'
        $principalsWithExchangePerms = $servicePrincipals | ForEach-Object {
            $sp = $_
            $appRoles = Invoke-MtGraphRequest -RelativeUri "servicePrincipals/$($sp.Id)/appRoleAssignments"
            $permissions = $appRoles.AppRoleId | ForEach-Object {
                $roleId = $_
                ($availablePermissions | Where-Object { $_.Id -eq $roleId }).Value
            }

            if ($permissions | Where-Object { $_ -in $exchangePermissions }) {
                [PSCustomObject]@{
                    Id          = $sp.Id
                    DisplayName = $sp.DisplayName
                    AppId       = $sp.AppId
                    Permissions = $permissions -join ', '
                }
            }
        }

        # Get application access policies
        try {
            $appAccessPolicies = Get-ApplicationAccessPolicy -ErrorAction Stop
        } catch {
            if ($_.Exception.Message -like "*couldn't be found*") {
                Write-Verbose -Message 'Test-MtSpExchangeAppAccessPolicy: No application access policies were found.'
                $appAccessPolicies = $null
            } else {
                throw
            }
        }

        $missingPolicies = @()
        $appsWithPolicies = @()
        foreach ($sp in $principalsWithExchangePerms) {
            $hasPolicy = $appAccessPolicies.AppId -contains $sp.AppId
            $filteredPermissions = $sp.Permissions -split ', ' | Where-Object { $_ -in $exchangePermissions }
            $appRecord = [PSCustomObject]@{
                Id          = $sp.Id
                DisplayName = $sp.DisplayName
                AppId       = $sp.AppId
                Permissions = $filteredPermissions -join ', '
            }

            if (-not $hasPolicy) {
                $missingPolicies += $appRecord
            } else {
                $appsWithPolicies += $appRecord
            }
        }

        $appsWithExchangePermissionsCount = ($principalsWithExchangePerms | Measure-Object).Count
        $invalidCount = ($missingPolicies | Measure-Object).Count
        $result = $invalidCount -eq 0

        if ($appsWithExchangePermissionsCount -eq 0) {
            $testResultMarkdown = 'Well done. No applications with Exchange permissions were found.'
        } elseif ($result) {
            $testResultMarkdown = "Well done. All **$appsWithExchangePermissionsCount** applications with Exchange permissions have Exchange application access policies configured."
        } else {
            $testResultMarkdown = "Found **$appsWithExchangePermissionsCount** applications with Exchange permissions. **$invalidCount** application(s) do not have Exchange application access policies configured."

            $missingPoliciesMarkdown = "`n`n### Applications Missing Exchange Application Access Policies`n`n"
            $missingPoliciesMarkdown += "| Application | Application ID | Exchange Permissions |`n"
            $missingPoliciesMarkdown += "| --- | --- | --- |`n"

            foreach ($app in ($missingPolicies | Sort-Object -Property DisplayName)) {
                $portalLink = Get-MtLinkServicePrincipal -ServicePrincipal $app -Blade Permissions
                $missingPoliciesMarkdown += "| $portalLink | $($app.AppId) | $($app.Permissions) |`n"
            }

            $testResultMarkdown += $missingPoliciesMarkdown
        }

        if ($appsWithPolicies.Count -gt 0 -and $invalidCount -gt 0) {
            $testResultMarkdown += "`n`n**Applications with access policies configured:** $($appsWithPolicies.Count)"
        }

        Add-MtTestResultDetail -Result $testResultMarkdown
        return $result
    } catch {
        Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
        return $null
    }
}