Manually Assign Remediation Roles to Optimize365
Overview
Optimize365's remediation features use a separate app registration, Optimize365-Write, which needs elevated Microsoft Entra roles beyond Global Reader. These roles let Optimize365 fix misconfigurations, enforce baselines, and apply security and compliance changes automatically. Use this article when the in-app Optimize Role Granter isn't an option and you need to assign the roles yourself, either in the Microsoft Entra admin center or with a PowerShell script.
Why These Roles Are Required
Remediation goes beyond visibility: it needs write permissions to enforce security and compliance changes across Microsoft 365 services. Without these roles, Optimize365 is limited to read-only scanning.
The required roles are:
- Exchange Administrator
- Security Administrator
- Compliance Administrator
- Conditional Access Administrator
- Authentication Administrator
- SharePoint Administrator
- Teams Administrator
- User Administrator
- Application Administrator
- Privileged Role Administrator
- Authentication Policy Administrator
- Cloud Application Administrator
- Groups Administrator
- Privileged Authentication Administrator
- Security Operator
- Azure Information Protection Administrator
- Cloud Device Administrator
- Global Secure Access Administrator
- Compliance Data Administrator
Prerequisites
- A Global Administrator user with access to Microsoft Entra ID
- Assign the roles in each client tenant (per tenant)
Assign the Remediation Roles
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).
Select the Exchange Administrator role to open its details.

Select Add assignments, search for "Optimize365-Write" (AppId: e5099652-03bb-4940-8490-3b3ca8e75369), and click Add.

Confirm the app now appears in the list of assignments.

Important: You must repeat steps 3–5 for every role listed above, not just Exchange Administrator. With 19 roles, Option 2 is much faster.
Option 2: PowerShell script
Download the script: assign-rem-roles.ps1, or run the PowerShell below. It connects to Microsoft Graph, finds the Optimize365-Write service principal, activates each role if needed, assigns all 19 roles, and verifies the assignments.
# 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 = "e5099652-03bb-4940-8490-3b3ca8e75369"
$rolesToAssign = @(
"Exchange Administrator",
"Security Administrator",
"Compliance Administrator",
"Conditional Access Administrator",
"Authentication Administrator",
"SharePoint Administrator",
"Teams Administrator",
"User Administrator",
"Application Administrator",
"Privileged Role Administrator",
"Authentication Policy Administrator",
"Cloud Application Administrator",
"Groups Administrator",
"Privileged Authentication Administrator",
"Security Operator",
"Azure Information Protection Administrator",
"Cloud Device Administrator",
"Global Secure Access Administrator",
"Compliance Data Administrator"
)
$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-Write app hasn't been consented in this tenant yet. Complete Step 1 of enabling remediation (Microsoft 365 admin consent) first, then run the script again. See Remediation.
The script reports "Partial success"
Some roles failed to assign. Check the results table the script prints for roles marked Failed, then assign those roles manually with Option 1.
Need more help
Contact support@optimize365.io.
Security Notes
- These are highly privileged roles, including Privileged Role Administrator and Privileged Authentication Administrator. Assign them only in tenants where you intend to use remediation.
- 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. - Before remediating authentication or Conditional Access controls, make sure break glass accounts are in place. See Break Glass Accounts and Lockout Risk.