When a Microsoft 365 account is compromised, most of the evidence lives in logs that age out on a fixed clock and can only be pulled through APIs with record caps, so an incomplete or late export quietly becomes the investigation's biggest gap. This guide covers how to collect that evidence: the Microsoft-Extractor-Suite PowerShell module, the Microsoft Graph audit log query API, Search-UnifiedAuditLog and the other native export paths, what each one returns and where each one silently truncates. It covers scoping a collection, the permissions and the collector identity, output formats, integrity and the checks that prove nothing was missed. It is written for incident responders, DFIR consultants and Microsoft 365 administrators who have to hand over evidence that stands up to review. What the unified audit log records and how to build detections on it are covered in the Microsoft 365 Unified Audit Log Guide for Detection; this page is about getting the data out.
This is an independent guide from NHI Mgmt Group, based on Microsoft's public documentation and the Microsoft-Extractor-Suite project documentation as of November 2026. It is not sponsored or reviewed by Microsoft or Invictus IR. Product features change, so confirm settings against the current documentation.
Key takeaways
- Retention sets the deadline. Entra ID sign-in and audit logs are kept for 7 days on the Free tier and 30 days on P1 or P2, message trace covers 90 days, and the unified audit log 180 days on Audit (Standard); collect the shortest-lived sources first.
- Every export path has a cap. Search-UnifiedAuditLog returns at most 5,000 records per call, or 50,000 per paged session, so a collector must split time windows until each window is under the cap.
- Microsoft-Extractor-Suite automates the splitting. Its Get-UAL function shrinks time windows adaptively to stay under 5,000 records and exports to CSV, JSON, JSONL or SOF-ELK format, alongside more than 40 other collection functions.
- The collector is a powerful identity. Reading every mailbox needs application permissions such as Mail.ReadWrite; register a dedicated app per engagement, use a certificate, and remove it when collection ends.
- Prove completeness, not just collection. Record the query, window, record count and hash for every export, and reconcile counts against a second method before analysis starts.
What needs collecting and how long it lasts
A business email compromise or tenant compromise investigation normally needs eight to ten sources. They differ in retention, in the API that serves them, and in whether turning them on now helps at all. None of the sources below is retroactive: enabling a log or upgrading a licence during an incident only affects data from that point, or data still inside the old retention window.
| Source | Default lookback | Native export path | Extractor Suite function |
|---|---|---|---|
| Entra ID sign-in logs (interactive, non-interactive, service principal, managed identity) | 7 days (Free), 30 days (P1, P2) | Microsoft Graph reports API, diagnostic settings | Get-GraphEntraSignInLogs |
| Entra ID audit logs | 7 days (Free), 30 days (P1, P2) | Microsoft Graph reports API, diagnostic settings | Get-GraphEntraAuditLogs |
| Unified audit log | 180 days (Audit Standard); 1 year for Exchange, SharePoint, OneDrive and Entra records (Audit Premium) | Purview Audit search, Search-UnifiedAuditLog, Graph auditLog queries, Management Activity API | Get-UAL, Get-UALGraph |
| Mailbox audit records | Follows unified audit log retention | Search-UnifiedAuditLog with record type ExchangeItem | Get-MailboxAuditLog |
| Message trace | 90 days, 10 days per query | Get-MessageTraceV2 | Get-MessageTraceLog |
| Inbox rules and transport rules | Current state only | Get-InboxRule, Get-TransportRule, Graph mailbox rules | Get-MailboxRules, Get-MailboxRulesGraph, Get-TransportRules |
| OAuth app grants, MFA methods, Conditional Access, devices, roles | Current state only | Microsoft Graph | Get-OAuthPermissionsGraph, Get-MFA, Get-ConditionalAccessPolicies, Get-Devices, Get-AdminUsers |
| Azure activity logs | Per Azure Monitor settings | Az module, diagnostic settings | Get-ActivityLogs, Get-DirectoryActivityLogs |
| Microsoft Graph activity logs | Only from when the diagnostic setting was created | Diagnostic settings to Log Analytics, storage or Event Hubs | Not a collection function; query the destination |
- Order of collection: Entra sign-in and audit logs first on Free-tier tenants (7 days), then current-state configuration (rules, OAuth grants, mailbox permissions), because an attacker or a well-meaning administrator can change it at any moment, then message trace, then the unified audit log.
- Current state is evidence too: an inbox rule or consent grant that still exists today proves persistence even if the audit record of its creation has aged out. Export it before containment removes it, and record who removed what and when.
- Graph activity logs: on Entra ID P1 or P2 tenants that already stream MicrosoftGraphActivityLogs, they show every Graph request with AppId, ServicePrincipalId, IPAddress, RequestUri, Scopes and ClientAuthMethod. Microsoft says delivery is usually within 30 minutes and can take up to 2 hours. If the setting did not exist before the incident, there is nothing to collect for the past.
For the retention obligations that apply to these logs, see the Log Retention Requirements Map.
Native export paths and their limits
Every tool, including the Extractor Suite, sits on one of four Microsoft interfaces. Knowing their limits tells you what a tool can and cannot do for you.
| Interface | Documented limits | Investigation use | Watch for |
|---|---|---|---|
| Purview portal Audit search | 180-day range; up to 10 concurrent search jobs per user with one unfiltered job; export of 50,000 rows (Standard) or 1,000,000 rows (Premium); jobs kept 30 days; broad searches can take up to 48 hours | Scoped searches by an analyst; quick confirmation | Truncated exports that look complete |
| Search-UnifiedAuditLog (Exchange Online PowerShell) | ResultSize maximum 5,000; ReturnLargeSet paging returns unsorted data up to 50,000 results; switching between ReturnLargeSet and ReturnNextPreviewPage on one SessionId limits output to 10,000 | Scripted collection of users, operations or windows | Silent cap at 5,000 or 50,000 per window; HighCompleteness is in preview and slower |
| Microsoft Graph auditLog queries (POST /security/auditLog/queries) | Asynchronous: submit, poll status (notStarted, running, succeeded, failed, cancelled), then read records; tenant daily and concurrent query limits | Large server-side searches filtered by record type, operation, user, IP, object or administrative unit | Global service only (not US Government L4/L5 or 21Vianet); intermittent reliability reported by tool maintainers |
| Office 365 Management Activity API | Content available for 7 days after it is published | Continuous collection into a SIEM, not historical recovery | Cannot reach back past 7 days |
- The Graph permission is scoped by service: AuditLogsQuery-Entra.Read.All, AuditLogsQuery-Exchange.Read.All, AuditLogsQuery-SharePoint.Read.All, AuditLogsQuery-OneDrive.Read.All, AuditLogsQuery-Endpoint.Read.All and AuditLogsQuery-CRM.Read.All exist alongside the broad AuditLogsQuery.Read.All. Grant only the services in scope.
- The keywordFilter caveat: in a Graph audit query, keywordFilter matches only non-indexed content such as AuditData properties, not indexed fields such as the operation name. Use operationFilters for operations.
- Message trace paging: Get-MessageTraceV2 has no paging. To continue, take the recipient address and received time of the last record and pass them as StartingRecipientAddress and EndDate. It accepts 100 queries in a rolling 5-minute window.
- Timestamps: audit and message trace output is in UTC. Keep it in UTC through analysis and convert only in the final report.
How Microsoft-Extractor-Suite works
Microsoft-Extractor-Suite is an open-source PowerShell module maintained by Invictus Incident Response and published on the PowerShell Gallery under the GNU GPL v2 licence (module version 4.1.0 in the repository in November 2026). It wraps the Exchange Online, Microsoft Graph and Az PowerShell modules, so it inherits their authentication and limits, and adds the window-splitting, paging, output handling and logging that analysts otherwise script by hand.
| Function group | Functions | What it adds over the raw cmdlet |
|---|---|---|
| Unified audit log | Get-UAL, Get-UALGraph, Get-MailboxAuditLog, Get-AdminAuditLog | Adaptive time windows (TargetEventsPerWindow, default 3,000, maximum 5,000); filters by -Group (Exchange, Azure, Sharepoint, Skype, Defender), -RecordType, -Operation, -UserIds, -IPAddresses, -ObjectIDs; -AuditDataOnly to keep only the AuditData payload |
| Mail and sessions | Get-Sessions, Get-MessageIDs, Get-Email, Get-Attachment, Get-MessageTraceLog | Turns MailItemsAccessed records into attacker SessionId and InternetMessageId lists, then retrieves the exact messages touched |
| Rules and permissions | Get-MailboxRules, Get-TransportRules, Get-MailboxPermissions, Get-MailboxAuditStatus | Tenant-wide export of persistence settings and of mailboxes whose audit configuration was changed |
| Identity | Get-GraphEntraSignInLogs, Get-GraphEntraAuditLogs, Get-OAuthPermissionsGraph, Get-MFA, Get-RiskyUsers, Get-RiskyDetections, Get-ConditionalAccessPolicies, Get-AdminUsers, Get-Devices | Sign-in collection by event type (interactive, non-interactive, service principal, managed identity) and a snapshot of identity configuration |
| Licensing | Get-Licenses, Get-LicenseCompatibility, Get-LicensesByUser | Tells you which retention and Premium events each user actually has before you plan the collection |
| Orchestration | Start-MESTriage (Quick, Standard, Comprehensive templates), Start-EvidenceCollection (beta) | Runs a defined set of functions per case, with custom templates as .psd1 files |
- Output formats: CSV (default for Get-UAL), JSON, JSONL and SOF-ELK. JSON or JSONL keeps the nested AuditData intact; CSV is easier for a quick look but flattens structure. Choose JSONL for anything going into a log platform.
- Dates: a date without a time defaults to midnight; a local timestamp is converted to UTC; an ISO 8601 value ending in Z is used as given. Always pass explicit start and end values in ISO 8601 UTC so the scope is unambiguous in your notes, because default lookbacks differ between functions.
- Speed: the project documents that adaptive splitting makes two to three API calls per window, so a tenant-wide unified audit log pull is slow. Its own guidance is to filter triage runs by user and to run Get-UAL on its own for full datasets, since long runs can time out on dropped connections.
- Platform: the documentation recommends a native Windows environment because of known compatibility issues elsewhere.
The collector identity and its permissions
Evidence collection needs read access to the most sensitive data in the tenant. The account or app that does it is a privileged non-human identity for the duration of the case and should be treated as one.
| Collection | Least role or permission the project documents | Note |
|---|---|---|
| Unified audit log | AuditLogs role (PowerShell) or AuditLogsQuery.Read.All (Graph) | Prefer the service-scoped AuditLogsQuery-*.Read.All permissions for Graph |
| Entra ID sign-in and audit logs | Reports Reader or Security Reader (Global Reader for sign-in logs only) | Avoid Global Administrator for collection |
| Inbox rules, mailbox permissions, audit status | View-Only Recipients, View-Only Configuration, View-Only Audit Logs | Read-only roles exist for all of these |
| MFA status, Conditional Access, risky users | UserAuthenticationMethod.Read.All, Policy.Read.All, IdentityRiskEvent.Read.All | Graph delegated or application |
| Email and attachment retrieval | Mail.ReadWrite as an application permission | The highest-impact grant: every mailbox in the tenant |
- Use a dedicated identity per case: a named investigator account with the read-only roles above, and a separate app registration only where application permissions are needed.
- Use certificate credentials: the project's example uses a client secret; a certificate held on the collection workstation is the stronger choice and avoids a reusable secret in scripts or history.
- Restrict mailbox reach: where only a few mailboxes are in scope, limit the app with Exchange Online application access policies or RBAC for Applications rather than leaving tenant-wide mail access.
- Time-box it: record when the app was created, by whom and for which case; remove the credential and the app, or the role assignments, when collection closes, and record that removal as an evidence-log entry.
- Keep the collection workstation separate: the documentation asks for an Unrestricted execution policy for the current user, so run it on a dedicated, patched forensic workstation rather than an administrator's daily machine.
For governing app registrations and consent, see the Entra ID Applications and Consent Guide.
A defensible collection procedure
- Confirm the logging state: check that unified audit log ingestion is on, export mailbox audit status (Get-MailboxAuditStatus), and run Get-LicenseCompatibility so you know which users have Premium retention and events. Record any gaps now, because they bound every later conclusion.
- Set the scope in writing: users, mailboxes, apps, IP ranges and a UTC time window that starts before the earliest suspected activity. Record it in the case log with the reason for each boundary.
- Collect short-lived sources first: Entra sign-in and audit logs, then current-state configuration, then message trace in 10-day windows, then the unified audit log.
- Collect the unified audit log twice for key users: once with Get-UAL (PowerShell) and once with Get-UALGraph or a Purview export for the same window. Different back ends that agree on counts give you a completeness check.
- Pivot on sessions: use Get-Sessions and Get-MessageIDs on MailItemsAccessed records to identify which messages the attacker session accessed, then retrieve only those messages where the case requires content.
- Hash and store: compute SHA-256 for every output file and the tool's own log, write them to a manifest, and copy everything to write-once or access-controlled case storage. Keep raw output untouched and work on copies.
- Close down: remove the collector's credentials and roles, then write a collection summary listing sources, windows, counts, tool versions, gaps and errors.
Chain of custody practice is covered in the Evidence Handling and Chain of Custody Guide, with a ready form in the Chain of Custody Form Template.
Choosing a method
- Small, scoped case (a few mailboxes, recent activity): Start-MESTriage with the Standard template filtered by user, plus a Purview export as a cross-check, is usually enough.
- Large tenant, long window: Get-UAL by record type or group, run per week or per day, in parallel sessions under different accounts if throttling bites; or Graph audit queries, which do the search server side. Expect hours to days.
- Tenant with an existing SIEM feed: query the SIEM first, then use the Extractor Suite to fill gaps and to prove the feed was complete for the case window. A SIEM that dropped events will not tell you so.
- Retained third-party responder: agree in advance whether they use their own collector app or yours, who approves the consent, and how output is handed back. See the Incident Response Retainer Buyer's Guide.
- Small organisations: on Business-tier tenants check that auditing is on before an incident; turning it on afterwards recovers nothing. Retention is 180 days for the unified audit log but only 7 or 30 days for Entra logs, which is often the decisive gap.
Measuring completeness and readiness
- Count reconciliation: difference between record counts from two methods for the same window and filter. Target zero, and explain any difference in the report.
- Cap hits: number of windows that returned exactly 5,000 or 50,000 records. Any such window is suspect until re-split.
- Time to first evidence: hours from declaration to having Entra sign-in logs for the scoped users on case storage. Short-retention sources set the target.
- Coverage at incident time: share of in-scope mailboxes with default audit settings intact and share of users with Premium retention, from the logging-state step.
- Collector hygiene: collector apps and credentials still present after case closure. Target zero.
Framework mapping
| Framework | Reference | How collection supports it |
|---|---|---|
| NIST CSF 2.0 | RS.AN-03, RS.AN-06, RS.AN-07 | Analysis of what happened; recording investigative actions; preserving data integrity and provenance |
| NIST SP 800-53 Rev 5 | AU-6, AU-9, AU-11, IR-4 | Audit review, protection of audit information, retention, incident handling |
| CIS Controls v8.1 | Safeguard 8.10, Control 17 | Retaining audit logs; incident response management |
| ISO/IEC 27001:2022 Annex A | A.5.28, A.8.15 | Collection of evidence; logging |
| MITRE ATT&CK v20 | T1114.002, T1564.008, T1078.004, T1098.001 | Remote email collection, email hiding rules, cloud accounts and additional cloud credentials: the behaviours the collected records are used to reconstruct |
Common failure patterns
- Exports that stop at a cap: a single large search returns exactly 5,000 or 50,000 rows and the team analyses it as complete. Always check counts against the limits and split.
- Starting with the unified audit log: days are spent pulling 180 days of audit data while the 7-day Entra sign-in logs expire. Collect by retention, shortest first.
- Containment before capture: malicious inbox rules, forwarding and consent grants are deleted before being exported, leaving only the audit record, or nothing if it aged out.
- Local-time drift: start and end dates typed in local time are converted silently, shifting the window by hours; mixing UTC output with local-time notes then misorders the timeline.
- A collector left behind: an app with Mail.ReadWrite and a client secret survives the case and becomes a standing route into every mailbox. The Microsoft 365 Tenant Compromise Playbook treats unexplained app credentials as a persistence finding; apply the same standard to your own.
- Assuming Premium events exist: the team searches for events that the affected user's licence never recorded, and reports absence of evidence as evidence of absence.
Practitioner checklist
- Before any incident, confirm unified audit log ingestion, mailbox audit defaults and Entra log export to long-term storage.
- Pre-stage a collection workstation with the Extractor Suite, ExchangeOnlineManagement, Microsoft.Graph and Az modules, and test it in a lab tenant.
- Pre-define read-only investigator role assignments and a documented process for a time-boxed collector app with certificate credentials.
- On declaration, collect Entra sign-in and audit logs and current-state configuration within the first hours.
- Pass every date as ISO 8601 UTC and record the exact command, filter and window for each export.
- Reconcile record counts between two methods for key users, and re-split any window that hit 5,000 or 50,000.
- Hash all output and the tool logs, store them in access-controlled case storage, and work only on copies.
- Remove collector apps, credentials and temporary roles at closure and document the removal.
Standards and references
- Microsoft Learn: Search the audit log
- Microsoft Learn: Search-UnifiedAuditLog
- Microsoft Learn: Create auditLogQuery
- Microsoft Learn: Microsoft Entra data retention
- Microsoft Learn: Get-MessageTraceV2
- Microsoft Learn: Microsoft Graph activity logs
- Invictus IR: Microsoft-Extractor-Suite repository
- Invictus IR: Microsoft-Extractor-Suite documentation
Related NHI Mgmt Group resources: Microsoft 365 Unified Audit Log Guide for Detection · Business Email Compromise Response Playbook · Entra ID Sign-In and Audit Log Detection Guide · Cloud Forensics Guide · Forensic Readiness Guide