[BP 2.3.1] Build Sheet: Protocol Retirement Discovery

The retirements calendar tells you the clock is running. This build sheet produces the artefact that answers it: a dated inventory of everything still speaking Exchange Web Services, SMTP AUTH, POP3 or IMAP4, and an allow list built from evidence rather than inherited.


The retirements article tells you the clock is running. This one produces the artefact that answers it: a dated inventory of every application, device and service still speaking Exchange Web Services, SMTP AUTH, POP3 or IMAP4, each with an owner and a replacement path, and an EWS allow list you built from that evidence rather than inherited from Microsoft’s telemetry.

The reason this is one build sheet and not three is that it is one skill against one population. The device that submits with a password is usually the device that reads with IMAP, and the integration that calls Exchange Web Services is usually the one nobody has an owner for. The discovery you do for any of them answers most of the others, and doing it three times is how the third one never gets done.

The spine of this sheet is a single fact worth stating before anything else. The report Microsoft gives you for finding Exchange Web Services usage emits Entra application IDs, and the parameter that controls Exchange Web Services access consumes Entra application IDs. Discovery and remediation share a key. Everything else here is corroboration, and if you only do the parts that share that key you will still be most of the way there.

Before you start

You need the Exchange Online PowerShell module and an account holding Exchange Administrator or Global Administrator, because the allow list is written with Set-OrganizationConfig. You need at least Reports Reader in Entra to read sign-in logs, and a role that can open the usage reports in the Microsoft 365 admin center. Whether the Exchange Web Services usage report appears on every subscription is not documented; if the tab is absent, the sign-in log path in Step 3 covers the same ground more slowly.

One capability check before you plan the work. The EwsAllowedAppIDs parameter is rolling out, and Microsoft’s own note is that every tenant can read it while not every tenant can yet write it. Read it first, on the day you start, so you find out now rather than at the end of a fortnight of discovery.

The worked estate is the one from the mail authentication sheet: catsnackjack.com, a few hundred mailboxes, a scanner in the warehouse, a room-booking panel in each meeting room, a signature manager, a backup product and a line-of-business ordering system that predates everyone currently employed.


Step 1: read the current state, before you change any of it

Write these values down before you touch anything, because several of them are about to become the evidence that you did not break something. Two of the reads have a trap in them and both traps produce a blank or absent value rather than an error, which is the worst way for a check to fail.

Connect-ExchangeOnline

# The Exchange Web Services posture. Null is the default and is the finding.
Get-OrganizationConfig |
  Format-List Name,EwsEnabled,EwsApplicationAccessPolicy,EwsAllowList,EwsBlockList

# EwsAllowedAppIDs does NOT appear without this switch. A plain Get-OrganizationConfig
# omits it entirely, which reads as "we have no allow list" whether or not that is true.
Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy |
  Format-List EwsAllowedAppIDs

# Mail submission and the legacy TLS opt-in, both organisation-wide.
Get-TransportConfig |
  Format-List SmtpClientAuthenticationDisabled,AllowLegacyTLSClients

The switch on the second command exists for performance: Microsoft retrieves the list only when you ask for it by name. That is a reasonable design and a poor default for anyone auditing a tenant, because the parameter is simply not in the output otherwise.

SettingWhat you are hoping to seeWhy it matters
EwsEnabledTrue, with a populated allow listNull is the default and behaves as allow-all today. On the October date Microsoft changes Null to False as the phased rollout reaches the tenant, and everything stops
EwsAllowedAppIDsA list you recognise, or an empty value you are about to fillApp-ID based. From October, EwsEnabled set to True with an empty list is a block-all configuration, not an allow-all one
EwsApplicationAccessPolicyUsually unset in an estate that has never been tunedUser-agent based, a separate and older mechanism, and it applies to REST as well as Exchange Web Services. It keeps working alongside the App ID list and an application has to satisfy both
SmtpClientAuthenticationDisabledTrue organisation-wide, with named exceptionsA $null at mailbox level inherits this value, which is why a per-mailbox inventory of SMTP AUTH is a reconciliation and not an inventory
AllowLegacyTLSClientsFalseTrue is a standing declaration that something in the estate still needs TLS 1.0 or 1.1, and it is the single best indicator for the POP and IMAP work in Step 5

On the last row, be precise about who is affected. Microsoft’s expectation, stated in the announcement, is that only customers who explicitly opted into the legacy endpoints are impacted by the TLS retirement. If AllowLegacyTLSClients returns False and no client is pointed at a -legacy endpoint, that item is already closed for you and you can say so rather than carrying it as an open risk.


Step 2: pull the Exchange Web Services usage report

This is the highest-value half hour in the whole exercise, and it is a report rather than a script. In the Microsoft 365 admin center open Reports, then Usage, then the Exchange report, then the Exchange Web Services usage tab. Set the range to ninety days and export to CSV.

ColumnWhat it gives you
Application IDThe Entra identifier of the calling application. This is the value the allow list consumes, and it is the reason to start here rather than in the sign-in logs
SOAP ActionThe specific operation called, which tells you how hard the migration will be. An application calling three actions is a different conversation from one calling thirty
Call VolumeCalls in the period. Useful for triage, misleading for risk: a quarterly job with a volume of four is more dangerous than a chatty one with four million, because you will not see it in a thirty-day window
Last Activity dateUTC. The column that tells you whether something is live or a fossil

Two constraints belong in the artefact rather than in your head. The data is collected and aggregated weekly rather than daily, so a seven-day window is close to a single sample. And the range tops out at ninety days, which means anything on a quarterly or annual cycle can be genuinely invisible to this report. Record the window you used next to the inventory, because the credibility of the whole document rests on it and a reader six months from now cannot reconstruct it.

You will not recognise every application ID, and that is expected rather than a failure of the report. Resolve them against Microsoft’s published list of first-party application IDs first, since Office and Power Query for Excel legitimately appear here, then against your own enterprise applications in Entra. An ID that resolves to nothing in either place is the most interesting row on the sheet and it goes to the top of the follow-up list, not the bottom.


Step 3: attribute what the report cannot, in the sign-in logs

The usage report tells you which application called and how often. It does not tell you which identity it called as, or from where. The sign-in logs do, and the same pass covers every protocol in this article at once, which is the mechanical reason the three retirements are one job.

In the Entra admin center open Entra ID, then Monitoring and health, then Sign-in logs. Add the Client app column if it is not shown, then filter on Client app and select the legacy authentication values. The ones that matter here are Exchange Web Services, Authenticated SMTP, IMAP4 and POP3. Take Other clients as well, because it collects anything the platform could not classify and an unclassified legacy sign-in is a finding by definition.

Client app valueWhat it means here
Exchange Web ServicesThe protocol retiring in October and April. Cross-reference against the application IDs from Step 2
Authenticated SMTPMicrosoft’s own description is that this is used by POP and IMAP clients to send. It is the submission half of the same device population
POP3 and IMAP4The retrieval half. A device appearing here almost always appears under Authenticated SMTP too
Other clientsSign-ins the platform could not attribute to a known client. Never dismiss this bucket; it is where the interesting devices hide

Run the same filter on the non-interactive sign-ins tab as well, and treat that as the important one rather than the afterthought. Service accounts, daemons and unattended integrations land there almost exclusively, so an interactive-only sweep returns a clean report on precisely the estate that has the biggest problem. If you would rather not drive the filters by hand, the Sign-ins using legacy authentication workbook under Monitoring and health does the same job over the same ninety-day ceiling.

What you are extracting from each row is the identity and the source address, because those are the two columns in your artefact that the usage report cannot fill. An application ID with no identity behind it is a migration task. An application ID with a shared mailbox and a warehouse IP behind it is a conversation with a named person, and that conversation is the actual deliverable.


Step 4: corroborate the submission traffic in message trace

Sign-in logs attribute the identity. Message trace attributes the traffic. Neither on its own produces a defensible row, because a sign-in tells you something authenticated and a trace tells you something was delivered, and the gap between those two is exactly where a device that authenticates nightly and has sent nothing for a year lives.

# The cmdlet is Get-MessageTraceV2. Run with no parameters it returns 48 HOURS,
# not the 90 days you may be expecting, and it does so without warning you.
Get-MessageTraceV2 -StartDate (Get-Date).AddDays(-10) -EndDate (Get-Date) -ResultSize 5000 |
  Where-Object { $_.FromIP } |
  Group-Object SenderAddress,FromIP |
  Sort-Object Count -Descending |
  Select-Object Count,Name

The ceilings matter more than the syntax. Retention is ninety days and is not configurable, but a single query returns at most ten days of that window, so covering the full ninety is nine or ten queries rather than one. The default result size is a thousand rows and the maximum is five thousand. There is no pagination: to walk past the end of a result set you re-query using the recipient address and received time of the last row you got. And the whole surface throttles at a hundred queries in any five-minute window, which is generous for a human and easy to breach with a loop.

Be honest in the artefact about what this evidence proves. Message trace shows sender, recipient, status and originating address. It does not record the authentication method, so it cannot on its own separate an authenticated submission from a connector relay or from anonymous Direct Send. Pair it with the sign-in log row or mark the row unproven.


Step 5: read the per-mailbox protocol posture

Everything so far is evidence of what happened. This is the configuration that permits it, and the two rarely agree. In Exchange Online use Get-EXOCASMailbox rather than Get-CASMailbox: Microsoft recommends it explicitly, and it is the one that behaves at estate scale.

# Every mailbox with a legacy retrieval protocol still permitted.
Get-EXOCASMailbox -ResultSize Unlimited -Properties PopEnabled,ImapEnabled,EwsEnabled,SmtpClientAuthenticationDisabled |
  Where-Object { $_.PopEnabled -or $_.ImapEnabled } |
  Select-Object PrimarySmtpAddress,PopEnabled,ImapEnabled,EwsEnabled,SmtpClientAuthenticationDisabled |
  Export-Csv .\protocol-posture.csv -NoTypeInformation

Read the SMTP column with care. A $null in SmtpClientAuthenticationDisabled is not a permission, it is an inheritance: the mailbox takes whatever the organisation setting from Step 1 says. So in a tenant where SMTP AUTH is still on organisation-wide, every mailbox that has never been touched reads as $null, a filter for explicit overrides returns nothing, and the empty result feels like a clean bill of health when it is the opposite. Explicit overrides are worth listing, but list them as exceptions to the organisation setting rather than as the population.

The same inheritance runs through Exchange Web Services, one level harder. The organisation-level setting overrides the per-mailbox ones outright, so a mailbox showing EwsEnabled as True means nothing at all once the organisation value is False. Build the inventory from the organisation value down, never from the mailbox value up. What governs new mailboxes rather than existing ones is a separate question about the mailbox plan applied at creation, and it is worth confirming in your own tenant rather than assuming, because the answer decides whether this work stays done.


Step 6: build the artefact

This is the deliverable. A sheet that produces a feeling of completeness instead of this table has not done the job, because the feeling does not survive the handover and the table does.

ColumnFilled fromWhy it is in the row
Application or deviceStep 2 application ID, resolved to a nameThe thing you will eventually switch off
OwnerA human, found by askingThe only column no tool can fill, and the only one that makes the retirement happen
ProtocolSteps 2, 3 and 5One device commonly holds two rows, submission and retrieval
Evidence sourceWhich step and which windowSo a reader can judge how much the row is worth without re-running the work
Source IPStep 3, corroborated in Step 4What lets you find the physical thing when nobody claims it
Sending or accessing identityStep 3Usually a shared mailbox or a service account, and usually the fastest route to the owner
Replacement pathGraph, a modern client, a vendor upgrade, or decommissionDecommission is a legitimate and frequently correct answer
Dated retirement commitmentA date somebody agreed toWithout a date this is a list, not a plan

Name the artefact so that a future reader can tell which window it covers without opening it: protocol-retirement-inventory-catsnackjack-2026-08-14.csv, with the tenant and the date the evidence was pulled rather than the date the file was last edited. Keep the exports that fed it alongside, under the same stem, so the row about the warehouse scanner can be traced back to the trace query that found it.

The worked row, so the shape is unambiguous. The signature manager resolves to an application ID that appeared in the usage report with eleven SOAP actions and a last activity date from yesterday; the sign-in logs show it authenticating as the svc-signatures service account from a single address in the server VLAN; message trace shows no submissions from that identity at all, because it reads and stamps rather than sends. Owner is the marketing operations lead. Protocol is Exchange Web Services only. Replacement path is the vendor’s Graph-based release, which exists and requires a licence change. Committed date is the end of September, one month before the phased disablement, and that gap is deliberate.


Step 7: build the allow list, and understand what writing it does

Now the two ends of the sheet meet. The application IDs in your inventory are the values the allow list takes, and the only editorial decision left is which of them you are prepared to keep alive through the retirement window.

# Writing the list. This REPLACES the entire value; it is not an append.
Set-OrganizationConfig -EwsAllowedAppIDs "11111111-2222-3333-4444-555555555555,aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"

# Read it back. Remember the switch, or the parameter is simply absent from the output.
Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy | Format-List EwsAllowedAppIDs

The trap in that first command is the one that will cost somebody an outage, and it is worth stating plainly rather than in a footnote. There is no incremental add and no incremental remove. Setting the parameter writes the full value, so an administrator who adds one application by running Set-OrganizationConfig with a single identifier has just deleted every other entry, silently, with a successful-looking result. Every change is a read, a recompute and a full write back.

# Add an application ID: read, combine, write the whole list back.
$current = (Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy |
            Select-Object -ExpandProperty EwsAllowedAppIDs)
$newAppId = "99999999-8888-7777-6666-555555555555"
$updated  = @($current, $newAppId)
Set-OrganizationConfig -EwsAllowedAppIDs ($updated -join ",")

# Remove one: same shape, because there is no single-item removal either.
$current    = (Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy |
               Select-Object -ExpandProperty EwsAllowedAppIDs)
$removeAppId = "99999999-8888-7777-6666-555555555555"
$appIds      = $current -split ","
$updated     = $appIds | Where-Object { $_ -ne $removeAppId }
Set-OrganizationConfig -EwsAllowedAppIDs ($updated -join ",")

Include the Microsoft first-party applications that showed up in your usage report. Office and Power Query for Excel appear there legitimately, and the list is an allow list rather than a third-party register: if it is in the report and you want it to keep working, it goes in the list. This is the step where an inherited list and a built list diverge most visibly, because Microsoft’s inferred version will contain whatever was running in its sample window, including things you were about to decommission and excluding anything that runs quarterly.

Then set the state, and set both parts of it. The August action is optional in Microsoft’s own words, and what it buys is not the survival of the protocol but the absence of an unplanned outage: a tenant that has configured a list and set the value to enabled before the end of August is excluded from the October change, and from the temporary shutdown tests Microsoft has reserved the right to run in the meantime.

Set-OrganizationConfig -EwsEnabled $true
ConfigurationTodayFrom October 2026
EwsEnabled NullAll Exchange Web Services traffic allowedStill allowed, until the phased rollout sets the value to False and everything stops
EwsEnabled True, list emptyAll traffic allowedAll traffic blocked. This is the reversal that catches people
EwsEnabled True, list populatedOnly listed applications allowedOnly listed applications allowed. The only configuration that means the same thing on both sides of the date
EwsEnabled FalseAll traffic blockedAll traffic blocked

Read the second row again, because it is the reason to do the list before the switch rather than after. Enabling the protocol without a populated list is harmless today and is a block-all configuration from October. An administrator who does half this work in August, meaning the enable but not the list, has built the exact configuration that fails.


Step 8: the validating test, written to survive the delay

Exchange Web Services access changes take up to a hundred and twenty minutes to take effect, and applications can keep receiving a forbidden response for a period after a change lands. That single fact invalidates the obvious test. If you change the configuration and immediately confirm that your allowed application still works, you have proven nothing whatsoever: it would have worked anyway, because the change had not reached anything yet.

So the test is built in two halves, and the durable half runs first.

# Half one, immediately after the write. This proves the CONFIGURATION,
# which is durable and available at once, and says nothing about enforcement.
$expected = @(
  "11111111-2222-3333-4444-555555555555",
  "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
)
$actual = ((Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy |
            Select-Object -ExpandProperty EwsAllowedAppIDs) -split ",").Trim()

$missing = $expected | Where-Object { $_ -notin $actual }
$extra   = $actual   | Where-Object { $_ -notin $expected -and $_ }

"EwsEnabled     : {0}" -f (Get-OrganizationConfig).EwsEnabled
"Allow list size: {0}" -f $actual.Count
"Missing        : {0}" -f ($(if ($missing) { $missing -join ", " } else { "none" }))
"Unexpected     : {0}" -f ($(if ($extra)   { $extra   -join ", " } else { "none" }))

Expected result is EwsEnabled reporting True, a list count matching your inventory, and nothing under missing or unexpected. The failure mode to watch for is a count of one after somebody ran a bare Set-OrganizationConfig, which is the read-modify-write trap from Step 7 announcing itself.

Half two proves enforcement, and it has to wait. Schedule it for the following morning rather than for two hours later, both because the propagation ceiling is a ceiling rather than a promise and because a test you have to stay late for is a test that gets skipped. Take one application that is deliberately not on your allow list, ideally something you have already decided to retire, and have its owner exercise it. The expected result is a clean failure with a forbidden response. If it still works the next day, your list is broader than you think or the change has not reached the tenant.

Testing that an allowed application still works is the weaker half and it is the one everybody does first. It can only ever tell you that nothing broke, which is also what an unapplied change looks like. Prove the block, not the pass.


The consolidated readback

One block to run at the end, and again in a month, because the value of this work is entirely in whether it stays true.

Connect-ExchangeOnline

Get-OrganizationConfig |
  Format-List Name,EwsEnabled,EwsApplicationAccessPolicy,EwsAllowList,EwsBlockList

Get-OrganizationConfig -RetrieveEwsOperationAccessPolicy |
  Format-List EwsAllowedAppIDs

Get-TransportConfig |
  Format-List SmtpClientAuthenticationDisabled,AllowLegacyTLSClients

Get-EXOCASMailbox -ResultSize Unlimited -Properties PopEnabled,ImapEnabled |
  Where-Object { $_.PopEnabled -or $_.ImapEnabled } |
  Measure-Object |
  Select-Object @{n="MailboxesWithPopOrImap";e={$_.Count}}

Completion checklist

  • The Exchange Web Services usage report has been pulled at ninety days and exported, and the window is recorded in the artefact
  • Every application ID in that export resolves to a named application, or is explicitly flagged as unresolved and owned by someone
  • The sign-in log sweep covered both the interactive and the non-interactive tabs, across Exchange Web Services, Authenticated SMTP, POP3, IMAP4 and Other clients
  • Every inventory row carries an owner, a replacement path and a date somebody agreed to
  • The allow list was written from the inventory rather than inherited, and includes the Microsoft first-party applications that appeared in the report
  • EwsEnabled reads True and the allow list is populated, both, before the end of August
  • The configuration half of the test passes, with nothing missing and nothing unexpected
  • The enforcement half of the test ran on a later day and a deliberately excluded application failed as intended
  • Anyone who might edit the allow list knows that writing it replaces it

What you have at the end of this is not a tidy tenant. It is a document with names and dates in it, which is the thing that survives the person who made it and the only version of this work that is worth anything in October. The retirements article carries the rest of the calendar, and the quick checklist carries the one-line version of each control for an assessment.


Best Practices
‹ Previous: [BP 2.3] Exchange Online on a Clock: Retirements and One-Way Doors
Next: [BP 2.4] The Exchange Online Quick Checklist