2026 is the year in which Microsoft plans to pull the plug on EWS, and before this happens, I’m taking some time to go over my code samples in order to update them to the Graph API or alternatives, as needed. One such code sample I’ve used over the years serves as a quick way to clean up user’s Deleted items folder, which in Exchange Online is not subject to the default 30 days “recycle” period, as we discussed previously.
While retention policies can indeed be configured to clean up deleted items, in most cases they will not act immediately, thus in scenarios where a faster approach might be needed, other solutions must be examined. Get-RecoverableItems is great for enumerating deleted items, but there is no companion PowerShell cmdlet to actually remove them (only to recover). And as the good old Search-Mailbox is also not an option anymore, and no real alternative has been released on that front either, we are stuck with the Graph API.
Unfortunately, there have been almost no improvements in the Graph story, either. The only thing worth mentioning here is the addition of the List items method, as part of the Mailbox Import/Export API. Said method is the only one to give you full inventory of all items within the Deleted Items folder, whereas the “native” one still ignores specific item types (i.e. Contacts, Tasks or Appointments are not returned). This is illustrated on the screenshot below:
#Count of Deleted item as per the Import/Export API GET https://graph.microsoft.com/beta/admin/exchange/mailboxes/vasil@Michev.info/folders/deletedItems/items?$count=true #Count of Deleted items via the "standard" endpoint GET https://graph.microsoft.com/beta/me/mailFolders/deletedItems/messages/$count #Count of Deleted items via Get-RecoverableItems (Get-RecoverableItems -Identity vasil -SourceFolder DeletedItems).Count
While we do have a few ways to get a proper count of Deleted items, removing them is another story. There is still no support for EWS’s Folder.Empty method, so we’re stuck with removing each item individually. The Import/Export API endpoints do not support any delete item operations, so no improvements coming from that front either. And while we can leverage them to get a set of item IDs for items of all types currently residing within Deleted items, only those representing valid “message” can be removed via the DELETE message method.
That said, there is a workaround that we can use for Contacts objects, as shared by Glen in this thread.
With all the above in mind, here’s how you can leverage the Graph API, or Graph SDK for PowerShell, to cleanup the Deleted items folder. As usual, we start with permissions. Removing the items requires Mail.ReadWrite, and we can also leverage the Contacts.ReadWrite scope, if planning to remove deleted Contacts objects as well. Remember that if you are going to use delegate permissions, you must run the code either as the mailbox owner or have Full permissions granted in addition to the Mail.ReadWrite.Shared scope.
Connect-MgGraph -Scopes "Mail.ReadWrite.Shared" -NoWelcome
While the goal is to clean up older items and free some space in the process, you might still want to enforce a recovery period so that user can easily restore items deleted in error. To facilitate this, we can add a “cutoff” period (in days) and account for it when examining the “age” of each item. While there are some caveats here, to keep things simple we will just be examining the LastModifiedDateTime value. A default 30-day window is enforced and a parameter is introduced to control it.
[Parameter(Mandatory = $false)] [int]$DaysToKeep = 30
Once connected to the Graph, we can start processing the set of mailboxes provided as input, and act on items found in their corresponding Deleted items folder. To keep things simple, we rely on the built-in Graph logic and error handling as a basic form of validation. Luckily the Get-MgUserMailFolder cmdlet can help us handle scenarios where the mailbox doesn’t exist, a matching folder was not found or we don’t have permissions to access it.
try { $deletedItemsFolder = Get-MgUserMailFolder -UserId $mbx -MailFolderId DeletedItems -ErrorAction Stop }
catch {
Write-Warning "Mailbox $mbx not found or inaccessible."
continue
}
As you can note from the above snippet, we can leverage the “well-known” value of DeletedItems, instead of having to use additional lookups to get the folder object. And since the properties of the folder object include the TotalItemCount, we do use it to set the correct expectations. As discussed above, we cannot rely on the Graph (Get-MgUserMailFolderMessage) on that front, as they only cover specific “kinds” of items, thus the count of objects we retrieve by it will usually be undervalued.
Lastly, we compare the LastModifiedDateTime of any items found in the Deleted items folder against our cutoff, then try the actual deletion. Some basic output is produced in the form of Mailbox name – total items found in the Deleted items with count of items that we can process, count of items skipped and those successfully deleted. The example below shows how to run the function and sample output:
Remove-DeletedMailboxItems -Mailbox sharednew@michev.info,shared@michev.info -DaysToKeep 10
You can get the function as a Gist code sample. As the goal here was to simply illustrate the current state of affairs, do not see this as a fully fledged or production-ready code. For a more robust solution, you can take a look at Tony’s script here.
In summary, the Graph API still lacks basic functionality when compared to good old EWS, and even scenarios as simple as emptying a folder can prove quite challenging. No support for archive access. No way to list all items within Deleted items or act on them. No hard-delete (although you can move items to the Purges folder to achieve the same). As the deprecation date approaches, I’m not very optimistic we will ever get parity on this, and many other scenarios.