Use the Microsoft Graph PowerShell SDK to Find and Remove Mailbox Items

Two years ago, I wrote about building a PowerShell script with Graph API requests to mimic the functionality of the Search-Mailbox cmdlet. Time moves on and technology changes. Microsoft finally deprecated and removed the Search-Mailbox cmdlet last year and the Microsoft Graph PowerShell SDK is now well into its second version. It’s time to explore a different aspect of finding and removing messages from Exchange Online mailboxes, this time using SDK cmdlets.

Among the differences we’ll discuss are:

  • How to find a set of user accounts to process. Instead of using the Get-ExoMailbox cmdlet from the Exchange Online module, we find user accounts licensed for Exchange Online.
  • Using a filter to find messages instead of searching a mailbox. Specifically, using date range and sender SMTP addresses in filters.
  • The difference between moving and removing messages.

Some developers still dislike the Microsoft Graph PowerShell SDK. The documentation remains inconsistent and unhelpful at times and getting to know the SDK foibles takes too much time and effort. I agree. However, after investing time to get to know the SDK, you learn how to use the Graph documentation to understand what SDK cmdlets can do. Using the SDK, perhaps with the help of a tool like GitHub Copilot, stops its peculiarities from getting (too much) in the way. Overall, not having to deal with pagination and token refreshes tips the balance for me, which is why most of my scripts now use the SDK.

The Scenario

A practical example is a great way to explain a principle. In this case, I decided that I should write a script to remove notification messages from user mailboxes. I get a bunch of notifications from Viva Engage (Yammer), LinkedIn, the Microsoft Technical Community, Power Apps, the Microsoft 365 admin center, and perhaps most bizarrely in some senses, Teams. Why do I say this? Well, how many times were you told that Teams would replace email? With that thought in mind, it amuses me that Teams generates quite so many emails.

The thing that connects these messages is that they are transactional and lose their value rapidly. There is little need to keep notifications after they are read, and certainly after a week or so, these messages lose all value. Having a script to clean them out from user mailboxes seems like a worthy public service.

The Script

The outline steps for the script flow are as follows:

  • Connect to the Graph. To access other users’ mailboxes, the script needs to use application rather than delegated permissions, so it uses an Entra ID app and a certificate thumbprint to authenticate. The app must receive consent for the User.Read.All, Mail.ReadWrite, and Mail.Send application permissions to read account information, access and update mailboxes, and send email with the results.
  • Define the SMTP email addresses used to send the notification messages to remove. The script defines 12 different addresses. A round dozen seemed like a good number, but you can define fewer or more addresses depending on your needs.
  • Set the date range to search for notification messages. The script looks in the Inbox for messages between 30 and 1825 (five years) old. If the user has refiled a notification message out of the Inbox, it’s probably because they want to keep the item, so the script shouldn’t process the message.
  • Find user accounts with an Exchange Online Plan 1 or Plan 2 service plan. All Microsoft 365 and Office 365 licenses include these service plans, so it’s a way to find Exchange mailboxes without loading the Exchange Online module.
  • For each account, search the mailbox for notification messages matching any of the 12 defined sender addresses in the specified date range. If any messages are found, delete them. The Remove-MgUserMessage moves the message into the Deletions folder in Recoverable Items rather than the Deleted Items folder. If you want to move items to Deleted Items instead, use the Move-MgUserMessage cmdlet. Details of removed messages are captured in a PowerShell list (Figure 1), which is available for further analysis and reporting if necessary.
Notification messages removed from mailboxes
Figure 1: Notification messages removed from mailboxes
  • After processing the mailboxes, send email to the mailboxes where messages were removed to report what happened. Some basic HTML formatting is done to generate the message body (Figure 2).
  • Finally, an output file is generated containing details of all the notification messages removed from user mailboxes. If the ImportExcel module is available on the workstation, the output file is an Excel worksheet. Otherwise, it’s a CSV file.
Email to tell user about the removal of some notifications
Figure 2: Email to tell thw=e user about the removal of some notifications

You can download the full script from GitHub.

Understanding the Filter Used to Find Messages

The heart of the script is the call to the Get-MgUserMailFolderMessage cmdlet to find messages in the Inbox that match any of the 12 defined sender addresses in the target date range. Here’s the command from the script:

[array]$Messages = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId 'Inbox' `
  -Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate) `
   and (sender/emailAddress/address eq '$UM0' or sender/emailAddress/address eq '$UM1' or sender/emailAddress/address eq '$UM2' `
   or sender/emailAddress/address eq '$UM3' or sender/emailAddress/address eq '$UM4' or sender/emailAddress/address eq '$UM5' `
   or sender/emailAddress/address eq '$UM6' or sender/emailAddress/address eq '$UM7' or sender/emailAddress/address eq '$UM8' `
   or sender/emailAddress/address eq '$UM9' or sender/emailAddress/address eq '$UM10' or sender/emailAddress/address eq '$UM11')" `
   -Property Id, Subject, Sender, SentDateTime -All -PageSize 999

The important points about this command are:

  • The target mailbox folder is stated as “Inbox” rather than a folder identifier. This is because Microsoft defines a set of well-known folders that can be referenced using tags rather than identifiers. For instance, the tag for the Deleted Items folder is DeletedItems and the tag for the Deletions folder in the Recoverable Items structure is RecoverableItemsDeletions. Well-known names work regardless of the locale chosen for the target mailbox. For instance, the query will automatically translate Inbox to Boîte de reception for mailboxes configured in a French language locale. If you want to retrieve messages from a folder that isn’t in the well-known set, you’ll need to find the folder identifier first. Do this by running the Get-MgUserMailFolder to retrieve the list of mailbox folders and select the identifier for the desired folder.
  • The Graph requires dates to be in sortable format with a Z suffix. For example, 2024-08-31T23:47:09Z.
  • The sender/emailaddress/address construct used to match the sender SMTP address is required because the Sender property holds two sub-properties, one of which is the emailaddress. That property holds two more sub-properties, and we want to match against the (SMTP) address property rather than the name of the sender.
$message.sender.emailaddress

Address               Name
-------               ----
noreply@microsoft.com Alice Smith (SHE/HER) in Teams
  • To speed up retrieval of message items from mailboxes, the script fetches a limited set of message properties (only those needed for reporting) and increases the page size from the default 10 to 999. The combination of these steps instructs the Graph to fetch large pages of mailbox data with just a few properties. It’s a lot faster than fetching pages of 10 items, all of which have many properties.

Next Steps

The script as written runs interactively. However, if you were to use it in a production environment, it’s probably better to run on a scheduled basis as an Azure Automation runbook. It’s often difficult to debug PowerShell code in Azure Automation runbooks, so making sure that the code works when run interactively in app mode is a practical step to ensure that the code will run on a headless server. Apart from all the output statements, Azure Automation will be happy to use the app to run the code. Alternatively, you can update the script to use a managed identity.

Another point to consider is that using application permissions means that the script can access all mailboxes in the tenant. Unless you really need to process every mailbox, it’s a good idea to limit access to sensitive mailboxes using RBAC for applications.

Remember that code like this is intended to be a practical example to illustrate a principle. In this case, it’s how to find messages using date ranges and sender addresses. I’m sure you’ll find a way to exploit code like this in your tenant. All that’s needed is a little inspiration.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. David

    Can you find a way to assign a retention tag to an individual email?

  2. Ken

    I really need to use my CASE (Copy and Steal Everything) statement on this to delete all phishing emails that are being sent. Especially when a user account is compromised somehow and blasts our org with the same email they were blasted with.

Leave a Reply