A practitioner’s guide to migrating Organization Relationships, Availability Address Spaces and Sharing Policies to Microsoft 365 Cross-Tenant Access Policy
Microsoft has quietly moved one of the least glamorous but most load-bearing features in Exchange Online. Cross-tenant Free/Busy, MailTips and Calendar Sharing, the plumbing that lets your users see a partner organisation’s availability in Scheduling Assistant no longer has a future on Exchange Web Services. It is moving to Microsoft 365 Cross-Tenant Access Policy, a capability layered on top of Entra Cross-Tenant Access Policy. The official guidance tells you which cmdlets to run. This post is about everything that sits between those cmdlets and a successful cutover: how to work out whether you are actually in scope, how to find your partners’ tenant IDs when nobody has them, where the mapping from old model to new is lossy, which parts of the documented rollback are broken, and how to sequence this across a fleet of managed tenants without breaking a CEO’s calendar on a Monday morning.
The 60-second version
1. Run three read-only cmdlets. If all three come back empty, you are done, close the tab.
2. If they return anything, the partner organisations they point at need to be identified by Entra tenant ID, not domain name.
3. Microsoft 365 Cross-Tenant Access Policy is inbound-only. Both sides must configure it or sharing stays one-way.
4. Legacy configuration takes precedence over the new policy, so you cannot run both and compare. Validation requires disabling the old config first. 5. The EWSAllowedAppIDs allow list does not save you here. It is a different control surface.
What is actually changing
Three legacy Exchange Online constructs are in scope. All three predate modern authentication, all three ride on EWS, and all three depend on the kind of broad service-to-service access that Microsoft is systematically removing across Microsoft 365.
| Legacy construct | What it does today | Why it is going away |
| Organization Relationship | Shares Free/Busy and MailTips with a named external organisation, with access level and optional distribution-group scope. | Uses EWS plus an organisation-wide token to fetch availability from the partner tenant. |
| Availability Address Space | Shares Free/Busy with an external organisation. Only AccessMethod OrgWideFBToken is relevant for tenant-to-tenant. | Same EWS availability path; the org-wide token is exactly the high-privilege pattern being retired. |
| Sharing Policy | Controls how your users share their own calendars externally, per-domain or via a wildcard, plus anonymous publishing. | Cross-tenant calendar sharing is serviced through EWS on the receiving side. |
The replacement is not a like-for-like Exchange object. It is a set of capabilities attached to an Entra cross-tenant access partner configuration. That layering matters, because it means this migration is no longer purely an Exchange task, it lands squarely in identity territory, and in most organisations that is a different team with a different change process.
The layer cake
- Layer 1 – Entra Cross-Tenant Access Policy. The partner configuration keyed on the external Entra tenant ID. This is the same object that already carries your B2B inbound/outbound settings and trust settings for MFA and device compliance.
- Layer 2 – Microsoft 365 Collaboration trust. A new inbound trust level (m365CollaborationInbound) enabled per partner. Without this, the capabilities below do nothing.
- Layer 3 – Microsoft 365 capabilities. The granular grants: Free/Busy basic or limited detail, MailTips limited or all, calendar sharing simple / detail / reviewer, plus anonymous variants.
Naming trap
Microsoft 365 Cross-Tenant Access Policy and Entra Cross-Tenant Access Policy are not synonyms, and both get shortened to XTAP in conversation. The Microsoft 365 layer sits on top of the Entra one. If you tell your identity team “we need a cross-tenant access policy”, they will reasonably assume you mean B2B trust settings and hand you something that does not carry Free/Busy.
The timeline, and the escape hatch that is not one
| When | What happens |
| Aug – Sep 2026 | Microsoft 365 Cross-Tenant Access Policy capabilities roll out. Worldwide from August, targeting completion by 1 September 2026. GCC mid-August to mid-September. DoD early September to end of September. Until the rollout reaches your tenant, the Graph beta calls will not work. |
| 1 Oct 2026 | Phased EWS disablement begins in Exchange Online. Tenants with EwsEnabled still null get flipped to false as the deployment reaches them. |
| 1 Apr 2027 | Full and permanent EWS shutdown in Exchange Online. No exceptions, no re-enablement. |
Everyone who has been tracking EWS retirement knows about EWSAllowedAppIDs, the allow list that lets you keep specific application IDs working past the October disablement while you finish migrating third-party integrations. It is a genuinely useful control, and it has nothing to do with this.
EWSAllowedAppIDs does not cover Organization Relationships
Microsoft updated the EWSAllowedAppIDs post on 6–7 August 2026 specifically to say so: organisation relationship EWS traffic is not related to, and is not preserved by, the state of the allow list. There is no app ID to add, because the traffic is service-to-service inside the substrate rather than an app calling EWS on a user’s behalf. Practical consequence: for cross-tenant Free/Busy there is no deferral lever. The migration is the mitigation.
What is explicitly out of scope
- Free/Busy, calendars and MailTips shared inside your own organisation. Nothing changes.
- Exchange hybrid – sharing between your on-premises and online users. That is governed by the Dedicated Hybrid App work, which is a separate migration with its own deadlines.
- Sharing with an organisation running on-premises Exchange. No immediate impact; Microsoft has signalled future Message Center posts for this scenario.
- Exchange Server on-premises generally. EWS retirement applies to Exchange Online only.
This trips people up in hybrid tenants
A typical long-lived hybrid tenant has Organization Relationships named something like “O365 to On-premises” sitting alongside genuine partner relationships. Those hybrid relationships are not part of this migration and must not be swept up in your cleanup. Filter on them explicitly before you disable anything, and confirm the TargetAutodiscoverEpr or the domain list does not point at your own on-premises namespace.
Are you actually in scope? A real inventory
MC1446796 went to every tenant. Most recipients need to do nothing. Three read-only cmdlets settle it but the raw output is not a migration plan, so the script below turns it into one.
THE THREE-COMMAND TRIAGE
Get-OrganizationRelationship | Format-List Name, DomainNames, Enabled, `
FreeBusyAccessEnabled, FreeBusyAccessLevel, FreeBusyAccessScope, `
MailTipsAccessEnabled, MailTipsAccessLevel, MailTipsAccessScope
Get-AvailabilityAddressSpace | Format-List ForestName, AccessMethod
Get-SharingPolicy | Format-List Name, Enabled, Domains, Default
If all three return nothing, stop. If any of them return something, keep reading but note that a returned object is not automatically in scope. An Organization Relationship with Enabled set to false is inert regardless of its Free/Busy settings. An Availability Address Space with an AccessMethod other than OrgWideFBToken cannot be migrated to the new model at all. A Sharing Policy that is enabled but assigned to zero mailboxes is doing nothing for anybody.
Inventory script: produce a migration worksheet, not a screen dump
This is the version I run. It classifies each object, flags the ones that need human decisions, resolves partner tenant IDs, and writes a CSV you can put in front of a change board.
GET-XTAPMIGRATIONINVENTORY.PS1
#requires -Version 5.1
<#
Get-XtapMigrationInventory.ps1
Read-only. Produces a per-object migration worksheet for the
Organization Relationship / Availability Address Space / Sharing Policy
to Microsoft 365 Cross-Tenant Access Policy migration.
#>
[CmdletBinding()]
param(
[string]$OutputPath = ".\XTAP-Inventory-$(Get-Date -f yyyyMMdd-HHmm).csv"
)
# --- Resolve an SMTP domain to its Entra tenant ID (unauthenticated, public metadata)
function Resolve-TenantId {
param([Parameter(Mandatory)][string]$Domain)
$uri = "https://login.microsoftonline.com/$Domain/v2.0/.well-known/openid-configuration"
try {
$meta = Invoke-RestMethod -Uri $uri -Method Get -ErrorAction Stop
return ($meta.issuer -split "/")[3]
}
catch { return $null }
}
$rows = [System.Collections.Generic.List[object]]::new()
$acceptedDomains = (Get-AcceptedDomain).DomainName
# --- 1. Organization Relationships -------------------------------------------
foreach ($or in Get-OrganizationRelationship) {
$isHybridish = $or.DomainNames | Where-Object { $acceptedDomains -contains $_ }
foreach ($d in $or.DomainNames) {
$tid = Resolve-TenantId -Domain $d
$notes = @()
if (-not $or.Enabled) { $notes += "DISABLED - no action" }
if ($isHybridish) { $notes += "Domain is an accepted domain of THIS tenant - likely hybrid, out of scope" }
if (-not $tid) { $notes += "Tenant ID not resolvable - confirm partner is in Microsoft 365" }
if ($or.FreeBusyAccessScope) { $notes += "Scoped to group: $($or.FreeBusyAccessScope) - needs a security group in XTAP" }
if ($or.MailTipsAccessScope) { $notes += "MailTips scoped to group: $($or.MailTipsAccessScope)" }
$fbCap = switch ($or.FreeBusyAccessLevel) {
"AvailabilityOnly" { "crossTenantCalendarAvailabilityBasic" }
"LimitedDetails" { "crossTenantCalendarAvailabilityLimitedDetails" }
default { $null }
}
$mtCap = switch ($or.MailTipsAccessLevel) {
"Limited" { "crossTenantMailTipsLimited" }
"All" { "crossTenantMailTipsAll" }
default { $null }
}
$rows.Add([pscustomobject]@{
Type = "OrganizationRelationship"
Name = $or.Name
Domain = $d
PartnerTenantId = $tid
Enabled = $or.Enabled
FreeBusy = "$($or.FreeBusyAccessEnabled) / $($or.FreeBusyAccessLevel)"
MailTips = "$($or.MailTipsAccessEnabled) / $($or.MailTipsAccessLevel)"
TargetCapability= (@($fbCap, $mtCap) | Where-Object { $_ }) -join "; "
Notes = $notes -join " | "
})
}
}
# --- 2. Availability Address Spaces ------------------------------------------
foreach ($aas in Get-AvailabilityAddressSpace) {
$tid = Resolve-TenantId -Domain $aas.ForestName
$notes = @()
if ($aas.AccessMethod -ne "OrgWideFBToken") {
$notes += "AccessMethod is $($aas.AccessMethod) - NOT migratable to XTAP"
}
if (-not $tid) { $notes += "Tenant ID not resolvable" }
$rows.Add([pscustomobject]@{
Type = "AvailabilityAddressSpace"
Name = $aas.ForestName
Domain = $aas.ForestName
PartnerTenantId = $tid
Enabled = $true
FreeBusy = $aas.AccessMethod
MailTips = ""
TargetCapability= if ($aas.AccessMethod -eq "OrgWideFBToken") { "crossTenantCalendarAvailabilityBasic" } else { "" }
Notes = $notes -join " | "
})
}
# --- 3. Sharing Policies ------------------------------------------------------
$assignment = Get-Mailbox -ResultSize Unlimited |
Group-Object SharingPolicy |
Select-Object Name, Count
foreach ($sp in Get-SharingPolicy) {
$assigned = ($assignment | Where-Object { $_.Name -like "*$($sp.Name)*" }).Count
if (-not $assigned) { $assigned = 0 }
foreach ($entry in $sp.Domains) {
# entries look like contoso.com:CalendarSharingFreeBusyDetail
$parts = "$entry" -split ":"
$dom = $parts[0]
$level = $parts[1]
$cap = switch ($level) {
"CalendarSharingFreeBusySimple" { "crossTenantCalendarSharingFreeBusySimple" }
"CalendarSharingFreeBusyDetail" { "crossTenantCalendarSharingFreeBusyDetail" }
"CalendarSharingFreeBusyReviewer" { "crossTenantCalendarSharingFreeBusyReviewer" }
default { $null }
}
$notes = @()
$tid = $null
switch -Wildcard ($dom) {
"Anonymous" {
$notes += "ANONYMOUS publishing - maps to the DEFAULT policy only, cannot be per-partner"
if ($cap) { $cap = $cap -replace "^crossTenant", "Anonymous" }
}
"*" {
if ($dom -eq "*") {
$notes += "WILDCARD - maps to the DEFAULT policy and applies to EVERY external tenant. Review before migrating."
} else {
$tid = Resolve-TenantId -Domain $dom
if (-not $tid) { $notes += "Tenant ID not resolvable" }
}
}
}
if (-not $sp.Enabled) { $notes += "DISABLED - no action" }
if ($assigned -eq 0) { $notes += "Assigned to 0 mailboxes by explicit assignment" }
if ($sp.Default) { $notes += "DEFAULT policy - applies to all unassigned mailboxes" }
$rows.Add([pscustomobject]@{
Type = "SharingPolicy"
Name = $sp.Name
Domain = $dom
PartnerTenantId = $tid
Enabled = $sp.Enabled
FreeBusy = $level
MailTips = ""
TargetCapability= $cap
Notes = $notes -join " | "
})
}
}
$rows | Sort-Object Type, Name | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Write-Host "Wrote $($rows.Count) rows to $OutputPath" -ForegroundColor Green
$rows | Where-Object { $_.Notes } | Format-Table Type, Name, Domain, Notes -AutoSize -Wrap
Two things this catches that a manual review usually misses. First, an Organization Relationship whose DomainNames include one of your own accepted domains, almost always a hybrid artefact, and almost always something you should leave alone. Second, a FreeBusyAccessScope pointing at a group that no longer exists; Exchange throws a warning on Get-OrganizationRelationship when that happens and it is easy to scroll past. Both of those are cleanup opportunities before you migrate anything.
The hard part: getting partner tenant IDs
Every legacy construct identifies the far end by SMTP domain. Microsoft 365 Cross-Tenant Access Policy identifies it by Entra tenant GUID. Microsoft’s guidance says, in effect, “ask an administrator in the external organisation for their tenant ID.” For a two-partner tenant that is a phone call. For an organisation with fourteen Organization Relationships accumulated over a decade, half of which point at companies that have since been acquired, it is a project.
You do not have to ask. Entra publishes the mapping as unauthenticated OpenID Connect metadata.
DOMAIN → TENANT ID, NO PARTNER CONTACT REQUIRED
function Resolve-TenantId {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$Domain
)
process {
$uri = "https://login.microsoftonline.com/$Domain/v2.0/.well-known/openid-configuration"
try {
$meta = Invoke-RestMethod -Uri $uri -Method Get -ErrorAction Stop
[pscustomobject]@{
Domain = $Domain
TenantId = ($meta.issuer -split "/")[3]
Issuer = $meta.issuer
}
}
catch {
[pscustomobject]@{ Domain = $Domain; TenantId = $null; Issuer = $null }
}
}
}
# Usage
(Get-OrganizationRelationship).DomainNames | Sort-Object -Unique | Resolve-TenantId
The issuer claim comes back as https://login.microsoftonline.com/{tenantId}/v2.0, so index 3 of the split is the GUID. It works for any domain registered in any Entra tenant, worldwide cloud. Three caveats worth stating in your runbook:
- A resolvable tenant ID proves the domain exists in Entra. It does not prove the organisation has Exchange Online mailboxes, and it does not prove they are the party you think they are.
- Sovereign and government clouds use different endpoints. If you serve GCC High or DoD tenants, point at the correct authority host.
- A domain that fails to resolve is usually a genuine finding: an on-premises-only partner, a decommissioned relationship, or a typo that has been sitting in your config for years.
Still confirm with the partner
Resolving the GUID saves you the discovery cycle, not the coordination cycle. Because the new model is inbound-only, you still need an administrator on the other side to create the mirror-image policy and you want them to confirm the GUID you resolved is theirs before either side writes anything. Treat the script as the thing that lets you open the conversation with a concrete ask instead of a vague one.
The mental model shift: outbound became inbound
An Organization Relationship reads as outbound. You create a relationship to contoso.com, you set FreeBusyAccessLevel, and your users start seeing their availability. It feels like you are configuring what you can retrieve.
Microsoft 365 Cross-Tenant Access Policy inverts that. Every capability is an inbound grant: you are declaring what an external tenant may retrieve from you. If you configure your side and your partner does not configure theirs, your users will see nothing while their users see everything. That is a support ticket waiting to happen, and it is the single most common way this migration goes sideways.
Bidirectional sharing requires two policies in two tenants
There is no equivalent of the old “both sides created a relationship and it just worked” symmetry to fall back on. Build the partner coordination into your project plan as a hard dependency, with a named contact and an agreed cutover window per partner. For managed-service work, that means a per-tenant partner matrix before you touch anything.
The mapping, consolidated
Microsoft splits this across three tables in the migration guide. Here is the whole thing in one place.
| Legacy setting | Microsoft 365 XTAP capability | Effective access |
| OrgRel FreeBusyAccessLevel = AvailabilityOnly, or any migratable Availability Address Space | crossTenantCalendarAvailabilityBasic | Free/Busy, time only |
| OrgRel FreeBusyAccessLevel = LimitedDetails | crossTenantCalendarAvailabilityLimitedDetails | Free/Busy with time, subject, location |
| OrgRel MailTipsAccessLevel = Limited | crossTenantMailTipsLimited | Restricted subset of recipient info |
| OrgRel MailTipsAccessLevel = All | crossTenantMailTipsAll | Out-of-office, auto-replies, all recipient info |
| Sharing Policy CalendarSharingFreeBusySimple | crossTenantCalendarSharingFreeBusySimple | Calendar sharing, time only |
| Sharing Policy CalendarSharingFreeBusyDetail | crossTenantCalendarSharingFreeBusyDetail | Time, subject, location |
| Sharing Policy CalendarSharingFreeBusyReviewer | crossTenantCalendarSharingFreeBusyReviewer | Full calendar detail |
| Anonymous entry in a Sharing Policy | AnonymousCalendarFreeBusySimple / AnonymousCalendarSharingFreeBusyDetail / AnonymousCalendarSharingFreeBusyReviewer | Anonymous calendar publishing; default policy only |
Note the asymmetry in the anonymous capability names
The time-only anonymous capability is AnonymousCalendarFreeBusySimple, no “Sharing” in the middle, while the detail and reviewer variants are AnonymousCalendarSharingFreeBusyDetail and AnonymousCalendarSharingFreeBusyReviewer. That is how they appear in Microsoft’s guidance, and it is exactly the kind of inconsistency that produces a 400 from Graph and half an hour of squinting. Copy, do not type.
Where the mapping is lossy
The capability table implies a clean one-to-one translation. In practice there are four places where the new model cannot express what the old one did, and you need a decision for each before you start writing policy.
Distribution groups become security groups
FreeBusyAccessScope and MailTipsAccessScope accept a distribution group. The XTAP resource scope takes a group object ID and Microsoft’s guidance calls for a security group. If your scoping today is a mail-enabled distribution group and after fifteen years of Exchange, it usually is you need a security group with equivalent membership, plus a plan to keep the two in sync during the coexistence window or a plan to retire the DG entirely.
- Nested distribution groups are the sharp edge. Confirm how membership resolves before you assume a flat copy is equivalent.
- Dynamic distribution groups have no direct equivalent. A dynamic Entra security group with a matching rule is the closest analogue, and “closest analogue” is doing a lot of work in that sentence.
Per-mailbox Sharing Policy assignment disappears
Sharing Policies are assigned to mailboxes. You can have a default policy for everyone and a High-Detail policy assigned to the executive assistants. Microsoft 365 Cross-Tenant Access Policy has no per-mailbox assignment, the only scoping dimension is a security group in the resource scope. Microsoft’s own guidance is blunt about this: if you have multiple Sharing Policies assigned to different mailboxes, you must use security groups to build comparable policies.
Translation: enumerate your assignments first, because that grouping becomes your new access model.
SHARING POLICY ASSIGNMENT CENSUS
# Who has what, before you lose the ability to answer this cleanly
Get-Mailbox -ResultSize Unlimited |
Group-Object SharingPolicy |
Select-Object @{n="Policy";e={ if ($_.Name) { $_.Name } else { "<default>" } }}, Count |
Sort-Object Count -Descending
The wildcard is not a like-for-like migration
A Default Sharing Policy typically contains a wildcard entry, *:CalendarSharingFreeBusySimple or similar, meaning “any external domain, at this level”. In the new model, a wildcard becomes a capability on the default Microsoft 365 Cross-Tenant Access Policy, applied through New-MgBetaPolicyCrossTenantAccessPolicyDefaultM365Capability.
Read this as a security change, not a plumbing change
The default policy is your posture toward every Microsoft 365 tenant on the planet, expressed in one object. Migrating a wildcard Sharing Policy verbatim is a decision to grant that level of calendar access broadly, which is what you already have today, but under a control surface that identity and security teams actually look at. This is the moment to ask whether the wildcard was ever a deliberate choice or just the shipping default that nobody revisited. Migrating to per-partner capabilities and leaving the default empty is usually the better end state, and this migration is the cheapest opportunity you will get to make that change.
Anonymous is default-only
Anonymous calendar publishing, the published URL your users generate from Outlook, maps only to the default policy. There is no per-partner anonymous capability, which is logically consistent (an anonymous recipient has no tenant) but does mean your only lever is the optional security group limiting which users may publish anonymously. If you have never restricted anonymous publishing, this is a second good moment to decide whether you want to.
Building the policies
Prerequisites, stated plainly: Global Administrator in your own tenant, the Microsoft Graph PowerShell SDK beta module, the partner’s tenant ID, and the rollout having actually reached your tenant. That last one is not a formality, the capability cmdlets will fail on a tenant the rollout has not landed on, and the failure is not especially self-describing.
Global Administrator is a real constraint
Microsoft’s guidance calls for Global Administrator for this work. In an organisation with a mature privileged-access model, that means a PIM activation with justification and an approval, scheduled per partner. Build the activation windows into the plan rather than discovering the approval SLA on cutover night.
Connect and enable the Microsoft 365 Collaboration trust
ENABLE M365 COLLABORATION TRUST FOR A PARTNER
Connect-MgGraph -Scopes "Policy.Read.All,Policy.ReadWrite.CrossTenantAccess,Policy.ReadWrite.CrossTenantCapability" -ContextScope Process
$partnerId = "<partnerTenantId>"
$body = @{
tenantId = $partnerId
m365CollaborationInbound = @{
users = @{
accessType = "allowed"
targets = @(
@{ target = "AllUsers"; targetType = "user" }
)
}
}
}
New-MgBetaPolicyCrossTenantAccessPolicyPartner -BodyParameter $body
This will fail if you already have a partner configuration
New-MgBetaPolicyCrossTenantAccessPolicyPartner creates a partner configuration. If the partner already exists and in any organisation already using B2B trust settings, cross-tenant sync, or inbound MFA trust, it very likely does the create call conflicts. Check first and update instead of creating. This is not an edge case. It is the default state for most enterprises with an established B2B posture, and it is the number one reason the documented happy path does not run cleanly on a real tenant.
IDEMPOTENT VERSION
$existing = Get-MgBetaPolicyCrossTenantAccessPolicyPartner `
-CrossTenantAccessPolicyConfigurationPartnerTenantId $partnerId `
-ErrorAction SilentlyContinue
$trust = @{
m365CollaborationInbound = @{
users = @{
accessType = "allowed"
targets = @( @{ target = "AllUsers"; targetType = "user" } )
}
}
}
if ($existing) {
Write-Host "Partner config exists for $partnerId - updating trust only" -ForegroundColor Yellow
Update-MgBetaPolicyCrossTenantAccessPolicyPartner `
-CrossTenantAccessPolicyConfigurationPartnerTenantId $partnerId `
-BodyParameter $trust
}
else {
New-MgBetaPolicyCrossTenantAccessPolicyPartner `
-BodyParameter ($trust + @{ tenantId = $partnerId })
}
Note that enabling the collaboration trust for AllUsers is deliberately broad. Narrower targeting at the trust level is supported but Microsoft does not cover it in the migration guide; the intended pattern is to leave the trust wide and control effective access with the resource scopes on each capability.
Add capabilities
Every capability uses the same body shape. The only things that change are the OData type and the resource scope.
PER-PARTNER CAPABILITY
$partnerId = "<partnerTenantId>"
$capability = "crossTenantCalendarAvailabilityLimitedDetails" # from the mapping table
# Scope: either a specific security group, or everyone
$group = @{ resourceId = "<groupObjectId>"; resourceType = "group" }
# $group = @{ resourceId = "All"; resourceType = "user" }
$body = @{
"@odata.type" = "microsoft.graph.$capability"
inboundAccess = @{
isAllowed = $true
resourceScopes = @{
included = @( $group )
excluded = @( @{ } )
}
}
}
New-MgBetaPolicyCrossTenantAccessPolicyPartnerM365Capability `
-CrossTenantAccessPolicyConfigurationPartnerTenantId $partnerId `
-BodyParameter $body
For wildcard and anonymous capabilities, swap the cmdlet for the default-policy variant and drop the partner tenant ID:
DEFAULT (WILDCARD / ANONYMOUS) CAPABILITY
New-MgBetaPolicyCrossTenantAccessPolicyDefaultM365Capability -BodyParameter $body
That empty hashtable in “excluded” is not a typo
excluded = @( @{ } ) appears verbatim in Microsoft’s guidance. It reads like a placeholder that escaped review, and it is the sort of thing that gets “cleaned up” by the next person to touch the script. Leave it. If you drop the excluded key entirely, or pass an empty array, behaviour is untested and undocumented and this is a beta surface, so it is not the place to improvise.
Cutover: why you cannot A/B this
Legacy configuration takes precedence over Microsoft 365 Cross-Tenant Access Policy. That single sentence determines the entire shape of your change window. You cannot build the new policy alongside the old one, validate it, and then decommission at leisure. The new policy is inert until the old one is switched off, so validation happens after you have already removed the safety net.
Practically, that means a change window per partner with:
- New policy in place on both sides, verified via Get-MgBetaPolicyCrossTenantAccessPolicyPartnerM365Capability before the window opens.
- A named tester in each tenant, on a call, with Scheduling Assistant open.
- Legacy config disabled.
- Validation. Allow for propagation, availability caching means “it does not work” in the first few minutes is not a reliable signal.
- Either proceed to cleanup, or roll back inside the window.
Disabling the legacy configuration
TAKE THE OLD CONFIG OUT OF THE PATH
# Organization Relationships - reversible
Set-OrganizationRelationship -Identity "<RelationshipName>" -Enabled $false
# Sharing Policies - reversible
Set-SharingPolicy -Identity "<PolicyName>" -Enabled $false
# Availability Address Spaces - NOT reversible in place. Back up, then remove.
Get-AvailabilityAddressSpace | Export-Clixml .\AvailabilityAddressSpaceBackup.xml
Get-AvailabilityAddressSpace | ForEach-Object {
Remove-AvailabilityAddressSpace -Identity $_.Identity -Confirm:$false
}
Availability Address Spaces have no Enabled property. Your only option is delete and recreate, which makes the backup file the rollback plan. Take it seriously and test the restore in a lab before you need it, because the restore snippet in Microsoft’s guidance does not do what it looks like it does.
The documented restore is broken – here is a corrected version
In Microsoft’s migration guide, the Add-AvailabilityAddressSpace call sits inside the inner foreach loop that walks the optional property names, rather than after it. As written it will attempt to add each address space up to seven times, with a progressively larger parameter set on each pass. The first call will typically succeed and the remaining six will error, so you may not notice, until you are restoring several address spaces at 11pm and the console is a wall of red.
RESTORE-AVAILABILITYADDRESSSPACE, FIXED
# Corrected restore: build the splat, THEN add once per address space.
$optional = @(
"Credentials", "DomainController", "ProxyUrl",
"TargetAutodiscoverEpr", "TargetServiceEpr",
"TargetTenantId", "UseServiceAccount"
)
Import-Clixml .\AvailabilityAddressSpaceBackup.xml | ForEach-Object {
$aas = $_
$p = @{
ForestName = $aas.ForestName
AccessMethod = $aas.AccessMethod
}
foreach ($n in $optional) {
if ($null -ne $aas.$n) { $p[$n] = $aas.$n }
}
try {
Add-AvailabilityAddressSpace @p -ErrorAction Stop
Write-Host "Restored $($aas.ForestName)" -ForegroundColor Green
}
catch {
Write-Warning "FAILED $($aas.ForestName): $($_.Exception.Message)"
}
}
Credentials do not survive Export-Clixml
If an Availability Address Space was created with a credential, the PSCredential in the export is serialised as a secure string encrypted under the DPAPI context of the account and machine that ran the export. Restore it from a different machine or a different account and it will not decrypt. For the OrgWideFBToken address spaces that matter to this migration there is no credential, but check your backup before you rely on it.
Validation
Free/Busy failures are notoriously ambiguous hashed availability, cached results, and a Scheduling Assistant that renders “no information” identically whether the cause is policy, permission or propagation. Test deliberately.
| What to test | How | What a failure tells you |
| Free/Busy, basic | Partner user opens Scheduling Assistant and adds one of your users. | Trust level or availability capability missing on your side. |
| Free/Busy, detail level | Same, but confirm subject and location render. | You migrated LimitedDetails to the Basic capability. |
| MailTips | Partner user addresses a message to a user of yours who has an out-of-office set. | MailTips capability missing, or you mapped Limited where All was configured. |
| Calendar sharing invitation | Your user shares a calendar to a partner recipient from Outlook on the web. | Sharing capability or resource scope wrong; check group membership. |
| Anonymous publishing | Publish a calendar and open the URL in a private window. | Anonymous capability missing from the default policy. |
| Scoped access | Test with a user inside the scope group and one outside it. | If both work, your resource scope is not applying. |
- Test in both directions explicitly. Inbound-only semantics mean one direction working proves nothing about the other.
- Test at least one room or resource mailbox. Room booking across tenants is the scenario users notice fastest and the one that generates the loudest tickets.
- Give propagation a fair window before declaring failure, and re-test rather than immediately rolling back.
Cleanup
Only after both sides have validated. Removal is irreversible in the sense that you will be rebuilding from your inventory CSV, not from the object.
CLEANUP
Remove-OrganizationRelationship -Identity "<RelationshipName>"
Get-AvailabilityAddressSpace | ForEach-Object {
Remove-AvailabilityAddressSpace -Identity $_.Identity -Confirm:$false
}
Remove-SharingPolicy -Identity "<PolicyName>"
Two things to keep
Do not remove hybrid Organization Relationships. They are out of scope for this migration and removing one will break Free/Busy between your on-premises and online users immediately.
Do not remove the Default Sharing Policy object itself. Remove-SharingPolicy on the default policy is not the intent of the guidance, if you want to stop wildcard external sharing, empty or narrow the domain entries and let the new default XTAP capability carry the policy.
The short version, as a task list
| When | Do this | Output |
| This week | Run the three triage cmdlets and the inventory script across every tenant you manage. | A CSV per tenant, and a list of tenants with nothing to do. |
| This week | Resolve partner tenant IDs from domains. Flag anything unresolvable. | Partner matrix with GUIDs. |
| Before Sep | Contact each partner. Confirm GUID, confirm they are migrating, agree a window. | Named contact per partner. |
| Sep 2026 | Confirm the capability rollout has reached each tenant. Pilot on a pair you control. | A validated runbook including rollback. |
| Sep – Dec 2026 | Migrate partner by partner. Validate before cleanup. | New policies live, legacy config disabled. |
| Before Apr 2027 | Clean up. Remove disabled legacy objects, retire the interim security groups you no longer need. | Nothing left on EWS. |
Sources
Exchange Online EWS, Your Time is Almost Up — Exchange Team Blog
Introducing EWSAllowedAppIDs: Preparing for the Final Phase of EWS Retirement — Exchange Team Blog
Deprecation of Exchange Web Services in Exchange Online — Microsoft Learn
Overview: Cross-tenant access with Microsoft Entra External ID — Microsoft Learn