Why · How it works · Before / after · Reports · Quick start · User guide · Developer guide
Important
Files downloaded from the Internet may be blocked by Windows and fail to run. Before using this project, unblock every file in the downloaded folder:
Get-ChildItem "C:\Chemin\Du\Dossier" -Recurse -File -Force | Unblock-FileReplace the example path with the folder where you downloaded or extracted this project.
The Microsoft Graph message trace API traces Exchange Online messages without a PowerShell session — but within strict rules: 100 requests per 5 minutes for the whole tenant, windows of 10 days, 90 days of history. And one rule it does not document: in $filter it keeps one value per property and silently ignores or — a query for three recipients returns only the last one. This tool plans its requests around these rules, keeps everything it receives in a local SQLite database, and never asks twice for what it already holds.
- One query per address of the shorter list (senders AND recipients), or per address (OR): the other conditions are applied on the database, so the report is exact.
*@domain, subject, status, message ID and IP addresses are supported. - One shared rolling budget of 90 requests per 5 minutes (configurable),
Retry-Afterhonoured, token renewed, network errors retried — and the requests of the last 5 minutes are remembered by the next run. - Page by page into SQLite: Ctrl+C or a failure keeps what was received; the next run collects only the rest. A period already collected by the same or a wider query is not asked again; only the last hours, which may still change, are.
-Mode Collectevery day keeps the whole tenant beyond the 90 days of Graph;-Mode Reportanswers from the database only, with any address pattern.- Why a message was not delivered: with
-IncludeDetailsthe route of each recipient is read and explained — Blocked by DLP, Blocked by mail flow rule (ETR), Recipient not found, Mailbox full, Quarantined, Rejected by remote server… with the rule, the status code and its Microsoft Learn page, and where the route of a failed recipient left the route of the delivered ones. - Read-only, one permission (
ExchangeMessageTrace.Read.All), certificate authentication built in — no module to install, SQLite bundled.
Invoke-MessageTraceGraph.ps1, the script this tool replaces, against Message Trace Report on the same lab tenant:
Each run writes Messages.csv (one row per message), Deliveries.csv (one row per message and recipient, what Graph returns, with the route, cause and reason when read), Routes.csv (one row per step of the routes read), Senders.csv and Recipients.csv (per day), optionally JSON with the Graph property names, and a self-contained HTML report that stays fast with 200,000 messages.
| Item | Requirement |
|---|---|
| PowerShell | 7.4 or later, Windows |
| Modules | None (certificate or secret). Interactive sign-in: Microsoft.Graph.Authentication |
| Permission | Microsoft Graph ExchangeMessageTrace.Read.All with admin consent |
| Tenant | The service principal of the Microsoft application 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373 (guide, chapter 7) |
| SQLite | Bundled in lib\sqlite — nothing to install |
git clone https://github.com/Nico77600/MessageTraceReport.git
cd MessageTraceReport\package
notepad .\config\MessageTraceReport.config.psd1 # TenantId, AppId, CertificateThumbprint
.\Invoke-MessageTraceReport.ps1 -Sender john@contoso.com # last 48 hours
.\Invoke-MessageTraceReport.ps1 -Recipient *@fabrikam.com -Status Failed -Range Last7Days -Open
.\Invoke-MessageTraceReport.ps1 -SenderFile .\vip.csv -Recipient *@gmail.com -Start 2026-09-01 -End 2026-10-01
.\Invoke-MessageTraceReport.ps1 -Sender alerts@contoso.com -Recipient helpdesk@contoso.com -Operator Or -IncludeDetails
.\Invoke-MessageTraceReport.ps1 -MessageId '<CAJ1234@mail.contoso.com>' -Range Last10Days -IncludeDetails # why each recipient got it or not
.\Invoke-MessageTraceReport.ps1 -Mode Collect # scheduled task, whole tenant
.\Invoke-MessageTraceReport.ps1 -Mode Report -Range Last30Days -Sender 'sales-*@contoso.com'
.\Invoke-MessageTraceReport.ps1 -Mode StatusComing from Invoke-MessageTraceGraph.ps1? Its parameter names are kept as aliases (-StartDate, -Senders, -RecipientsCsv …): see the migration table. The package folder of this repository holds exactly the files needed to run Message Trace Report, with both guides. The zip of each release contains the same run-time files with the HTML guides; .\tools\New-MtrPackage.ps1 builds that zip content from the repository.
| Guide | Content |
|---|---|
| User guide | What you need before the first trace, the one-time setup in the package folder or the extracted release, then one command per everyday question: a sender, a domain, a list of addresses, a message ID, why a recipient did not get it, the daily collection, a report from the database. Where the files are written, how to read the HTML report, the exit codes, and the situations that come back with what to do. |
| Developer guide | Everything else: what the Graph API really does and the lab measurements, the performance figures, the prerequisites and the application registration, every configuration key, tracing and the routes in detail, the scheduled collection, the reports and their columns, exit codes and logs, the migration from the original script, the internals and how to test a change, troubleshooting and the Graph calls. |
Both guides also exist as a single HTML file with a light and a dark theme (package/docs/MessageTraceReport-UserGuide.html, package/docs/MessageTraceReport-Guide.html): download them and open them locally, or use the copies in the release zip.
Invoke-Pester -Path .\tests # Pester 5+, in-memory Graph API, no connection to Microsoft 365
.\tests\New-DemoReport.ps1 -Open # a report of fictitious Contoso data, through the real enginetools\Build-Documentation.ps1 rebuilds the HTML guides; tools\New-ReadmeImages.ps1 renders the graphics of this page from the guide, in a light and a dark version.
MIT. The bundled SQLite components keep their own licenses: see THIRD-PARTY-NOTICES.md.
This Script is a Personal project. It's provided "AS-IS". It's not an official Microsoft product so no support can be expected from Microsoft.
As any scripts you must read carefully the documentation and test it first in a Test environment before any run in Production.



