Skip to content

Preserving Microsoft 365 Evidence Under Emergency Conditions

When responding to a confirmed or suspected compromise in a Microsoft 365 tenant, the incident responder’s most relentless adversary is not just the threat actor—it is the log retention countdown clock. Unlike on-premises systems where disk images and event logs can be frozen indefinitely, cloud telemetry in Microsoft 365 continuously ages out and is irrevocably destroyed by automated platform retention policies.

A delay of hours or days during initial scoping can permanently destroy interactive sign-in records, real-time message delivery traces, and cloud application activity. This guide defines an authoritative, chronological protocol for emergency evidence preservation across Microsoft 365 and Entra ID.


Evidence preservation in Microsoft 365 comprises two parallel tracks:

  1. Log Plane Preservation (Ephemeral Telemetry): Extracting and archiving rolling operational logs (Entra ID sign-ins, audit logs, Exchange Message Trace, Defender raw telemetry) before they reach their native retention expiration.
  2. Data Plane Preservation (Content Immutability): Placing legal and forensic holds (Litigation Hold, Purview eDiscovery Hold) on mailboxes, OneDrive document libraries, and SharePoint sites to prevent attackers or unknowing users from permanently purging emails, files, and chat messages.

Understanding the distinction between log retention and content retention is foundational: Litigation Hold does not preserve audit logs, and exporting audit logs does not freeze mailbox contents. Both must be executed in parallel.


Chronological Order of Preservation: Expiration Velocity

Section titled “Chronological Order of Preservation: Expiration Velocity”

Preservation actions must be prioritized strictly according to expiration velocity—the rate at which unarchived evidence disappears forever:

graph TD
subgraph "Preservation Velocity Hierarchy"
T1["Tier 1: Ultra-Critical (0-7 Days Expiration)<br/>- Entra Free Sign-in & Audit Logs (7d)<br/>- Real-time Message Trace (10d)<br/>- Ephemeral MFA Fraud/Risk Context"]
T2["Tier 2: High Priority (10-30 Days Expiration)<br/>- Entra P1/P2 Sign-in & Audit Logs (30d)<br/>- Defender XDR Raw Advanced Hunting (30d)<br/>- Identity Protection Risk Detections"]
T3["Tier 3: Compliance & Content (30-180 Days)<br/>- Purview Unified Audit Log (180d standard)<br/>- Exchange Historical Message Trace (90d)<br/>- Mailbox & OneDrive Content Deletion Loops"]
end
T1 -->|Preserve within Hour 1-4| T2
T2 -->|Preserve within Hour 4-12| T3
Expiration WindowData StreamImpact of DelayImmediate Action
7 DaysEntra ID Free Sign-in Logs & Directory AuditsCritical authentication records and IP addresses permanently erased.Immediate Graph API bulk export via PowerShell script.
10 DaysReal-time Exchange Message Trace (Get-MessageTrace)Granular per-hop message delivery metadata transitions to slow historical search.Query message trace for all suspicious outbound/inbound spikes.
14 to 30 DaysDeleted Item Recovery & Purges folder (Mailbox)Attacker-deleted emails permanently purged from Recoverable Items\Purges.Place Litigation Hold on target mailboxes immediately.
30 DaysEntra ID P1/P2 Sign-ins & Defender XDR TelemetryAdvanced Hunting raw tables (CloudAppEvents, DeviceInfo) truncated to 30 days.Export KQL hunting datasets and trigger Diagnostic Settings streaming.
90 DaysHistorical Exchange Message TraceTransport logs older than 90 days cannot be retrieved under any circumstances.Submit Start-HistoricalSearch for broader scope.
180 DaysPurview Unified Audit Log (Audit Standard)General tenant-wide user and admin operational events purged.Export UAL via Search-UnifiedAuditLog or Office 365 Management API.

Step 1: Placing Immediate Content Holds (Litigation Hold)

Section titled “Step 1: Placing Immediate Content Holds (Litigation Hold)”

If an attacker has accessed a mailbox, they will frequently create rules to delete incoming security warnings or manually wipe sent malicious messages. Enabling Litigation Hold immediately freezes the mailbox: even if the user or attacker runs “Empty Deleted Items” or purges items via shift-delete, Exchange Online silently routes the data to the hidden Recoverable Items\Purges and DiscoveryHolds folders where it cannot be deleted.

Terminal window
# Connect to Exchange Online
Connect-ExchangeOnline -ShowBanner:$false
$TargetMailbox = "victim@domain.com"
$IncidentId = "INC-2026-8941"
# 1. Inspect current hold status
Get-Mailbox -Identity $TargetMailbox | Select-Object DisplayName, LitigationHoldEnabled, LitigationHoldDate, RetentionHoldEnabled
# 2. Place urgent Litigation Hold (Indefinite retention)
Set-Mailbox -Identity $TargetMailbox `
-LitigationHoldEnabled $true `
-LitigationHoldDuration Unlimited `
-LitigationHoldOwner "DFIR-$IncidentId" `
-RetentionUrl "https://incident-management.internal/cases/$IncidentId"
Write-Host "[+] Litigation Hold successfully applied to $TargetMailbox" -ForegroundColor Green
# 3. Verify hold status
Get-Mailbox -Identity $TargetMailbox | Select-Object LitigationHoldEnabled, LitigationHoldDate, LitigationHoldOwner

[!WARNING] Litigation Hold Propagation Delay: While the cmdlet takes effect immediately in directory metadata, client-side indexing and Exchange store synchronization can take up to 60 minutes to fully lock background purges. Do not delay placing holds.


Step 2: Emergency Extraction of Entra ID Sign-in & Audit Logs

Section titled “Step 2: Emergency Extraction of Entra ID Sign-in & Audit Logs”

Because Entra Free tenants retain logs for only 7 days and P1/P2 retain only 30 days, dumping authentication telemetry is the highest-priority extraction task.

Terminal window
# ==============================================================================
# Hermes Codex - Emergency Entra ID Sign-in Log Extractor (Microsoft.Graph)
# Exports interactive, non-interactive, and service principal sign-ins with pagination
# ==============================================================================
Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Reports -ErrorAction Stop
# Connect with necessary audit scopes
Connect-MgGraph -Scopes "AuditLog.Read.All", "Directory.Read.All" -NoWelcome
$ExportDir = "./ForensicArtifacts_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
New-Item -ItemType Directory -Path $ExportDir -Force | Out-Null
$DaysToExtract = 30
$StartDate = (Get-Date).AddDays(-$DaysToExtract).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$OutputFile = "$ExportDir/Entra_SignIns_Last30Days.jsonl"
Write-Host "[*] Extracting Entra Sign-Ins since $StartDate into $OutputFile..." -ForegroundColor Cyan
# Use Graph API with server-side filter and nextLink pagination
$Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=createdDateTime ge $StartDate&`$top=1000"
$Count = 0
do {
try {
$response = Invoke-MgGraphRequest -Method GET -Uri $Uri
$records = $response.value
foreach ($record in $records) {
$jsonLine = $record | ConvertTo-Json -Compress -Depth 10
Add-Content -Path $OutputFile -Value $jsonLine
$Count++
}
Write-Host "[+] Retrieved $Count records..." -ForegroundColor Gray
$Uri = $response.'@odata.nextLink'
# Throttling defense: brief pause between pages
Start-Sleep -Milliseconds 200
} catch {
if ($_.Exception.Response.StatusCode -eq 429) {
$retryAfter = $_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds
if (-not $retryAfter) { $retryAfter = 10 }
Write-Warning "[!] Throttled (HTTP 429). Backing off for $retryAfter seconds..."
Start-Sleep -Seconds $retryAfter
} else {
Write-Error "[-] Fatal Graph API error: $($_.Exception.Message)"
break
}
}
} while ($null -ne $Uri)
# Compute SHA-256 hash for chain of custody
$hash = Get-FileHash -Path $OutputFile -Algorithm SHA256
$hash | Export-Clixml -Path "$ExportDir/Entra_SignIns_Last30Days.hash.xml"
Write-Host "[✓] Sign-in extraction complete: $Count records. SHA-256: $($hash.Hash)" -ForegroundColor Green

Step 3: Emergency Extraction of the Unified Audit Log (UAL)

Section titled “Step 3: Emergency Extraction of the Unified Audit Log (UAL)”

The Purview Unified Audit Log stores records across Exchange, SharePoint, OneDrive, Teams, and Entra ID. Extracting UAL via PowerShell must account for large result sizes, API timeouts, and chunking by time window.

Terminal window
# ==============================================================================
# Hermes Codex - Chunked UAL Forensic Extractor
# Splits queries into 6-hour windows to avoid result truncation and memory exhaustion
# ==============================================================================
Import-Module ExchangeOnlineManagement -ErrorAction Stop
Connect-ExchangeOnline -ShowBanner:$false
$TargetUser = "victim@domain.com"
$StartDate = (Get-Date).AddDays(-15)
$EndDate = (Get-Date)
$OutputDir = "./UAL_Extract_$(Get-Date -Format 'yyyyMMdd')"
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
$CurrentStart = $StartDate
$IntervalHours = 6
while ($CurrentStart -lt $EndDate) {
$CurrentEnd = $CurrentStart.AddHours($IntervalHours)
if ($CurrentEnd -gt $EndDate) { $CurrentEnd = $EndDate }
$TimeStr = "$($CurrentStart.ToString('yyyyMMdd_HHmm'))_to_$($CurrentEnd.ToString('yyyyMMdd_HHmm'))"
$BatchFile = "$OutputDir/UAL_$TimeStr.json"
Write-Host "[*] Querying UAL window: $CurrentStart to $CurrentEnd..." -ForegroundColor Cyan
try {
$events = Search-UnifiedAuditLog `
-StartDate $CurrentStart `
-EndDate $CurrentEnd `
-FreeText $TargetUser `
-ResultSize 5000 `
-ErrorAction Stop
if ($events.Count -gt 0) {
# Convert AuditData JSON string into structured object
$structuredEvents = $events | ForEach-Object {
$rawAuditData = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
CreationDate = $_.CreationDate
RecordType = $_.RecordType
Operations = $_.Operations
UserIds = $_.UserIds
ResultStatus = $_.ResultStatus
AuditData = $rawAuditData
}
}
$structuredEvents | ConvertTo-Json -Depth 10 | Set-Content -Path $BatchFile
Write-Host " [+] Saved $($events.Count) events to $BatchFile" -ForegroundColor Green
} else {
Write-Host " [-] 0 events found in this slice." -ForegroundColor Gray
}
} catch {
Write-Error " [!] Error during UAL slice: $($_.Exception.Message)"
}
$CurrentStart = $CurrentEnd
Start-Sleep -Seconds 2 # Polite delay to avoid Exchange runspace throttling
}

Step 4: Configuring Continuous SIEM Export (Preventing Future Expiration)

Section titled “Step 4: Configuring Continuous SIEM Export (Preventing Future Expiration)”

Extracting point-in-time logs preserves history, but if the investigation lasts several weeks, newly generated logs will continue to age out. Setting up Diagnostic Settings in Entra ID routes all future sign-ins, directory audits, and risk telemetry directly to an Azure Log Analytics Workspace or Event Hub.

Terminal window
# ==============================================================================
# Configure Entra ID Diagnostic Settings to Azure Log Analytics
# Requires Global Administrator or Security Administrator + Azure Contributor
# ==============================================================================
# 1. Target Log Analytics Workspace Resource ID
$WorkspaceId = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-soc/providers/Microsoft.OperationalInsights/workspaces/law-incident-retention"
# 2. Define Diagnostic Setting JSON payload
$DiagnosticPayload = @{
name = "DFIR-ContinuousPreservation"
properties = @{
workspaceId = $WorkspaceId
logs = @(
@{ category = "SignInLogs"; enabled = $true },
@{ category = "NonInteractiveUserSignInLogs"; enabled = $true },
@{ category = "ServicePrincipalSignInLogs"; enabled = $true },
@{ category = "ManagedIdentitySignInLogs"; enabled = $true },
@{ category = "AuditLogs"; enabled = $true },
@{ category = "ProvisioningLogs"; enabled = $true },
@{ category = "RiskyUsers"; enabled = $true },
@{ category = "UserRiskEvents"; enabled = $true }
)
}
} | ConvertTo-Json -Depth 5
# 3. Push to Microsoft Graph Diagnostic Settings API
$Uri = "https://graph.microsoft.com/v1.0/reports/diagnosticSettings"
Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $DiagnosticPayload -ContentType "application/json"
Write-Host "[+] Continuous Diagnostic Streaming successfully established!" -ForegroundColor Green

Step 5: Handling Throttling (HTTP 429) and API Quotas

Section titled “Step 5: Handling Throttling (HTTP 429) and API Quotas”

During large-scale forensic acquisitions, responders routinely trigger cloud platform throttling limits:

  • Error Code: 429 Too Many Requests.
  • Key Header: Retry-After (specifies the number of seconds the client must pause before retrying).
  • Concurrency Quota: Maximum of 4 concurrent requests per tenant for large audit log queries.
  • Handling Strategy: Always implement exponential backoff with jitter: WaitTime = max(Retry-After, 2^retryCount + random(0, 1))

Exchange Online PowerShell Throttling Rules

Section titled “Exchange Online PowerShell Throttling Rules”
  • Runspace Limit: Maximum 3 concurrent active sessions per admin account.
  • Micro-Delays: Add Start-Sleep -Milliseconds 500 between consecutive calls to Search-UnifiedAuditLog or Get-MessageTrace.
  • ResultSize Limits: Search-UnifiedAuditLog returns a maximum of 5,000 records per command invocation. Always break queries into time slices (e.g., 6 hours) rather than expanding the search window.

The Transversal Doctrine: Preservation vs Proof

Section titled “The Transversal Doctrine: Preservation vs Proof”

In forensic reporting, preserving a data store does not automatically prove malicious presence. Responders must articulate findings using the Hermes Codex 7-tier scale:

+-------------------------------------------------------------------------------+
| THE 7 LEVELS OF FORENSIC CERTAINTY |
| |
| 1. Possible -> Storage supports retention and holds. |
| 2. Configured -> Litigation Hold active, Diagnostic Settings streaming. |
| 3. Authorized -> Responders hold eDiscovery Manager and Audit Reader. |
| 4. Accessible -> API endpoints responding, credentials valid. |
| 5. Utilized -> Bulk export scripts executed against tenant. |
| 6. Observed -> Log records archived into local JSONL and verified. |
| 7. Proven -> Hash-verified, chain-of-custody sealed forensic proof. |
+-------------------------------------------------------------------------------+

Forensic Distinctions in Evidence Preservation

Section titled “Forensic Distinctions in Evidence Preservation”
  1. Configured != Observed: Placing a Litigation Hold is a configuration action. It ensures future modifications are retained; it does not prove that previously deleted emails were saved if the attacker purged them prior to the hold being applied (outside the 14-day Deleted Items Recovery window).
  2. Observed != Proven: Extracting an authentication record from the Graph API shows a raw observed log line. To elevate this to proven forensic evidence in a court or regulatory disclosure, the raw JSON must be accompanied by an immutable SHA-256 hash, an audit timestamp, the querying account identity, and correlation against downstream UAL activity.

TrapTechnical Root CauseInvestigative ImpactCorrective Action
Assuming Litigation Hold Saves Audit LogsLitigation Hold operates exclusively on the mailbox data plane (Recoverable Items); it has zero effect on UAL or Entra logs.Responders mistakenly believe audit logs will not expire, losing critical evidence at day 31.Always dump Entra ID sign-in logs and UAL to external storage in parallel with content holds.
Monolithic UAL QueriesRunning Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) in a single call.Query hits the 5,000 record ceiling, truncating 80% of activity, or times out.Always slice UAL queries into small time intervals (6h to 24h) and iterate.
Ignoring Non-Interactive Sign-InsExporting only /auditLogs/signIns without realizing that non-interactive tokens, refresh token redemptions, and service principals require separate filtering or Graph endpoints.AiTM session hijacking and automated exfiltration remain completely hidden.Query all sign-in categories: interactive, non-interactive, service principal, and managed identity.
Lack of Chain of Custody HashingDumping JSON/CSV logs onto an investigator laptop without calculating cryptographic checksums.Evidence challenged in legal proceedings or regulatory filings for lack of integrity verification.Compute SHA-256 hashes immediately upon file creation and log extraction metadata.
Aggressive Threading Causing Tenant LockoutRunning multithreaded extraction scripts with 50 workers against Graph API.Tenant receives broad 429 throttling blocks, affecting corporate business operations.Restrict concurrency to 2-4 threads and respect the Retry-After header strictly.

  • Purview Audit Standard Default Retention: Retention for standard UAL audit records is 180 days across all enterprise plans.
  • Graph Purview Audit API (v1.0): Microsoft transitioned programmatic UAL exports from the legacy Office 365 Management Activity API to modern Microsoft Graph Purview Audit APIs.
  • Unified Diagnostic Settings: Diagnostic streaming in Entra ID now supports streaming service principal sign-in logs and managed identity sign-in logs directly to Log Analytics.
  • Exchange Online PowerShell v1 and v2: Retired. All scripted interactions must use ExchangeOnlineManagement v3+ with REST-backed cmdlets.
  • Basic Auth on Admin Endpoints: Basic authentication is completely blocked. Certificate-based service principal authentication is the required standard for automated forensic collection pipelines.
  • UAL Indexing Latency: Even in emergency response, newly generated events can take up to 60 minutes to appear in UAL queries. Do not assume an adversary was inactive in the last hour simply because UAL returns zero records.
  • Non-Retroactive Litigation Hold: Items permanently purged from Recoverable Items\Purges before the hold was applied cannot be recovered by any technical means.

  1. Act according to expiration velocity: Entra Free logs expire in 7 days, Message Trace in 10 days, and Entra P1/P2 logs in 30 days. Prioritize these within the first 4 hours of response.
  2. Litigation Hold freezes mailbox contents, not audit logs: Apply holds immediately to compromised accounts, but extract logs independently.
  3. Chunk UAL extractions: Never attempt single-query bulk dumps of the Unified Audit Log. Slice queries into 6-to-12 hour windows to prevent truncation at 5,000 records.
  4. Establish continuous export early: Route Entra ID and Purview logs to an Azure Log Analytics Workspace via Diagnostic Settings to preserve evidence while the investigation unfolds.
  5. Hash everything immediately: Maintain chain of custody by generating SHA-256 hashes for all exported artifacts immediately upon completion.