1. Knowledge Base
  2. Control Monitor
  3. Identity Management Integrations

How to connect M365 and Entra without granting Global Reader

Note: The only method from Microsoft to grant “View only recipient" as described below, is via Powershell.

Create an App Registration

Note:  This section is only necessary if you don't already have an app created for M365 or Entra ID

Navigate to the App registrations section in the Azure Portal.
Select “+ New registration” toward the top of the page.
  • Enter a name for your application
    • Choose Single tenant as the supported account type (Accounts in this organizational directory only). Click Register.
    • Leave Redirect URI (optional) as it is.
      After registration:
  • Note down the Application (client) ID and Directory (tenant) ID from the app’s Overview page.
  • In the left menu, expand the Manage section and select Certificates & secrets and create a new Client Secret:
  • Click New client secret, enter a description, and set an expiration period.
    Note down the generated Client Secret Value (you won’t be able to view it later).

we now have:

APP ID (Application (client) ID) from step 6
TENANT ID (Directory (tenant) ID) from step 6
APP SECRET (Client Secret Value) from step 7

Granting API Permissions

Assigning roles to your App

  • Save the following powershell script as a .ps1 file
  • Add your app name from the "Create An App Registration" step above in the # CONFIGURATION line, replacing "app_name" with your app name
  • Run the script
<#
.SYNOPSIS
Assigns Exchange Online roles to a Microsoft Entra ID (Azure AD) app (service principal).

.DESCRIPTION
- Connects to Microsoft Graph and Exchange Online.
- Finds the Azure AD App by display name.
- Ensures it is registered as a service principal in Exchange Online.
- Assigns one or more management roles to the service principal.
#>

# CONFIGURATION
$AppName    = "app_name"
$RoleNames  = @("View-Only Recipients", "Security Reader")
$SPName     = "SP for App $AppName"

$ProgressPreference = 'SilentlyContinue'

function Ensure-Module {
    param ([string]$Name)
    if (-not (Get-Module -ListAvailable -Name $Name)) {
        Write-Host "Installing module '$Name'..." -ForegroundColor Cyan
        Install-Module -Name $Name -Scope CurrentUser -Force -AllowClobber
    }
}

# Ensure required modules
Ensure-Module "ExchangeOnlineManagement"
Ensure-Module "Microsoft.Graph.Applications"

Import-Module ExchangeOnlineManagement -ErrorAction Stop
Import-Module Microsoft.Graph.Applications -ErrorAction Stop

# Connect to Graph and Exchange Online
try {
    Connect-MgGraph -Scopes "Application.Read.All", "Directory.Read.All"
    Connect-ExchangeOnline -ErrorAction Stop
} catch {
    Write-Error ("Connection failed: {0}" -f $_.Exception.Message)
    exit 1
}

# Find Azure AD App's Service Principal
$AzureADApp = Get-MgServicePrincipal -Filter "DisplayName eq '$AppName'"
if (-not $AzureADApp) {
    Write-Warning "Azure AD App '$AppName' not found in Microsoft Graph. Exiting."
    exit 1
}
Write-Host "Found Azure AD App in Graph: $($AzureADApp.AppId)" -ForegroundColor Green

# Create Service Principal in Exchange Online (if needed)
try {
    $existingSP = Get-ServicePrincipal | Where-Object { $_.AppId -eq $AzureADApp.AppId }
    if (-not $existingSP) {
        Write-Host "Creating service principal in Exchange Online..." -ForegroundColor Cyan
        $null = New-ServicePrincipal -AppId $AzureADApp.AppId -ObjectId $AzureADApp.Id -DisplayName $SPName -ErrorAction Stop
        Start-Sleep -Seconds 10
    } else {
        Write-Host "Service Principal already exists in Exchange Online." -ForegroundColor Green
    }
} catch {
    Write-Warning ("Could not create service principal (might already exist): {0}" -f $_.Exception.Message)
}

# Retrieve SP from Exchange Online
$SP = Get-ServicePrincipal | Where-Object { $_.AppId -eq $AzureADApp.AppId }
if (-not $SP) {
    Write-Error "Failed to find Service Principal in Exchange Online after creation."
    exit 1
}

# Assign roles to the service principal
foreach ($RoleName in $RoleNames) {
    $existingAssignment = Get-ManagementRoleAssignment -Role $RoleName -GetEffectiveUsers |
        Where-Object { $_.RoleAssigneeType -eq "ServicePrincipal" -and $_.App -eq $SP.ObjectId }

    if ($existingAssignment) {
        Write-Host "Service Principal is already assigned to role '$RoleName'. Skipping." -ForegroundColor Yellow
    } else {
        try {
            New-ManagementRoleAssignment -Role $RoleName -App $SP.ObjectId -ErrorAction Stop
            Write-Host "Role '$RoleName' assigned to Service Principal '$($SP.DisplayName)'" -ForegroundColor Green
        } catch {
            Write-Error ("Failed to assign role '{0}': {1}" -f $RoleName, $_.Exception.Message)
        }
    }
}