Checking and enabling Exchange Online archives from a ticket

"My mailbox is full" used to mean signing in to the admin center for that client, finding the user, checking whether they had an archive, and maybe turning it on. Now the engineer runs one package from the ticket chat, and the answer lands on the ticket.

What it does

Calling Exchange Online without the module

The package doesn't load the ExchangeOnlineManagement module. It uses the same pattern as Pia's built-in Exchange activities: post the cmdlet as JSON to the Exchange admin API, with a client-scoped token.

POST https://outlook.office365.com/adminapi/beta/{tenant}/InvokeCommand
Authorization: Bearer {token}
X-AnchorMailbox: UPN:SystemMailbox{bb558c35-97f1-4cb9-8ff7-d53741dc928c}@{tenant}

{"CmdletInput":{"CmdletName":"Get-Mailbox","Parameters":{"ResultSize":"Unlimited"}}}

One helper does every call. It follows @odata.nextLink for paging and retries up to three times, but only for network hiccups ("timed out" or "unable to connect"). Anything else is a real failure, and it gets thrown so the engineer sees it.

The {tenant} part comes from Graph: the organization's initial .onmicrosoft.com domain. If that lookup fails, the admin API accepts the tenant GUID too, so that's the fallback.

The bug: every archive showed as off

The first version reported no archive for mailboxes that clearly had one. Two things were wrong.

Get-Mailbox -Identity comes back trimmed. It leaves out the Archive* fields and RetentionPolicy, even when you ask for them by name. The list-style query I used for the dropdown returns them fine. So the check uses that same query, filtered down to the one user:

$safeUpn = $upn.Replace("'", "''")
$mbx = @(Invoke-ExoCommand 'Get-Mailbox' @{
    RecipientTypeDetails = @('UserMailbox', 'SharedMailbox')
    ResultSize           = 'Unlimited'
    Filter               = "UserPrincipalName -eq '$safeUpn'"
} $mbxProps)[0]

The UPN sits inside single quotes in the filter, so any single quote in it gets doubled first. If the filter somehow comes back empty, it falls back to -Identity.

The helper returned an array inside an array. It ended with return ,$data, the usual PowerShell trick to stop an array from being unrolled. Callers already wrapped the result in @(...), so [0] handed back the whole list instead of the first mailbox. A plain return $data fixed it.

Is the archive actually on?

No single field is reliable. An all-zero ArchiveGuid means no archive, but ArchiveStatus can sit at None for a while after you enable one. So the check reads every signal Get-Mailbox gives:

Then it asks the archive itself. If Get-MailboxStatistics -Archive returns stats flagged IsArchiveMailbox, the archive exists. After enabling one, the package confirms with ArchiveGuid rather than ArchiveStatus, for the same reason.

Smaller things that cost time

The takeaway

Don't trust the first object an API hands you to be the whole object. When one field says "off," check a second source before you tell anyone it's off.

All writing