Dealing with Folders and Messages
Exchange Online mailboxes are literally stuffed full of information. It’s not just email that finds its way into folders. Mailboxes also hold calendar events, contacts, tasks, compliance records, and anything else that users and developers store.
Exchange Web Services (EWS) has long been the API for access to mailbox data. But Microsoft will block EWS access for non-Microsoft apps from 1 October 2026. Even if Microsoft has some gaps in coverage to close, the Graph API is the path forward for app developers. Microsoft still hasn’t made much progress in the delivery of Graph APIs for Exchange Online management, but APIs do exist to access the most important mailbox elements: folders, messages, calendar events, and contacts. Tasks are handled by the long-term plan to unify tasks within Microsoft 365.
If administrators want to access mailbox content, they should use the Graph APIs. You can use an interactive Microsoft Graph PowerShell SDK session to test the commands explained here against your own mailbox. Access to other user mailboxes requires an app with consent for the Mail.Read application permission. Apps that access mailbox content obviously could access confidential or sensitive information, so consider using RBAC for Applications to restrict app access to mailboxes.
Accessing Mail Folders in a Mailbox
Mailbox folders are defined as a Graph resource. The cmdlet to find all the mail folders in a mailbox is Get-MgUserMailFolder. Mail folder is an important point. The set of folders returned by Get-MgUserMailFolder don’t include folders like Calendar and Contacts because those folders are created for a specific purpose. The set includes any folder that can hold mail messages, including those created by the mailbox owner.
The example shown below uses the All parameter to instruct the Graph to return all mail folders instead of the first 10, which is the default. The IncludeHiddenFolders parameter is set to true to have the Graph include hidden folders in the returned set.
[array]$Folders = Get-MgUserMailFolder -Userid $UserId -All -IncludeHiddenFolders $true
The properties returned for an individual mail folder look something like that shown below. The id is the most important property because it’s needed to fetch details of the messages stored in the folder.
ChildFolderCount : 0 ChildFolders : DisplayName : Inbox Id : AAMkADAzNzBmMzU0LTI3NTItNDQzNy04NzhkLWNmMGU1MzEwYThkNAAuAAAAAAB_7ILpFNx8TrktaK8VYWerAQBe9CuwLc2fTK7W46L1SAp9AAAA2lHHAAA= IsHidden : False MessageRules : Messages : MultiValueExtendedProperties : ParentFolderId : AAMkADAzNzBmMzU0LTI3NTItNDQzNy04NzhkLWNmMGU1MzEwYThkNAAuAAAAAAB_7ILpFNx8TrktaK8VYWerAQBe9CuwLc2fTK7W46L1SAp9AAAA2lHEAAA= SingleValueExtendedProperties : TotalItemCount : 5517 UnreadItemCount : 0 AdditionalProperties : {[sizeInBytes, 1938417762]}
A folder can have child folders. For instance, if you delete a folder, Exchange moves the folder to the Deleted Items folder where it becomes a child folder. This example uses the Get-MgUserMailFolderChildFolder cmdlet to retrieve details of child folders found in the Deleted Items folder. The deleted folder holds 2 items of a total size of 393,465 bytes.
[array]$ChildFolders = Get-MgUserMailFolderChildFolder -Userid $UserId -MailFolderId 'DeletedItems' $ChildFolders | Format-Table DisplayName, TotalItemCount, AdditionalProperties DisplayName TotalItemCount AdditionalProperties ----------- -------------- -------------------- Project Helia 2 {[sizeInBytes, 393465]}
In this case, the command uses a ‘well-known folder name’ to identify the folder to process. The well-known folders are those that Outlook creates by default. Microsoft documents the well-known folders online. If a folder isn’t a well-known folder, you must pass the folder identifier. For example, this command uses the identifier for a selected folder:
$Folder = $Folders[5] $ChildFolders = Get-MgUserMailFolderChildFolder -Userid $UserId -MailFolderId $Folder.id
Accessing Messages
After retrieving the set of mail folders, the next step is to fetch messages from folders using the Get-MgUserMailFolderMessage cmdlet. Some points to remember are:
- The default page size for messages is 10. To speed retrieval up by cutting down on the number of round-trips to fetch items, use a larger page size (up to the maximum page size of 999).
- Only mail messages are retrieved. A mail folder can hold a mixture of different kinds of items. However, Get-MgUserMailFolderMessage only finds items with message class IPM.Note.
- For performance’s sake, it is critical to restrict the number of properties for the request to return. This is always the case with Graph requests, but limiting properties is very important when dealing with messages.
Assuming that a folder is selected, we can run Get-MgUserMailFolderMessage to fetch message items. Here’s an example where the cmdlet parameters limit fetching to three properties and use the maximum page size to minimize retrieval time:
[array]$Items = Get-MgUserMailFolderMessage -UserId $UserId -MailFolderId $Folder.id -All ` -Property sender, createdDateTime, subject -PageSize 999
To illustrate the importance of using a large page size when dealing with large quantities of objects, running the command above against a folder containing 26,465 items took 11.283 seconds with a page size of 999. Using the default page size (10) took 188 seconds. That’s quite a difference.
Using Single Value Extended Properties
A single value extended property contains a single value for objects like messages, calendar events, and contacts. These are optional properties, many of which originated as MAPI properties and are not included in the message resource type. Multi-value extended properties are also available, but I won’t address them here.
Microsoft built Exchange Server using MAPI, the messaging application programming interface. Since 1992, many changes and updates have occurred in MAPI to support server and client (Outlook) developments. Today, 1080 tags are listed in the Exchange Server Protocols Master Property List, including tags for the spam confidence level of a message, the HTML body part, delivery time, conversation index, and so on. You can see these properties by examining messages using the MFCMAPI utility.
Single value extended properties store some message properties that you’d expect to be in the default set. Amongst these properties is PidTagMessageSize, which stores the size of a message in bytes. As explained above, the properties returned for a mailbox folder include the total size of items in the folder, but messages don’t include a size property unless you fetch the PidTagMessageSize. Here’s how to modify the Get-MgUserMailFolderMessage command used above to include the retrieval of the message size:
[array]$Items = Get-MgUserMailFolderMessage -UserId $UserId -MailFolderId $Folder.id -All ` -Property sender, createdDateTime, subject -PageSize 999 ` -ExpandProperty "singleValueExtendedProperties(`$filter=Id eq 'LONG 0x0E08')"
To fetch the message size, the cmdlet uses the ExpandProperty parameter to instruct the Graph to expand the values of the single value extended property identified by the filter clause, which uses the unique tag identifier (0x0E08) to locate the property. The tag identifier is described in the protocol definition for a single value extended property (see link for PidTagMessageSize above).
The single item extended property is returned in an array. To use the message size in bytes, extract the value from the array:
$Item.SingleValueExtendedProperties Id Value -- ----- Long 0xe08 169616 $Item.SingleValueExtendedProperties.value 169616
An example of using the PidTagMessageSize to report message size is available in the script discussed in this article. The script uses Graph API requests to fetch the property.
Dealing with Other Mailbox Content
The steps outlined in this article are the basics of dealing with mail folders and messages using Microsoft Graph PowerShell SDK cmdlets. Dealing with the calendar and contacts requires a different set of cmdlets and permissions.
For instance, to find the set of calendars available to a mailbox requires consent for the Calendars.Read permission before running the Get-MgUserCalendar cmdlet. The set of calendars includes any calendar that the user can access. This code fetches that set and extracts the default calendar:
[array]$Calendars = Get-MgUserCalendar -UserId $UserId $DefaultUserCalendar = $Calendars | Where-Object {$_.Name -eq 'Calendar'}
Fetching events from a calendar is done using the Get-MgUserCalendarView cmdlet. This example searches a calendar to find events that match a filter for a date range:
[array]$Events = Get-MgUserCalendarView -UserId $UserId -CalendarId $DefaultUserCalendar.Id -EndDateTime '2024-06-30' -StartDateTime '2024-06-01' -Filter "Subject eq 'Important Meeting '"
Much the same happens to retrieve contacts. The Contact.Read permission enables use of the Get-MgContact cmdlet to fetch details of personal contacts stored in the Contacts folder of the mailbox.
[array]$Contacts = Get-MgContact -All
Once you get used to how the Graph works, everything follows a logical path. I guess that’s the point of having a well-defined set of resources and API methods. At least, I think it is. With that thought in mind, I hope that the advice given here helps you to navigate mailbox folders and messages.