When a phishing blast lands or an account is compromised, the clock starts — and the first hour of Microsoft 365 incident response is too often spent connecting, not responding. The actions you need are spread across at least three PowerShell surfaces (Exchange Online, Microsoft Purview, and Microsoft Graph) and several admin portals, each with its own sign-in ceremony, module quirks, and gotchas that keep shifting. This post walks through a free, open-source tool that consolidates those first-hour actions into a single console, and the platform realities that shaped it.

The tool: IR-Toolkit on GitHub — a single-file PowerShell WPF console, MIT-licensed, no build step.

The problem: three surfaces, one incident

Pulling a malicious message out of mailboxes now goes through a Microsoft Purview content search. Containing a compromised account goes through Microsoft Graph. Tracing a message or hunting a suspicious auto-forward goes through Exchange Online. Each connects differently, carries different roles, and fails in its own way. Under incident pressure, that context-switching is exactly where time leaks — and where mistakes happen.

The idea: one responder console

IR-Toolkit launches a graphical window with buttons to connect, hunt, contain, purge, and export, backed by a live activity log, a sortable results grid, and progress indicators. You sign in once and keep working. It is a convenience layer over supported Microsoft cmdlets for admins who already hold the relevant roles — not a replacement for governance, approvals, or the portals.

What it does

  • Connections & modules — MFA-friendly sign-in to Exchange Online, Purview, and Graph, with live status and a checker that installs a pinned, known-good Microsoft.Graph build.
  • Email purge (Purview) — build a query from sender, recipients, subject, date range, and attachment name/type; run an estimate first (real counts plus a per-item list), then purge behind a typed confirmation, soft or hard, with a loop mode for larger volumes. Every run writes a "discovered" and a "deleted" audit manifest.
  • User containment (Graph/Entra) — block sign-in, revoke all sessions, force a password reset, re-enable. Block + revoke is the standard fast-containment pair.
  • Investigation — Message Trace V2, inbox-rule and forwarding inspection, an all-mailbox external-forwarding hunt, and a newly-enrolled-MFA audit for spotting post-compromise persistence.

The one interesting engineering choice: a persistent worker runspace

WPF needs a single-threaded-apartment UI thread, and long-running cloud calls would freeze the window. So all cloud work runs in one long-lived background runspace, with UI updates marshalled back through the WPF dispatcher. Using a single persistent runspace rather than one per action is deliberate: the authenticated Exchange, Purview, and Graph sessions live inside it and persist across every subsequent action, so you sign in once. Tasks run one at a time; buttons disable while a task is in flight and re-enable when a dispatcher timer sees the async result complete.

Everything that changed in M365 admin PowerShell in 2025

Building this surfaced a series of platform changes that quietly break older scripts. If you maintain M365 automation, these are worth knowing:

Change What it means for your scripts
Search-Mailbox retiredDelete-by-query now uses a Purview content search (New-ComplianceSearch then New-ComplianceSearchAction -Purge) via Connect-IPPSSession.
10-item purge capA purge removes at most 10 items per mailbox per action. Larger volumes require looping the search-and-purge cycle.
-EnableSearchOnlySessionFrom ExchangeOnlineManagement v3.9, purge needs this switch on the S&C session or it fails with an opaque error. It changes the token audience, not the available cmdlets.
Message Trace V2Get-MessageTrace is deprecated for Get-MessageTraceV2, capped at a 10-day window and 5,000 rows with no classic paging — chunk the range.
Filter server-side, not client-sideBecause the service caps results before your filter runs, domain filtering must use the wildcard address form (*@contoso.com) or matches are silently dropped.
Graph SDK vs PowerShell 5.1Microsoft.Graph 2.34–2.36 throw GetTokenAsync ... does not have an implementation on Windows PowerShell 5.1; pin a known-good build, connect Graph first, or use PowerShell 7.
Entra modules retiredAzureAD and MSOnline are gone; all Entra user actions run through Microsoft Graph.

Turning cryptic errors into operator guidance

A tool used under pressure should not answer a missing role or a load-order conflict with a raw stack trace. IR-Toolkit catches the common failure modes — the Graph assembly conflict, a missing Search-And-Purge or Preview role, a capped result set — and writes the specific fix into the activity log instead. That guidance is as much the product as the actions themselves.

Operate it safely

Purge and password actions are destructive. Always run an estimate before a purge and confirm scope; prefer the narrowest scope that still catches every recipient; keep date ranges tight on tenant-wide operations; and treat exported manifests as incident evidence. Remember that even a hard delete can be retained by holds, retention, or Single Item Recovery — the "deleted" manifest records what the purge targeted, not what was irrecoverably destroyed. Test in a lab tenant first.

Get the tool and audit your posture

The script and full documentation are on GitHub: powershell-security-toolkit — the first tool in a broader collection, contributions welcome. To baseline the Microsoft 365 and Entra configuration you would be responding within, our browser-based x365 audit tool surfaces Conditional Access coverage, legacy-auth exposure, and MFA registration state without sending tenant data off your device. Pair it with our Conditional Access gaps and Active Directory Hardening guides for the surrounding identity hardening.

Frequently asked questions

What is the supported way to delete phishing emails from all mailboxes in Microsoft 365?

Since Search-Mailbox was retired, the supported method is a Microsoft Purview content search followed by a compliance purge action: create the search with New-ComplianceSearch, run it, then remove matches with New-ComplianceSearchAction -Purge. This connects through the Security & Compliance endpoint (Connect-IPPSSession) and requires the Search And Purge role in Purview, which is separate from Exchange Organization Management. Each purge action removes up to 10 items per mailbox, so larger campaigns need repeated rounds.

Why does my Microsoft Graph PowerShell script fail with "GetTokenAsync does not have an implementation"?

This is a known incompatibility between Microsoft.Graph SDK builds 2.34 to 2.36 and Windows PowerShell 5.1, and it can also be triggered when the Exchange module is loaded before Graph and poisons the shared authentication assemblies. The fixes are to pin a known-good Graph version (such as 2.33.0) installed in user scope and import it explicitly, to connect Microsoft Graph before Exchange and Purview, or to run under PowerShell 7 where the newer builds work.

How many items can a Microsoft Purview compliance purge delete at once?

A compliance purge action removes a maximum of 10 items per mailbox per action. To clear a larger phishing blast you must loop the search-and-purge cycle until the search returns zero matches. IR-Toolkit automates this with a "loop until cleared" option and a configurable maximum number of rounds.

Is IR-Toolkit safe to run in production?

It performs legitimate, supported administrative actions against tenants you are authorised to manage, and it requires you to already hold the relevant admin roles. However, purge and password actions are destructive, so the safe practice is to run an estimate first, confirm scope, keep exported manifests as evidence, and test in a lab tenant before using it during a live incident.

Where can I download the tool?

IR-Toolkit is open source under the MIT License and available on GitHub at https://github.com/theadminstack/powershell-security-toolkit. It is a single PowerShell script with no build step; download it, unblock it, and run it in STA mode. Full setup and role requirements are in the repository README.