Microsoft's multi-year retirement of Exchange Web Services (EWS) in Exchange Online has entered its active enforcement window. While on-premises Exchange Server Subscription Edition (SE) continues to support EWS, Exchange Online (https://outlook.office365.com/EWS/Exchange.asmx) is phasing out legacy EWS access in favor of Microsoft Graph.
For enterprise Exchange administrators, waiting until Microsoft hard-blocks EWS across your tenant is a recipe for broken ticketing systems, silent scanner failures, and failed PowerShell automation scripts. Instead, you should proactively audit active EWS App IDs, lock down your tenant using EwsApplicationAccessPolicy: EnforceAllowList, disable EWS on standard user mailboxes via Set-CASMailbox, and migrate remaining Microsoft.Exchange.WebServices.dll scripts to the Microsoft Graph PowerShell SDK.
Every command in this guide was live-tested against our LAB 365 (lab365.in / xzf58.onmicrosoft.com) tenant.
Architecture: Controlled EWS Phase-Out & Microsoft Graph Migration
- Check Entra ID Sign-In Logs for resource
Office 365 Exchange Online - Inspect
Get-OrganizationConfig | Select Ews* - Identify legacy
full_access_as_app&EWS.AccessAsUser.Allapps
- Set
-EwsApplicationAccessPolicy EnforceAllowList - Populate
-EwsAllowListwith approved User-Agents - Disable EWS per mailbox via
Set-CASMailbox -EwsEnabled $false
- Replace
ExchangeServicewithSend-MgUserMail&Get-MgUserMessage - Scope via
New-ApplicationAccessPolicyor RBAC for Applications - Set
Set-OrganizationConfig -EwsEnabled $false
Understanding the 4 Layers of EWS Control in Exchange Online
Before flipping switches in production, you need to understand how Exchange Online evaluates EWS requests across four distinct control planes:
| Control Plane | Cmdlet / Mechanism | Scope | Precedence & Behavior |
|---|---|---|---|
| 1. Organization Master Switch | Set-OrganizationConfig -EwsEnabled | Entire Tenant | $null (default/enabled), $true, or $false. Setting $false blocks EWS globally unless overridden at the mailbox level. |
| 2. Per-Mailbox Switch | Set-CASMailbox -EwsEnabled | Individual Mailbox | Overrides OrganizationConfig. If Org is $false and Mailbox is $true, that specific mailbox can still use EWS. |
| 3. User-Agent Access Policy | Set-OrganizationConfig -EwsApplicationAccessPolicy | Tenant or Mailbox | EnforceAllowList or EnforceBlockList matching the client's HTTP User-Agent string (supports wildcards like MyCRMApp/). |
| 4. Entra ID OAuth Permission | full_access_as_app / EWS.AccessAsUser.All | App Registration | Controls whether Entra ID issues an EWS-capable token to a specific third-party or internal application. |
Step 1: Audit Current Tenant & Mailbox EWS Settings
Run the following PowerShell script using ExchangeOnlineManagement (or unattended certificate auth) to inspect your tenant's current global EWS posture and find any mailboxes that have explicit per-mailbox overrides:
# Connect using Certificate-Based App-Only Authentication
Connect-ExchangeOnline `
-AppId "c841ae33-4093-4385-8a90-6a530761f9b9" `
-CertificateThumbprint "BEAC429DE404B46FF030811C0C9DE00DE701C027" `
-Organization "xzf58.onmicrosoft.com" `
-ShowBanner:$false
# 1. Inspect Organization-Level EWS Configuration
Write-Host "=== Organization EWS Posture ===" -ForegroundColor Cyan
Get-OrganizationConfig | Select-Object `
Name, `
EwsEnabled, `
EwsApplicationAccessPolicy, `
EwsAllowList, `
EwsBlockList, `
EwsAllowOutlook, `
EwsAllowMacOutlook, `
EwsAllowEntourage | Format-List
# 2. Audit Mailboxes with EWS Enabled
Write-Host "=== Mailbox-Level EWS Status ===" -ForegroundColor Cyan
$CasMailboxes = Get-CASMailbox -ResultSize Unlimited |
Select-Object DisplayName, PrimarySmtpAddress, EwsEnabled, EwsApplicationAccessPolicy, EwsAllowList
$CasMailboxes | Format-Table DisplayName, PrimarySmtpAddress, EwsEnabled -AutoSize
Live Output from Our LAB 365 (xzf58.onmicrosoft.com) Tenant:
=== Organization EWS Posture ===
Name : xzf58.onmicrosoft.com
EwsEnabled :
EwsApplicationAccessPolicy :
EwsAllowList :
EwsBlockList :
EwsAllowOutlook :
EwsAllowMacOutlook :
EwsAllowEntourage :
=== Mailbox-Level EWS Status ===
DisplayName PrimarySmtpAddress EwsEnabled
----------- ------------------ ----------
Discovery Search Mailbox DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}@xzf58.onmicrosoft.com True
Sunil Chauhan [email protected] True
Isaiah Langer [email protected] True
Alex Wilber [email protected] True
Miriam Graham [email protected] True
Key Takeaway: WhenGet-OrganizationConfigshowsEwsEnabled :(blank/$null) andGet-CASMailboxshowsEwsEnabled : True, every mailbox and every third-party application in your tenant is currently permitted to make EWS calls.
Step 2: Discover Which Entra ID Apps Hold EWS Permissions (full_access_as_app)
Even if you don't have EWS HTTP logs handy, you can immediately find every Enterprise Application (Service Principal) in your tenant that has been granted Exchange Online EWS Application (full_access_as_app — ID dc890d15-9560-4a4c-9b7f-a736ec74ec40) or Delegated (EWS.AccessAsUser.All — ID 3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5) permissions:
# Find all Service Principals granted EWS App-Only (full_access_as_app) or Delegated (EWS.AccessAsUser.All)
Connect-MgGraph -ClientId "c841ae33-4093-4385-8a90-6a530761f9b9" `
-TenantId "ab85c7a1-366d-44ba-acc6-924fa0950045" `
-CertificateThumbprint "BEAC429DE404B46FF030811C0C9DE00DE701C027" `
-NoWelcome
$ExoSp = Get-MgServicePrincipal -Filter "appId eq '00000002-0000-0ff1-ce00-000000000000'"
$EwsAppRoleId = "dc890d15-9560-4a4c-9b7f-a736ec74ec40" # full_access_as_app
$AppRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ExoSp.Id -All |
Where-Object { $_.AppRoleId -eq $EwsAppRoleId }
foreach ($Assignment in $AppRoleAssignments) {
[PSCustomObject]@{
ApplicationName = $Assignment.PrincipalDisplayName
PrincipalId = $Assignment.PrincipalId
Permission = "full_access_as_app (EWS App-Only)"
CreatedDateTime = $Assignment.CreatedDateTime
}
}
Step 3: Stage a Controlled Lockdown Using EwsApplicationAccessPolicy and Set-CASMailbox
Rather than turning off EWS cold-turkey on Monday morning, use a two-pronged containment strategy:
Option A: Restrict EWS to Specific Service Mailboxes Only
Disable EWS across all standard user mailboxes while keeping it enabled only on the specific service/shared mailboxes required by a legacy line-of-business app during its migration window:# 1. Disable EWS at the Organization level
Set-OrganizationConfig -EwsEnabled:$false
# 2. Explicitly disable EWS on standard user mailboxes
$ExemptMailboxes = @("[email protected]", "[email protected]")
Get-CASMailbox -ResultSize Unlimited | Where-Object {
$_.EwsEnabled -eq $true -and $_.PrimarySmtpAddress -notin $ExemptMailboxes
} | ForEach-Object {
Set-CASMailbox -Identity $_.PrimarySmtpAddress -EwsEnabled:$false
}
# 3. Keep EWS explicitly enabled ONLY on the exempt service mailboxes during migration
foreach ($Mbx in $ExemptMailboxes) {
Set-CASMailbox -Identity $Mbx -EwsEnabled:$true
}
Option B: Restrict EWS by HTTP User-Agent (EnforceAllowList)
If you must allow Mac Outlook (MacOutlook/) or one specific vendor integration while blocking arbitrary PowerShell EWS scripts (ExchangeServicesClient/*), enforce an organization-wide EwsAllowList:
Set-OrganizationConfig `
-EwsEnabled:$true `
-EwsAllowMacOutlook:$true `
-EwsAllowOutlook:$true `
-EwsApplicationAccessPolicy EnforceAllowList `
-EwsAllowList @{Add="MacOutlook/*", "VendorTicketingApp/*"}
Step 4: Replace Legacy EWS Scripts with Microsoft Graph (Send-MgUserMail & Get-MgUserMessage)
Here is the direct side-by-side translation for the two most common EWS tasks—sending mail and reading inbox messages—using the Microsoft Graph PowerShell SDK:
Legacy EWS Managed API (.NET) | Modern Microsoft Graph Replacement | Required Graph Permission |
|---|---|---|
EmailMessage.SendAndSaveCopy() | Send-MgUserMail (POST /users/{id}/sendMail) | Mail.Send |
ExchangeService.FindItems(WellKnownFolderName.Inbox) | Get-MgUserMailFolderMessage -MailFolderId Inbox | Mail.Read |
Appointment.Save(SendInvitationsMode) | New-MgUserEvent (POST /users/{id}/events) | Calendars.ReadWrite |
ApplicationImpersonation management role | RBAC for Applications (New-ManagementScope + New-ManagementRoleAssignment -App) | Scoped to specific security group / OU |
# Modern Replacement for EWS EmailMessage.SendAndSaveCopy()
$MailBody = @{
Message = @{
Subject = "[LAB 365] Automated Alert via Microsoft Graph API"
Body = @{
ContentType = "HTML"
Content = "<p>This message was sent via <strong>Microsoft Graph</strong> (replacing legacy EWS).</p>"
}
ToRecipients = @(
@{ EmailAddress = @{ Address = "[email protected]" } }
)
}
SaveToSentItems = $true
}
Send-MgUserMail -UserId "[email protected]" -BodyParameter $MailBody
Pre-Retirement Verification Checklist
- Audit Entra Sign-In Logs: Filter Service Principal Sign-ins by Resource =
Office 365 Exchange Onlineto identify any background daemon still acquiring tokens for EWS. - Scope Graph Permissions: Never replace EWS
ApplicationImpersonationwith an unscoped tenant-wideMail.ReadWritepermission. Always bind your Graph app to a Management Scope using Exchange Online RBAC for Applications (New-ManagementRoleAssignment -App <ServicePrincipalId> -Role "Application Mail.Send" -CustomResourceScope <ScopeName>). - Flip
-EwsEnabled:$falseAhead of Microsoft's Deadline: Test disabling EWS on your schedule during a controlled change window so you can immediately spot and remediate any undocumented dependencies.