Exchange Web Services (EWS) Retirement in Exchange Online: How to Audit Usage, Enforce AllowLists, and Migrate to Microsoft Graph

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

Exchange Online EWS Deprecation & Lockdown Pipeline
Phase 1: Discovery
Audit Active EWS Traffic
  • 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.All apps
Phase 2: Containment
Enforce EWS AllowList
  • Set -EwsApplicationAccessPolicy EnforceAllowList
  • Populate -EwsAllowList with approved User-Agents
  • Disable EWS per mailbox via Set-CASMailbox -EwsEnabled $false
Phase 3: Decommission
Migrate to Microsoft Graph
  • Replace ExchangeService with Send-MgUserMail & Get-MgUserMessage
  • Scope via New-ApplicationAccessPolicy or 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 PlaneCmdlet / MechanismScopePrecedence & Behavior
1. Organization Master SwitchSet-OrganizationConfig -EwsEnabledEntire Tenant$null (default/enabled), $true, or $false. Setting $false blocks EWS globally unless overridden at the mailbox level.
2. Per-Mailbox SwitchSet-CASMailbox -EwsEnabledIndividual MailboxOverrides OrganizationConfig. If Org is $false and Mailbox is $true, that specific mailbox can still use EWS.
3. User-Agent Access PolicySet-OrganizationConfig -EwsApplicationAccessPolicyTenant or MailboxEnforceAllowList or EnforceBlockList matching the client's HTTP User-Agent string (supports wildcards like MyCRMApp/).
4. Entra ID OAuth Permissionfull_access_as_app / EWS.AccessAsUser.AllApp RegistrationControls 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:

POWERSHELL
# 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:

TEXT
=== 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: When Get-OrganizationConfig shows EwsEnabled : (blank/$null) and Get-CASMailbox shows EwsEnabled : 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:

POWERSHELL
# 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:
POWERSHELL
# 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:
POWERSHELL
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 ReplacementRequired Graph Permission
EmailMessage.SendAndSaveCopy()Send-MgUserMail (POST /users/{id}/sendMail)Mail.Send
ExchangeService.FindItems(WellKnownFolderName.Inbox)Get-MgUserMailFolderMessage -MailFolderId InboxMail.Read
Appointment.Save(SendInvitationsMode)New-MgUserEvent (POST /users/{id}/events)Calendars.ReadWrite
ApplicationImpersonation management roleRBAC for Applications (New-ManagementScope + New-ManagementRoleAssignment -App)Scoped to specific security group / OU
POWERSHELL
# 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

  1. Audit Entra Sign-In Logs: Filter Service Principal Sign-ins by Resource = Office 365 Exchange Online to identify any background daemon still acquiring tokens for EWS.
  2. Scope Graph Permissions: Never replace EWS ApplicationImpersonation with an unscoped tenant-wide Mail.ReadWrite permission. 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>).
  3. Flip -EwsEnabled:$false Ahead 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.