Manually Assign the Global Reader Role to Optimize365
Overview
Optimize365's security scanning needs the Global Reader role assigned to the Optimize365-ReadOnly app registration. Global Reader lets the platform perform read-only scans of your client's security configuration without making any changes. Use this article when the in-app Optimize Role Granter isn't an option and you need to assign the role yourself, either in the Microsoft Entra admin center or with a PowerShell script.
Why Global Reader Is Required
Without the Global Reader role, Optimize365 can scan only a limited set of security controls. Some controls aren't supported through Microsoft Graph and must be checked with PowerShell cmdlets, which need this role.
Basic Security Baseline (no Global Reader needed)
For lightweight prospecting and visibility, Optimize365 supports a Basic Security Baseline. It provides a limited set of controls that do not require the Global Reader role, so you can get started with minimal friction.
Note: The Basic Baseline is intended for entry-level/prospecting scans and insight, and does not require enabling the full security baseline.
Prerequisites
- A Global Administrator user with access to Microsoft Entra ID
- Repeat the assignment for each client tenant (you may need to do this per tenant)
Assign Global Reader
Option 1: Microsoft Entra admin center
Sign in to the Microsoft Entra admin center.
Go to Entra ID > Roles & admins (click Show more... if you can't see it).
Double-click Global Reader to open it.

Select Add assignments, search for "Optimize365-ReadOnly" (AppId: eb18d58b-c50f-4478-94dc-15ef9f12e34e), and click Add.

Confirm the app now appears in the list of assignments.

Option 2: PowerShell script
Download the script: assign-global-reader.ps1, or run the PowerShell below. It connects to Microsoft Graph, finds the Optimize365-ReadOnly service principal, activates the Global Reader role if needed, assigns it, and then verifies the assignment.
# Disconnect any existing sessions
Disconnect-MgGraph -ErrorAction SilentlyContinue
# Connect with required scopes
Connect-MgGraph -Scopes @(
"RoleManagement.ReadWrite.Directory",
"Directory.ReadWrite.All",
"Application.Read.All"
)
$appId = "eb18d58b-c50f-4478-94dc-15ef9f12e34e"
$rolesToAssign = @(
"Global Reader"
)
$servicePrincipal = Get-MgServicePrincipal -Filter "appId eq '$appId'"
if (-not $servicePrincipal) {
Write-Host "❌ Service principal not found" -ForegroundColor Red
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
exit
}
Write-Host "✅ Found: $($servicePrincipal.DisplayName)`n" -ForegroundColor Green
$roleTemplates = Get-MgDirectoryRoleTemplate -All
$activatedRoles = Get-MgDirectoryRole -All
$assignmentResults = @()
foreach ($roleName in $rolesToAssign) {
$result = [PSCustomObject]@{
RoleName = $roleName
Status = "Unknown"
Action = ""
}
$roleTemplate = $roleTemplates | Where-Object { $_.DisplayName -eq $roleName }
if (-not $roleTemplate) {
$result.Status = "Failed"
$result.Action = "Template not found"
$assignmentResults += $result
continue
}
$role = $activatedRoles | Where-Object { $_.RoleTemplateId -eq $roleTemplate.Id }
if (-not $role) {
try {
$role = New-MgDirectoryRole -RoleTemplateId $roleTemplate.Id
$result.Action = "Activated"
} catch {
$result.Status = "Failed"
$result.Action = "Activation failed"
$assignmentResults += $result
continue
}
}
try {
$members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All
if ($members | Where-Object { $_.Id -eq $servicePrincipal.Id }) {
$result.Status = "Success"
$result.Action = "Already assigned"
} else {
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$($servicePrincipal.Id)"
}
$result.Status = "Success"
$result.Action = "Newly assigned"
}
} catch {
$result.Status = "Failed"
$result.Action = "Assignment failed"
}
$assignmentResults += $result
}
Start-Sleep -Seconds 5
Write-Host "Verifying assignments...`n" -ForegroundColor Cyan
$uri = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?`$filter=principalId eq '$($servicePrincipal.Id)'"
$roleAssignments = Invoke-MgGraphRequest -Method GET -Uri $uri
$verifiedRoles = @()
foreach ($assignment in $roleAssignments.value) {
$roleDefUri = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions/$($assignment.roleDefinitionId)"
$roleDef = Invoke-MgGraphRequest -Method GET -Uri $roleDefUri
$verifiedRoles += $roleDef.displayName
}
$successCount = ($assignmentResults | Where-Object { $_.Status -eq "Success" }).Count
$failedCount = ($assignmentResults | Where-Object { $_.Status -eq "Failed" }).Count
$totalCount = $rolesToAssign.Count
$verifiedCount = ($verifiedRoles | Where-Object { $rolesToAssign -contains $_ }).Count
Write-Host ("=" * 60) -ForegroundColor Cyan
Write-Host "FINAL STATUS" -ForegroundColor Cyan
Write-Host ("=" * 60) -ForegroundColor Cyan
Write-Host "Total: $totalCount | Success: $successCount | Failed: $failedCount | Verified: $verifiedCount`n"
$assignmentResults | Format-Table -Property RoleName, Status, Action -AutoSize
if ($verifiedCount -eq $totalCount) {
Write-Host "✅ All $totalCount roles assigned and verified" -ForegroundColor Green
} elseif ($verifiedCount -gt 0) {
Write-Host "⚠️ Partial success: $verifiedCount/$totalCount verified" -ForegroundColor Yellow
} else {
Write-Host "⚠️ Roles assigned but verification incomplete" -ForegroundColor Yellow
}
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
Write-Host "`n✅ Disconnected" -ForegroundColor Green
Troubleshooting
The script reports "Service principal not found"
The Optimize365-ReadOnly app hasn't been consented in this tenant yet. Complete admin consent first (click CONNECT in Optimize365, or use a consent share link), then run the script again.
The app doesn't appear when searching in Add assignments
Search by the AppId eb18d58b-c50f-4478-94dc-15ef9f12e34e instead of the name, and confirm admin consent has been completed for the tenant.
Need more help
Contact support@optimize365.io.
Security Notes
- Global Reader is a read-only role; Optimize365 uses it to scan, not to change anything.
- The script requests the Microsoft Graph scopes
RoleManagement.ReadWrite.Directory,Directory.ReadWrite.All, andApplication.Read.Allfor the signed-in admin's session, and disconnects when it finishes.