Which applications still use EWS in your tenant
No single place in Microsoft 365 lists every application that uses EWS. The usage report shows what ran recently and the permissions show what is allowed to run. If Microsoft built your allow list, it holds only the applications seen in the previous 60 days. The PowerShell on this page reads all of these places and gives each application ID a name, a kind and an owner.
Where EWS applications show up
| Place | What it shows | What it misses |
|---|---|---|
| EWS usage report | Application IDs, SOAP actions and call volume | Anything that did not run in the last 90 days. It shows IDs, not names |
| The EwsAllowedAppIDs list | The IDs allowed today, entered by you or by Microsoft | A list that Microsoft built misses applications that did not run in the 60 days before |
| EWS permissions in Microsoft Entra ID | full_access_as_app (application) and EWS.AccessAsUser.All (delegated) | Whether the application still uses them |
| RBAC for Applications in Exchange Online | Applications given the Application EWS.AccessAsApp role in Exchange rather than a permission in Microsoft Entra ID | The same |
| Sign-in logs | Names and users behind IDs that are not in your directory | The protocol: Outlook, mobile mail and EWS applications all sign in to Office 365 Exchange Online |
| The audit log | Which mailboxes an application reads through EWS: MailItemsAccessed records with Client=WebServices | Mailboxes that do not record it. Microsoft turns it on by default for users with E3 or E5 licenses |
An application that holds an EWS permission but never appears in the usage report is not always unused. A job that runs once a quarter looks exactly like that, and a list built from 60 days of traffic leaves it out.
Steps 2 to 4 read the first four places and write the results to one file. Every command in them only reads.
1. Export the usage report
In Microsoft 365 admin center, open Reports, Usage, Exchange, EWS usage ↗, choose 90 days and export the report to CSV. Microsoft adds usage to it once a week, so the last 10 days can be missing. Outside Microsoft’s Worldwide cloud the report is not available; skip the first block of step 3.
2. Sign in
Run the blocks of steps 2 to 4 one after another in one PowerShell window. They sign in as you, so there is no app registration to create. Use an administrator account that can run Exchange Online PowerShell and consent to Directory.Read.All in Microsoft Graph. Install the modules once:
Install-Module Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns -Scope CurrentUser
Install-Module ExchangeOnlineManagement -Scope CurrentUserThen sign in and set up what the next blocks share: the IDs of Exchange Online, Microsoft Graph and Microsoft’s own tenants, and Add-App, which keeps one row per application ID and notes where the ID was found.
Connect-MgGraph -Scopes "Directory.Read.All" -NoWelcome
Connect-ExchangeOnline -ShowBanner:$false
$exchangeOnlineAppId = "00000002-0000-0ff1-ce00-000000000000"
$microsoftGraphAppId = "00000003-0000-0000-c000-000000000000"
$microsoftTenantIds = "f8cdef31-a31e-4b4a-93e4-5f571e91255a", "72f988bf-86f1-41af-91ab-2d7cd011db47"
$tenantId = (Get-MgContext).TenantId
$guid = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
$apps = @{}
function Add-App($AppId, $FoundIn) {
$id = "$AppId".Trim().ToLower()
if (-not $apps[$id]) {
$apps[$id] = [pscustomobject]@{
AppId = $id; Name = ""; Kind = ""; Publisher = ""; Owners = ""
FoundIn = @(); Calls = 0; SoapActions = @()
}
}
if ($apps[$id].FoundIn -notcontains $FoundIn) { $apps[$id].FoundIn += $FoundIn }
$apps[$id]
}3. Read the four places
The usage report
Reads the CSV from step 1 and adds up the calls and SOAP actions of each application ID. It finds the columns by name, so it also reads the export’s own headers, such as AppID and SoapAction.
$report = Import-Csv "$HOME\Downloads\<the exported file>.csv"
$columns = $report[0].PSObject.Properties.Name
$idColumn = $columns -match "app.*id" | Select-Object -First 1
$actionColumn = $columns -match "action" | Select-Object -First 1
$callsColumn = $columns -match "call" | Select-Object -First 1
foreach ($row in $report) {
if ($row.$idColumn -notmatch $guid) { continue }
$app = Add-App $row.$idColumn "usage report"
$app.Calls += [long]($row.$callsColumn -replace "\D", "")
$action = $row.$actionColumn -replace ".*/", ""
if ($app.SoapActions -notcontains $action) { $app.SoapActions += $action }
}The allow list
Adds the IDs on EwsAllowedAppIDs and prints EwsEnabled. The retirement dates guide explains what each value of EwsEnabled means.
$config = Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy
"EwsEnabled: $($config.EwsEnabled)"
foreach ($id in $config.EwsAllowedAppIDs -split ",") {
if ($id -match $guid) { $null = Add-App $id "allow list" }
}EWS permissions in Microsoft Entra ID
Adds the applications that hold full_access_as_app on Office 365 Exchange Online, and those granted EWS.AccessAsUser.All, which both Exchange Online and Microsoft Graph offer.
$exchange = Get-MgServicePrincipal -Filter "appId eq '$exchangeOnlineAppId'"
$graph = Get-MgServicePrincipal -Filter "appId eq '$microsoftGraphAppId'"
$fullAccess = ($exchange.AppRoles | Where-Object Value -eq "full_access_as_app").Id
foreach ($assignment in Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $exchange.Id -All) {
if ($assignment.AppRoleId -eq $fullAccess) {
$null = Add-App (Get-MgServicePrincipal -ServicePrincipalId $assignment.PrincipalId).AppId "full_access_as_app"
}
}
foreach ($resource in $exchange, $graph) {
foreach ($grant in Get-MgOauth2PermissionGrant -Filter "resourceId eq '$($resource.Id)'" -All) {
if (($grant.Scope -split " ") -contains "EWS.AccessAsUser.All") {
$null = Add-App (Get-MgServicePrincipal -ServicePrincipalId $grant.ClientId).AppId "EWS.AccessAsUser.All"
}
}
}RBAC for Applications in Exchange Online
Adds the applications that Exchange Online gives an EWS role, such as Application EWS.AccessAsApp.
foreach ($principal in Get-ServicePrincipal) {
$roles = Get-ManagementRoleAssignment -RoleAssignee $principal.Identity | Where-Object Role -like "*EWS*"
if ($roles) { $null = Add-App $principal.AppId "Exchange RBAC" }
}4. Name each application
Looks up each ID in your directory, sorts the applications into four kinds and writes them to ews-apps.csv: the ID, the name, the kind, the vendor or the owners, where the ID was found, and the calls and SOAP actions from the report.
foreach ($app in $apps.Values) {
$sp = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'"
if (-not $sp) { $app.Kind = "Not in your directory"; continue }
$app.Name = $sp.DisplayName
if ($sp.ServicePrincipalType -eq "ManagedIdentity" -or $sp.AppOwnerOrganizationId -eq $tenantId) {
$app.Kind = "Your own"
$registration = Get-MgApplication -Filter "appId eq '$($app.AppId)'"
if ($registration) {
$app.Owners = (Get-MgApplicationOwnerAsUser -ApplicationId $registration.Id).UserPrincipalName -join "; "
}
} elseif ($microsoftTenantIds -contains $sp.AppOwnerOrganizationId) {
$app.Kind = "Microsoft"
} else {
$app.Kind = "Vendor"
$app.Publisher = $sp.VerifiedPublisher.DisplayName
}
}
$result = $apps.Values | Sort-Object Kind, Name | Select-Object AppId, Name, Kind, Publisher, Owners,
@{ Name = "FoundIn"; Expression = { $_.FoundIn -join "; " } }, Calls,
@{ Name = "SoapActions"; Expression = { $_.SoapActions -join " " } }
$result | Export-Csv .\ews-apps.csv -NoTypeInformation -Encoding UTF8
$result | Format-Table AppId, Name, Kind, FoundIn -AutoSizeSteps 2 to 4 are also one file: Find-EwsApps.ps1. Run it with the CSV from step 1, or without -UsageReport where the report is not available. If PowerShell refuses to run a downloaded script, run Unblock-File .\Find-EwsApps.ps1 first.
.\Find-EwsApps.ps1 -UsageReport "$HOME\Downloads\<the exported file>.csv"Microsoft’s field engineers publish a script of their own, Exchange-App-Usage-Reporting ↗. It adds a search of the audit log for EWS activity, which helps in clouds without the usage report, and it runs as an app registration that you create for it.
5. Read the result
| Kind | How the script decides | Who can move it off EWS |
|---|---|---|
| Microsoft | The application is registered in a Microsoft tenant | Microsoft, through updates |
| Vendor | The application is registered in another organization’s tenant. The Publisher column shows the vendor if Microsoft has verified it | The vendor |
| Your own | The application is registered in your tenant, or it is a managed identity of one of your Azure resources | Your developers. The Owners column lists the owners of the app registration |
| Not in your directory | Your tenant has no service principal for the ID | Usually Microsoft; see below |
Microsoft’s own applications do not always have a service principal in your tenant. For an ID that is not in your directory, check Microsoft’s list of common first-party application IDs ↗, then open the sign-in logs in Microsoft Entra admin center and filter by the application ID: the entries show the name of the application and the users who signed in with it. The logs go back 7 days without Microsoft Entra ID P1 or P2 and 30 days with it.
Two IDs come up often:
f8d98a96-0999-43f5-8af3-69971c7bb423, named iOS Accounts or Apple Internet Accounts, is Mail, Calendar and Contacts on a Mac. iPhone and iPad connect through Exchange ActiveSync, the Mac through EWS. Apple says it is moving them to Microsoft Graph in a future update to macOS 27.d3590ed6-52b3-4102-aeff-aad2292ab01c, Microsoft Office, is the ID of Office clients such as classic Outlook for Mac, which Microsoft says needs it on the list.
Microsoft also lists features of its own that stop working when EWS is turned off: Excel, Power BI and Power Platform dataflows, cross-tenant free/busy and server-side sync from Dynamics on-premises, among others. They are on the baseline security mode settings ↗ page.
Your EWS app list covers what to do with each kind: what to ask a vendor, and the three routes for your own code.
What no list shows
- Free/busy, MailTips and calendar sharing with other organizations through organization relationships. They use EWS without OAuth, so they have no application ID. Microsoft keeps them working until April 1, 2027 when EWSEnabled is True in both organizations, whatever the list holds, and is moving them to cross-tenant access policies.
- Code that has not run in the report period and has no permission yet, such as a script waiting to be scheduled. Search your repositories for
Microsoft.Exchange.WebServices,ExchangeService,/EWS/Exchange.asmxandexchangelib. - Mailboxes on Exchange Server. The retirement concerns Exchange Online only, so an application that reaches on-premises mailboxes keeps EWS there.
Next: the list and the code
Delete the rows of applications that no longer need EWS, then set the list from the file. The list replaces the old one in full, and a change can take up to 24 hours to apply:
$ids = (Import-Csv .\ews-apps.csv).AppId -join ","
Set-OrganizationConfig -EwsEnabled $true -EwsAllowedAppIDs $idsRead the retirement dates before you change EwsEnabled: what happens depends on its value today.
For your own .NET applications, load the CSV from step 1 into the usage checker, or paste the SoapActions of one application. It shows which of those operations Sunsetless EWS covers, so you can decide whether to rewrite the code for Microsoft Graph or keep it and swap the package.
Sources
- Microsoft Learn: EWS usage report ↗
- Microsoft, “Notes From the Field: Finding and Remediating EWS App Usage Before Retirement” ↗
- Microsoft, “EWS Deprecation Is Here”, October 1, 2026 ↗
- Microsoft Learn: authenticate an EWS application by using OAuth ↗
- Microsoft Learn: RBAC for Applications in Exchange Online ↗
- Microsoft Learn: verify first-party Microsoft applications in sign-in reports ↗
- Microsoft, “Cross-tenant Free/Busy, MailTips, and Calendar Sharing are moving to Cross-Tenant Access Policy” ↗
- Microsoft Learn: MailItemsAccessed ↗
- Microsoft Learn: Exchange audit log properties ↗
- Microsoft, “Microsoft and Apple Working Together to Improve Exchange Online Security” ↗
- Apple: Integrate Apple devices with Microsoft Exchange ↗