Explore the evolving landscape of managing Exchange Online with Microsoft's Graph PowerShell module versus the traditional Exchange Online Management module. Discover practical insights and examples of using Graph PowerShell for mailbox operations and its current limitations in administrative functionality.
An introduction
Exchange and PowerShell go hand in hand and for the longest time those who administer Exchange Online used the Exchange Online Management PowerShell module. Microsoft's Graph PowerShell module provides access to Exchange Online as well and this can be seen in cmdlets present in sub-modules, however this coverage does not provide complete management capabilities. So why use it?
Sub-modules
The Microsoft Graph PowerShell module currently has 38 different sub-modules and thousands of cmdlets to choose from. In terms of Exchange management, there is no one module that includes all cmdlets for Exchange in Graph. One sub-module stands out and the is the Microsoft.Graph.Mail module and has 75 cmdlets as of the writing of this article. For reference, the Exchange Online PowerShell module has over 800 cmdlets as of the writing of this article. What other sub-modules may be of service when managing Exchange?
Microsoft.Graph.Compliance
Microsoft.Graph.Security
Microsoft.Graph.Reports
Microsoft.Graph.Contacts
These four sub-modules contain useful cmdlets for interacting with Exchange Online and administrators will find utilitarian cmdlets for tasks that focus on users mailboxes versus the macro level of configuring Exchange Online. Lets dive into what Exchange Online PowerShell can be used for and then dive into these modules to explore what practical tasks we can accomplish with them.
Exchange Online (ExO) management tasks
Exchange is a complex product that contains many manageable features. Below is a sample of what we can manage using the Exchange Online PowerShell module (v3):
- Anti-phishing
- Anti-spam
- Availability address space
- Clutter
- Contacts
- Distribution groups (dynamic as well)
- DKIM
- Email address policies
- Global address list
- Inbox rules
- Journaling
- Mail flow connectors
- Mailboxes calendars, folders, permissions, imports, migrations, OOF
- Message encryption
- Message traces
- Mobile device / OWA policies
- Public folders
- Quarantine policy
- RBAC roles
- Retention policy
- Safe attachment policies
- Safe link policies
- Sharing policy
- Transport rules
- and more.
Exchange and Graph PowerShell
Below is a short list of what we can do in Graph. It is vastly oversimplified as there are numerous cmdlets that we can use in Graph for Exchange Online, however they are relegated to user / mailbox level options.
- Send mail messages
- Low level mailbox integration emails, folders, invites, etc.
- Mailbox contacts
- User photos
Note: The above cmdlets are very user focus, with little to no Exchange Online administrative options.
As with any task in Graph, we cannot assume that Exchange related cmdlets exist only in the Microsoft.Graph.Mail sub-module, so lets run a more general query to see what cmdlets with the noun -email exist outside the Mail sub-module:
Get-Command *email*

Current capabilities in Graph
The desire to use Graph PowerShell instead of Exchange by Administrators would seem to have had a small effect on Microsoft as the capabilities present are not all encompassing. Exchange Online management has a lot of facets that are sufficiently covered in the Exchange Online PowerShell module but a lot of the backend management is missing in Graph. This doesn't mean that Graph does not have its place when it comes to managing Exchange but we need to be aware of limitations present in Graph. In this section of the article we will dive into some excellent uses of Graph PowerShell for the Exchange workload.
Mailbox operations
The majority of cmdlets focus on users and their mailboxes and can be quite handy in queries and troubleshooting issues with mailboxes.
Mail folders
PowerShell can be used to explore parts of mailboxes, with one of these being Folders. Folders are where people have files and organize their emails for business or personal use. Within these folders we can query things like size and counts of total and unread emails. Why would we care? Outlook has limitations that can be exposed when folders get to big. The below query might expose this to help troubleshooting Outlooking sync issues:
Get-MgUserMailFolder -UserId 22561a78-a72e-4d39-898d-cd7c57c84ca6 |
Format-Table DisplayName,*ItemCount,AdditionalProperties

In the above screenshot we see that while a couple folders have thousands of items, the numbers do not exceed Outlook limits.
The previous example is a simple query, but what if wanted to look at all mailboxes and see if any TotalItemCounts were over a certain threshold like 10,000 as this could cause poor Outlook performance. How would we do this?
foreach ($Mailbox in $Mailboxes) {
try {
$Folders = Get-MgUserMailFolder -UserId $Mailbox.Id -ErrorAction Stop |
Select-Object DisplayName, TotalItemCount
}
catch
{}
foreach ($Folder in $Folders) {
if ($Folder.TotalItemCount -gt 10000) {
$FolderName = $Folder.DisplayName
$MailboxName = $Mailbox.DisplayName
Write-Host "$MailboxName - $FolderName has over 10k mail items."
}
}
}
or Write-Host like so:
foreach ($Folder in $Folders) {
if ($Folder.TotalItemCount -gt 10000) {
$FolderName = $Folder.DisplayName
$MailboxName = $Mailbox.DisplayName
Write-Host "$($Folder.DisplayName) - $($Mailbox.DisplayName) has over 10k mail items."
}
Expected results from this code if mailboxes have over 10,000 items in a folder:

Four mailboxes were discovered with over 10,000 items stored in the Inbox Folder. We can proactively ask users to clean this up to prevent future issues.
Caveat: If we use delegate permissions, then we will only retrieve information about the local user and instead we need to connect to Graph using an application.
Example:
$Thumbprint = '77643669B10C64A23B35DA761FEEF4222039F439'
$AppId = 'dad165ff-4Fa8-4d0f-bbf9-5F67876be441'
$TenantId = '5d0DD54e-0082-4eb8-a311-ce17a036f3f4'
Connect-MgGraph -CertificateThumbprint $Thumbprint -ClientId $AppId -TenantId $TenantId
Mailbox message cleanup
There are a couple of scenarios here, one would be if an unintended email was sent, we could search and remove these messages, or perhaps we enabled the Microsoft Quarantine email notification by accident, without telling users and we need to remove those emails. Either scenario can be carried out with some Graph PowerShell.
Make sure again to connect with the Azure App to Graph as this will allow us to access all mailboxes. First, a sample run:
$UserID = '5fc8abf1-2178-4043-b601-4802e0a703cd'
Get-MgUserMessage -UserId $UserID -Filter "Subject eq 'Emails Quarantined for your mailbox.'"

One off to remove the top message: (testing for expected results)
Remove-MgUserMessage -UserId $UserID -MessageId
'AAMkADNhNWI1ODRiLTVmNjQtNGU4Mi1hYmRlLWFjMzJkYTY2Nzk2NQBGAAAAAABoWADN1g6YRpgk4ssxd16qBwCAEw_FeYpCSKoYCRXHiLVBAS0F7CqPAABpkGQwsqIcSYSWFGyKXc7bAAI4DSpIAAA='
This does indeed remove one email message. Next, we want to remove all of these for one user:
$msgFilter = "Subject eq 'Emails Quarantined for your mailbox.'"
$EmailToRemove = (Get-MgUserMessage -UserId $UserID -Filter $msgFilter).Id
foreach ($Email in $EmailToRemove) {
Remove-MgUserMessage -UserId $UserID -MessageId $Email -WhatIf
}
-WhatIf used to validate the cmdlet is correct and it is:

The -WhatIf switch reports the emails that would be removed from the mailbox
Remove the -WhatIf switch:
foreach ($Email in $EmailToRemove) {
Remove-MgUserMessage -UserId $UserID -MessageId $Email
}
Then we find out that, well, there are more emails that match this criterion. Why is that? Graph and paging. When running the cmdlet, we only pull the top 10 results. If we want all, we need to tell Graph that with the -All switch.
$EmailToRemove = Get-MgUserMessage -UserId $UserID -Filter "Subject eq 'Emails Quarantined for your mailbox.'" -All
Now we have them all:

We see from the .Count property that there are an additional 248 emails to remove. We see this because the Get-MgUserMessage does not return all results by default.
It is recommended to run -WhatIf one more time and then run the real thing. Once complete, check for any remaining messages, but there should be zero:

Now that weve run the code again, all emails were removed and can no longer be discovered.
Reviewing emails in a user's mailbox
An additional cmdlet exists, which will shrink coding needed, that will pull email counts for a users folders IF we have the folder ID.
Get-MgUserMailFolder -UserId $UserId |
Select-Object DisplayName,ID |
ForEach-Object {
Write-Host "$($_.DisplayName) - " -NoNewLine
Get-MgUserMailFolderMessageCount -UserId $UserId -MailFolderId $_.Id
}

Get-MgUserMailFolderMessageCount can also be used to get folder totals.
User photos
User Photos were at a time a function of Exchange Online PowerShell, and this was done historically because Exchange was the workload organizations used to enter Microsoft 365. Now that Exchange is no longer the center of the Microsoft 365 ecosystem, Microsoft has deprecated this and moved forward with a solution in Graph PowerShell instead. Indeed, from within Graph we see that there are four User Photo oriented cmdlets:
Get-MgUserPhoto
Get-MgUserPhotoContent
Remove-MgUserPhoto
Set-MgUserPhotoContent
To add a photo to an existing user, we can run code like this:
Set-MgUserPhotoContent -UserId Eric@powershellgeek.com -InFile C:\Data\SampleUserPhoto.jpg
To prove it works, we can check that Eric does indeed have a photo:
Get-MgUserPhoto -UserId Eric@powershellgeek.com

We now have the size of the users profile photo, 91x100 pixels. And we can even easily download the photo:
Get-MgUserPhotoContent -UserId Eric@powershellgeek.com -OutFile c:\data\SamplePhoto-Ronald.jpg

Above are the two photos, on the left is the one we exported and, on the right, the one we imported (view from 'C:\Data' directory).
Very easy to use and it could potentially be done in bulk. To find out who has a photo and who is missing their photo, we can run this one-liner:
Get-MgUser -All |
Select-Object DisplayName, Id |
ForEach-Object {
try {
$Photo = Get-MgUserPhoto -UserId $_.Id -ErrorAction Stop
Write-Host "$($_.DisplayName) has a photo." -ForegroundColor Green
}
catch {
Write-Host "$($_.DisplayName) is missing photo." -ForegroundColor Yellow
}
}
Which yields this:

Some users have a photo, some do not. Now we can go about adding photos to users with Graph PowerShell.
Discovering cmdlets
This is where using a consolidated module is easier when managing one Workload and by this I mean Exchange Online Management. Graph is a model based on access to API URLs and Graph PowerShell cmdlets are wrappers for these API calls. The problem exists when you are looking for cmdlets to manage a workload and they are spread among quite a few other modules. Try this search and you will see a lot of different modules referenced:
Get-Command '*user*mail*' | Format-Table Name, Source
These sub-modules are the unique values we find in this search:

We see that there are indeed quite a few functions that have mail related PowerShell cmdlets. For those new to managing some aspects of Exchange from Graph, there will be a learning curve. Don't think of this as a barrier, but an exploratory adventure into the many options that Graph offers for you. In fact, some of the cmdlets in Graph, with respect to Exchange, are quite useful and make some administrative tasks easier to work with. The delving into various aspects of a user's mailbox means that we can use Graph PowerShell instead of EWS, which is good as Microsoft announced the deprecation of EWS recently.
Conclusion Final thoughts
Microsoft Graph is like a treasure hunt where cmdlets that are relevant to a workload, may be stored in different sub-modules because of how Graph is organized. Add to this the permissions levels, Delegated vs Application permissions as well as PowerShell cmdlets vs Invoke0-RestMethod an administrator may be forgiven for feeling like the entire Graph PowerShell SDK is cumbersome. However, it is a good alternative for the functionality it provides and in terms of Exchange Online, Graph provides a much-needed interconnect into the inner parts of users mailboxes that may not be possible with Exchange Online PowerShell. In terms of overall fitness for managing Exchange Online, Graph simply is not there yet, and administrators will still need to lean on both the Exchange Online PowerShell module and the Security and Compliance PowerShell module along with Graph to effectively manage this workload. Microsoft will probably get to a point in the future where this may not be true, but that is not today.
Eric takes over our office!
Are you' wondering who Eric is and why we chose this dog picture for the blogpost?
Eric takes over the office! Guess who is our employee of the month (and last month, and next)? Eric, our Labrador! He sometimes takes over the office and with the help of ScriptRunner, he automated everything from sending 'important' emails to scheduling tasks effortlessly. He even found time for a nap under the desk. Spoiler: Hes a pro at both automation and relaxation! Check out Erics hilarious office adventure in the full video! Watch the video with adorable Eric here on LinkedIn and comment!
And if you're looking for more serious help, we offer cheat sheets to help scripting:
Your must-have PowerShell Cheat Sheet to automate Microsoft Exchange
Streamline your Microsoft Exchange administration tasks with our 5-page PowerShell cheat sheet for Microsoft Exchange. This indispensable guide compiles essential PowerShell commandlets and best practices to automate repetitive tasks, manage users, and configure settings with ease. Whether you're new to Exchange or an experienced admin, this cheat sheet is designed to make your job simpler and more efficient.
Get your Exchange Cheat Sheet here!
Your ultimate PowerShell Cheat Sheet

Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.The poster is available for download and in paper form.