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
- Builds a dropdown of every user and shared mailbox in the client's tenant, with
Archive: OnorArchive: Offnext to each name. - Reports primary storage against quota, item count, and recoverable items.
- Reports the archive's status, size, and whether auto-expanding archiving is on.
- If the archive is off, asks yes or no. Anything other than a clear "yes" counts as no.
- Posts the whole report to the ticket as an internal note, with the engineer's name on it.
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:
ArchiveGuidis not all zerosArchiveStatusisActiveArchiveStateis something other thanNoneArchiveDatabaseis set
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
- Exchange reports sizes as text, like
1.23 GB (1,320,702,443 bytes). The package pulls out the byte count in parentheses and does the math itself, so the report can say12.40 GB of 50.00 GB (24.8% used). - The dropdown comes in through
form_def. The key just has to match the Tags field's reference name on the form. No other setup. - A Tags field can hand back a plain value, a one-item list, or a
{text, value}object. The package unwraps whichever it gets down to the UPN. Sort-Objectcollapses arrays in Pia. Piping throughForEach-Object { $_ }first keeps the list a list.- ConnectWise notes don't render HTML, so the ticket copy strips the
<b>tags the chat version uses. - When enabling fails, the most common cause is a license that doesn't include Exchange Online Archiving. The package says so in the failure message.
- When it works, the archive can take a while to show up in Outlook, and the retention policy decides when mail moves. The default MRM policy moves items after two years. The package tells the engineer both, so nobody promises the user an empty inbox by lunch.
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.