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
- Grant the above app the API permissions for Entra, M365 or both:
- Microsoft Entra ID API Permissions
- Microsoft M365 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)
}
}
}