Microsoft 365 automation that still depends on Exchange Web Services (EWS) needs a plan. EWS has been the dependable workhorse for mailbox automation for years, but Microsoft now points Exchange Online developers and administrators toward Microsoft Graph for modern mail, calendar, user, and group access.
If you own an old PowerShell script, help desk tool, calendar integration, or background service that talks to /EWS/Exchange.asmx, this tutorial walks you through a practical migration path. You will inventory what the EWS workload does, map each operation to Microsoft Graph, set up the Microsoft Graph PowerShell SDK, and test the common mailbox and calendar patterns most teams need first.
Prerequisites
To follow along, you will need:
-
A Microsoft 365 tenant with Exchange Online.
-
A test mailbox you are allowed to query.
-
PowerShell 7 or Windows PowerShell 5.1.
-
Permission to use or request Microsoft Graph permissions in Microsoft Entra ID.
-
Basic familiarity with EWS concepts such as
ExchangeService, Autodiscover, impersonation, and EWS operation names.
[!NOTE]The examples below are safe patterns, but Graph calls that read real mailbox data require tenant authentication and consent. Run them in a test tenant or against a non-production mailbox first.
Understanding What Changes When You Leave EWS
EWS and Microsoft Graph solve similar business problems, but they do it with different shapes.
EWS is a SOAP-based API that sends XML over HTTP/S to Exchange. EWS applications commonly use the EWS Managed API, ExchangeService, Autodiscover, impersonation, and operation names such as FindItem, GetItem, CreateItem, and UpdateItem.
Microsoft Graph is a REST API. Instead of building SOAP envelopes, you call resources such as users, messages, mail folders, and events. Authentication and authorization move through Microsoft Entra ID and OAuth, using delegated or application permissions.
That shift means you should not treat the migration as a simple endpoint replacement. A better approach is to map behavior:
| EWS concept | Microsoft Graph concept |
|---|---|
ExchangeService client object |
Graph SDK client or REST call to https://graph.microsoft.com |
| SOAP XML request | REST request with JSON response |
| Autodiscover endpoint resolution | Graph resource paths such as /users/{id}/messages |
| EWS impersonation | Delegated permissions, application permissions, and access policies |
FindItem and GetItem |
List and get message/event endpoints |
| EWS subscriptions | Graph change notifications or delta queries |
Microsoft publishes an EWS-to-Graph API mapping reference, and that page should become part of your migration workbook.
Step 1: Inventory Every EWS Dependency
Start with discovery. Before rewriting code, find every place your environment uses EWS. Search source control, scheduled task servers, automation accounts, config files, and vendor integration settings.
Look for common EWS markers:
Select-String -Path .\* -Recurse -Pattern \
'ExchangeService','Microsoft.Exchange.WebServices','EWS/Exchange.asmx','FindItem','GetItem','AutodiscoverUrl'
[!TIP]Include configuration files in the search, not just source code. Many legacy jobs store the EWS URL or service account in a JSON, XML, INI, or scheduled task export.
For each hit, capture the following details in a spreadsheet or issue tracker:
| Field | Why it matters |
|---|---|
| Application or script name | Gives you an owner and migration scope |
| EWS operations used | Drives the Graph endpoint mapping |
| Authentication method | Determines delegated vs. application permission design |
| Mailboxes touched | Helps limit permission scope |
| Business process | Helps prioritize cutover order |
| Schedule or trigger | Identifies outage windows and rollback needs |
| Output dependencies | Shows reports, files, or downstream systems to validate |
This step is not glamorous, but it prevents the common migration failure: rewriting the visible script while missing the scheduled task that runs once a month.
Step 2: Choose Delegated or Application Permissions
Graph permissions are where many EWS migrations slow down. EWS-era code often assumes a service account can reach many mailboxes. Graph makes you describe that access through permissions and admin consent.
Use delegated permissions when the automation runs as a signed-in user and should only do what that user can do. This model fits help desk tools, interactive scripts, and user-driven mailbox utilities.
Use application permissions when a background service runs without a signed-in user. This model fits daemons, nightly jobs, and integrations. Application permissions are powerful, so do not approve broad tenant-wide access unless the workload truly needs it.
For mail workloads, Microsoft explains that Graph can access Outlook mail with the appropriate delegated or application mail permissions. Your security review should answer three questions before code is written:
-
Which mailbox data does the app need?
-
Is the app acting as a user or as itself?
-
Can access be limited to a mailbox group, test mailbox, or application access policy?
[!WARNING]Do not grant
Mail.ReadWriteor broad application permissions just to make the first proof of concept work. Start with read-only permissions, test the smallest scenario, and expand only when the migration requires it.
Step 3: Install and Connect the Microsoft Graph PowerShell SDK
The Microsoft Graph PowerShell SDK is a good bridge for administrators because it lets you test Graph behavior before you rewrite full applications.
Install the SDK from the PowerShell Gallery:
Install-Module Microsoft.Graph -Scope CurrentUser
Connect with the scopes you need for a basic mail read test:
Connect-MgGraph -Scopes 'User.Read','Mail.Read','Calendars.Read'
Check your context after signing in:
Get-MgContext | Select-Object Account, TenantId, Scopes
If your tenant requires admin consent for these scopes, the connection step will not complete until an administrator approves the permissions.
Step 4: Rewrite a Basic Mailbox Read
Many EWS scripts start by finding recent messages. In EWS, that usually means FindItem against a folder. In Graph PowerShell, start with Get-MgUserMessage.
$userId = '[email protected]' Get-MgUserMessage -UserId $userId -Top 10 -Property 'id,subject,receivedDateTime,from' | Select-Object Subject, ReceivedDateTime, @{Name='From';Expression={$_.From.EmailAddress.Address}}
The equivalent REST shape is easy to understand:
“`plain text
GET https://graph.microsoft.com/v1.0/users/[email protected]/messages?$top=10&$select=id,subject,receivedDateTime,from
Notice two important differences from EWS: - You request JSON properties with `$select` instead of binding to strongly typed EWS properties. - The message ID you receive is a Graph resource ID. Do not assume it matches an EWS ID stored in an old database. ## Step 5: Rewrite Calendar Reads Calendar integrations are another common EWS dependency. Graph exposes calendar and event resources directly. Use the SDK to list upcoming events:
$userId = ‘[email protected]’
$start = (Get-Date).ToUniversalTime().ToString(‘o’)
$end = (Get-Date).AddDays(7).ToUniversalTime().ToString(‘o’)
Get-MgUserCalendarView -UserId $userId -StartDateTime $start -EndDateTime $end |
Select-Object Subject, Start, End, Organizer
For REST-based applications, the same idea targets the calendar view endpoint: ```plain text GET https://graph.microsoft.com/v1.0/users/[email protected]/calendarView?startDateTime=2026-07-05T00:00:00Z&endDateTime=2026-07-12T00:00:00Z
This is where you should validate time zones, recurrence behavior, and organizer fields. Calendar bugs are easy to miss if you only test one simple meeting.
Step 6: Handle Message Trace and Reporting Carefully
Not every Exchange admin task has a clean one-for-one Graph replacement. For example, message trace and some reporting workflows are often better handled through Exchange Online PowerShell cmdlets rather than Graph mail endpoints.
If your EWS-era process mixes mailbox item access with Exchange reporting, split it into two workstreams:
-
Use Microsoft Graph for mailbox, user, group, and calendar operations that Graph supports.
-
Use Exchange Online PowerShell for Exchange admin reporting and transport-related tasks that remain in the Exchange management plane.
That split keeps your migration honest. The goal is not to force every Exchange task through Graph; the goal is to remove unsupported EWS dependencies without breaking the business workflow.
Step 7: Build a Validation Checklist
Before cutover, validate the migration with the people who own the process, not just the code.
Use this checklist:
-
Confirm every EWS operation has a Graph, Exchange Online PowerShell, or documented replacement path.
-
Confirm permissions are least-privilege and approved.
-
Test with a normal mailbox, shared mailbox, and any special mailbox type the workload supports.
-
Compare old and new output for the same time window.
-
Test throttling and retry behavior.
-
Log request IDs and errors so support can troubleshoot failures.
-
Keep the old job disabled but recoverable during the first production run.
A simple comparison harness can help during parallel testing:
$userId = '[email protected]' $outputPath = '.\graph-mail-sample.csv' Get-MgUserMessage -UserId $userId -Top 25 -Property 'subject,receivedDateTime,from' | Select-Object Subject, ReceivedDateTime, @{Name='From';Expression={$_.From.EmailAddress.Address}} | Export-Csv -Path $outputPath -NoTypeInformation Write-Host "Graph sample exported to $outputPath"
[!NOTE]Keep one exported sample from the old EWS job and one exported sample from the new Graph job. Ask the business owner to review the fields, not just the developer who wrote the script.
Common Migration Pitfalls
Avoid these traps as you work through the migration:
-
Assuming EWS and Graph IDs are interchangeable. Store new Graph IDs separately and plan any lookup migration.
-
Skipping consent review. A working script with excessive permissions creates a security problem.
-
Ignoring shared mailboxes. Validate shared mailbox access early because it exposes permission design issues.
-
Testing only happy-path messages. Include attachments, meetings, recurring events, and large folders.
-
Forgetting non-code dependencies. Scheduled tasks, vendor connectors, and runbooks often hide EWS URLs in configuration files.
Wrapping Up
Migrating from EWS to Microsoft Graph API is a manageable project when you treat it as a workflow migration. Inventory the old EWS behavior, map each operation, choose the right permission model, and prove the replacement with Graph PowerShell before rewriting production code.
The biggest mindset shift is moving from a service-account SOAP model to a permissioned REST model. Once you make that shift explicit, your migration plan becomes clearer: small tests, least-privilege permissions, side-by-side validation, and a controlled cutover.
Your next step is to pick one low-risk EWS script, map its operations against Microsoft’s EWS-to-Graph reference, and produce the same output with Microsoft Graph PowerShell. Once that works, you have a repeatable pattern for the rest of your EWS estate.