In this module

IR1.7 PowerShell for Incident Response

90-120 minutes · Module 1 · Free
Operational Objective
The Universal Tool Problem: every Windows system has PowerShell. Every M365 tenant supports PowerShell management modules. When KAPE is unavailable, Velociraptor is not deployed, and the Defender portal is down for maintenance, PowerShell still works. It is the responder's universal tool — capable of live evidence collection, containment actions, log analysis, and automation. The investigator who masters PowerShell for IR can respond on any Windows system with zero tool deployment.
Deliverable: PowerShell remoting configured for remote evidence collection, core IR cmdlets validated, containment action scripts tested, and a library of IR PowerShell snippets ready for deployment.
⏱ Estimated completion: 15 minutes

PowerShell for Incident Response

The tool that is always available — even when nothing else is

You are responding to an incident on a system where KAPE has not been deployed. Velociraptor is not installed. You have no forensic tools on this endpoint. But you have PowerShell — and with PowerShell, you can collect running processes, network connections, scheduled tasks, service lists, registry values, event log entries, and file metadata. It is not as efficient as purpose-built forensic tools, but it is universally available on every Windows system and it can keep the investigation moving while you deploy the proper toolkit.

PowerShell is not a forensic tool in the same way KAPE and EZTools are forensic tools. It does not parse binary artifacts or build timelines. What PowerShell provides is direct access to the operating system and the M365 management plane — the ability to query, collect, and act on any system the investigator can authenticate to.

# Enable PowerShell remoting on the forensic workstation
# Run as Administrator
Enable-PSRemoting -Force

# Verify WinRM service is running
Get-Service WinRM | Select-Object Status, StartType
# Expected: Running, Automatic

# Test connectivity to a remote system before attempting a session
Test-WSMan -ComputerName "DESKTOP-NGE042"
# If this fails: the target's WinRM is not enabled, the firewall
# blocks port 5985, or the target is offline

# Open a remote session (Kerberos auth in domain environments)
$session = New-PSSession -ComputerName "DESKTOP-NGE042" -Credential (Get-Credential)

# Run commands remotely — the ScriptBlock executes on the target
Invoke-Command -Session $session -ScriptBlock {
    Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
}

# Run commands on MULTIPLE systems simultaneously
$targets = @("DESKTOP-NGE042", "DESKTOP-NGE001", "SERVER-NGE-DC01")
Invoke-Command -ComputerName $targets -Credential $cred -ScriptBlock {
    Get-ScheduledTask | Where-Object { $_.State -eq 'Ready' -and
        $_.Actions.Execute -match 'powershell|cmd|wscript' }
}
# Results include PSComputerName — you can see which system each result came from

# Close the session when done
Remove-PSSession $session
# Configure TrustedHosts for non-domain environments
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.1.50,192.168.1.51" -Force

# Connect using IP address with explicit credentials
$session = New-PSSession -ComputerName "192.168.1.50" -Credential (Get-Credential) -Authentication Negotiate
# Install M365 management modules for IR
# Run as Administrator

# Exchange Online (email forensics — IR9)
Install-Module -Name ExchangeOnlineManagement -Force

# Microsoft Graph (Entra ID investigation — IR8, IR11)
Install-Module -Name Microsoft.Graph -Force

# Azure AD (legacy — some IR scripts still use this)
Install-Module -Name AzureAD -Force

# Verify installations
Get-Module -ListAvailable ExchangeOnlineManagement
Get-Module -ListAvailable Microsoft.Graph
Get-Module -ListAvailable AzureAD

# Connect to Exchange Online (test connection)
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com
# Run a test command
Get-Mailbox | Select-Object -First 5 DisplayName, UserPrincipalName
Disconnect-ExchangeOnline -Confirm:$false
# Microsoft Graph connection with investigation-appropriate scopes
# Each scope grants specific API access — use minimum necessary

# For identity investigation (IR8):
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All", "Directory.Read.All"

# For identity containment (IR8, IR14):
Connect-MgGraph -Scopes "User.ReadWrite.All"

# For OAuth app investigation (IR11):
Connect-MgGraph -Scopes "Application.Read.All", "ServicePrincipalEndpoint.Read.All"

# For comprehensive IR (all of the above):
Connect-MgGraph -Scopes "User.ReadWrite.All", "AuditLog.Read.All", "Directory.Read.All", "Application.Read.All"

# Verify the connection and available scopes
Get-MgContext | Select-Object Scopes, Account, TenantId
# Check installed module versions
Get-Module -ListAvailable ExchangeOnlineManagement, Microsoft.Graph, AzureAD |
    Select-Object Name, Version | Format-Table

# Update modules to latest (run before a major investigation)
Update-Module ExchangeOnlineManagement -Force
Update-Module Microsoft.Graph -Force

# If Update-Module fails (common with Microsoft.Graph due to
# submodule dependencies), uninstall and reinstall:
# Uninstall-Module Microsoft.Graph -AllVersions -Force
# Install-Module Microsoft.Graph -Force
# Collect running processes with command lines and network connections
# Use when KAPE/Velociraptor are unavailable
Get-Process | Select-Object Id, ProcessName, Path, StartTime,
    @{Name='CommandLine'; Expression={(Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)").CommandLine}} |
    Export-Csv "C:\IR\Evidence\processes.csv" -NoTypeInformation

# Collect active network connections with owning process
Get-NetTCPConnection |
    Where-Object { $_.State -eq 'Established' } |
    Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort,
    @{Name='Process'; Expression={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
    Export-Csv "C:\IR\Evidence\connections.csv" -NoTypeInformation
# Revoke all active sessions for a compromised user
# Forces re-authentication — breaks stolen session tokens
Connect-MgGraph -Scopes "User.ReadWrite.All"
Revoke-MgUserSignInSession -UserId "jmorrison@northgateeng.com"

# Disable the account (prevents new sign-ins)
Update-MgUser -UserId "jmorrison@northgateeng.com" -AccountEnabled:$false

# Reset the password (invalidates cached credentials)
$newPassword = @{
    Password = [System.Web.Security.Membership]::GeneratePassword(16, 4)
    ForceChangePasswordNextSignIn = $true
}
Update-MgUser -UserId "jmorrison@northgateeng.com" -PasswordProfile $newPassword
# Remove malicious inbox rules created by the attacker
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com

# List all inbox rules for the compromised user
Get-InboxRule -Mailbox "jmorrison@northgateeng.com" | Format-List Name, Description, Enabled, ForwardTo, RedirectTo, DeleteMessage

# Remove a specific malicious rule (attacker-created forwarding)
Remove-InboxRule -Mailbox "jmorrison@northgateeng.com" -Identity "Update Rule" -Confirm:$false

# Check for mail forwarding (SMTP forwarding, not inbox rules)
Get-Mailbox "jmorrison@northgateeng.com" | Select-Object ForwardingSmtpAddress, ForwardingAddress, DeliverToMailboxAndForward
# Search mailbox audit log for the compromised user
# Identify what the attacker accessed
Search-UnifiedAuditLog -StartDate "2026-03-15" -EndDate "2026-03-16" -UserIds "jmorrison@northgateeng.com" -Operations MailItemsAccessed, Send, MoveToDeletedItems, New-InboxRule -ResultSize 5000 |
    Select-Object CreationDate, UserIds, Operations, AuditData |
    Export-Csv "C:\IR\Evidence\mailbox_audit.csv" -NoTypeInformation
# Comprehensive endpoint evidence collection via PowerShell
# Run as Administrator on the target system (or remotely via Invoke-Command)

$outputDir = "C:\IR\Evidence\PS_Collection_$(Get-Date -Format 'yyyyMMdd_HHmm')"
New-Item -ItemType Directory -Path $outputDir -Force

# Scheduled tasks — persistence mechanism (T1053.005)
Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' } |
    Select-Object TaskName, TaskPath, State,
    @{Name='Actions'; Expression={($_.Actions | ForEach-Object { $_.Execute + " " + $_.Arguments }) -join "; "}},
    @{Name='Triggers'; Expression={($_.Triggers | ForEach-Object { $_.GetType().Name }) -join "; "}} |
    Export-Csv "$outputDir\scheduled_tasks.csv" -NoTypeInformation

# Services — persistence and lateral movement artifacts
Get-CimInstance Win32_Service |
    Select-Object Name, DisplayName, State, StartMode, PathName, StartName |
    Export-Csv "$outputDir\services.csv" -NoTypeInformation

# Startup programs — persistence via Run keys and Startup folders
Get-CimInstance Win32_StartupCommand |
    Select-Object Name, Command, Location, User |
    Export-Csv "$outputDir\startup_programs.csv" -NoTypeInformation

# Local user accounts — look for attacker-created accounts
Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordLastSet,
    Description, SID |
    Export-Csv "$outputDir\local_users.csv" -NoTypeInformation

# Local group membership — check Administrators group for unauthorized members
Get-LocalGroupMember -Group "Administrators" |
    Select-Object Name, ObjectClass, PrincipalSource |
    Export-Csv "$outputDir\local_admins.csv" -NoTypeInformation

# DNS cache — reveals recently resolved domains (C2 indicators)
Get-DnsClientCache |
    Select-Object Entry, RecordName, RecordType, Data, TimeToLive |
    Export-Csv "$outputDir\dns_cache.csv" -NoTypeInformation

# Recent event log entries — last 24 hours of Security log
Get-WinEvent -FilterHashtable @{LogName='Security'; StartTime=(Get-Date).AddHours(-24)} -MaxEvents 10000 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Export-Csv "$outputDir\security_events_24h.csv" -NoTypeInformation

# PowerShell ScriptBlock log — what PowerShell commands were executed
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} -MaxEvents 500 -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, @{Name='ScriptBlock'; Expression={$_.Properties[2].Value}} |
    Export-Csv "$outputDir\powershell_scriptblocks.csv" -NoTypeInformation

Write-Host "Collection complete: $outputDir" -ForegroundColor Green
Get-ChildItem $outputDir | ForEach-Object { Write-Host "  $($_.Name) — $([math]::Round($_.Length/1KB, 1)) KB" }
# Batch evidence collection across multiple endpoints
# Use when scoping an incident across the organization

$targetComputers = @(
    "DESKTOP-NGE001",
    "DESKTOP-NGE042",
    "LAPTOP-NGE015",
    "SERVER-NGE-DC01"
)

$credential = Get-Credential -Message "Enter domain admin credentials for remote collection"
$iocTaskName = "ChromeUpdate"  # Suspicious scheduled task from initial investigation

$results = Invoke-Command -ComputerName $targetComputers -Credential $credential -ScriptBlock {
    param($taskName)
    $task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
    [PSCustomObject]@{
        ComputerName = $env:COMPUTERNAME
        TaskFound    = $null -ne $task
        TaskState    = if ($task) { $task.State } else { "N/A" }
        TaskAction   = if ($task) { ($task.Actions | ForEach-Object { $_.Execute }) -join "; " } else { "N/A" }
        LastRunTime  = if ($task) { (Get-ScheduledTaskInfo -TaskName $taskName -ErrorAction SilentlyContinue).LastRunTime } else { "N/A" }
    }
} -ArgumentList $iocTaskName -ErrorAction SilentlyContinue

$results | Format-Table -AutoSize
$results | Export-Csv "C:\IR\Evidence\scope_check_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

# Report: which endpoints have the indicator?
$affected = ($results | Where-Object { $_.TaskFound -eq $true }).Count
Write-Host "`n$affected of $($targetComputers.Count) endpoints have the '$iocTaskName' scheduled task" -ForegroundColor $(if ($affected -gt 0) { 'Red' } else { 'Green' })
# Entra ID investigation: check for attacker-created persistence
Connect-MgGraph -Scopes "Application.Read.All", "AuditLog.Read.All"

# Check for recently registered applications (attacker may register
# a malicious app for persistent OAuth access)
Get-MgApplication -Filter "createdDateTime ge 2026-03-14T00:00:00Z" |
    Select-Object DisplayName, AppId, CreatedDateTime,
    @{Name='Owners'; Expression={(Get-MgApplicationOwner -ApplicationId $_.Id).AdditionalProperties.userPrincipalName -join "; "}} |
    Format-Table -AutoSize

# Check for service principal credentials (attacker may add a
# secret to an existing service principal for persistence)
Get-MgServicePrincipal -All | ForEach-Object {
    $sp = $_
    $creds = Get-MgServicePrincipal -ServicePrincipalId $sp.Id -Property "passwordCredentials,keyCredentials"
    if ($creds.PasswordCredentials -or $creds.KeyCredentials) {
        [PSCustomObject]@{
            DisplayName = $sp.DisplayName
            AppId = $sp.AppId
            PasswordCredentials = ($creds.PasswordCredentials | Measure-Object).Count
            KeyCredentials = ($creds.KeyCredentials | Measure-Object).Count
            LatestCredential = ($creds.PasswordCredentials | Sort-Object StartDateTime -Descending | Select-Object -First 1).StartDateTime
        }
    }
} | Where-Object { $_.LatestCredential -gt (Get-Date).AddDays(-7) } |
    Format-Table -AutoSize

# Check for suspicious MFA method additions
# Attacker may add their own phone number or authenticator app
Get-MgUserAuthenticationMethod -UserId "jmorrison@northgateeng.com" |
    Select-Object Id, @{Name='Type'; Expression={$_.AdditionalProperties.'@odata.type'}}
# Isolate a compromised device via Defender for Endpoint API
# This blocks all network connections except to the Defender service
# The device can still be managed and investigated remotely

$deviceId = "abc123def456"  # Device ID from Defender portal
$body = @{
    Comment = "INC-NE-2026-0315-001: Isolating compromised workstation for investigation"
    IsolationType = "Full"  # Full = block all traffic. Selective = allow Outlook/Teams
} | ConvertTo-Json

Invoke-MgGraphRequest -Method POST -Uri "https://api.securitycenter.microsoft.com/api/machines/$deviceId/isolate" -Body $body -ContentType "application/json"
# IR Script: Complete Identity Containment
# Save as: C:\IR\Tools\Scripts\Contain-Identity.ps1
# Usage: .\Contain-Identity.ps1 -UserPrincipalName "jmorrison@northgateeng.com" -CaseID "INC-NE-2026-0315-001"
#
# This script performs the complete identity containment sequence
# for a compromised M365 account in the correct order:

param(
    [Parameter(Mandatory)][string]$UserPrincipalName,
    [Parameter(Mandatory)][string]$CaseID
)

$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC" -AsUTC
$log = @("=== Identity Containment Log ===", "Case: $CaseID", "Target: $UserPrincipalName", "Started: $timestamp", "")

Write-Host "=== Containing $UserPrincipalName ===" -ForegroundColor Red

# Step 1: Revoke all active sessions (breaks stolen tokens immediately)
Connect-MgGraph -Scopes "User.ReadWrite.All" -NoWelcome
Revoke-MgUserSignInSession -UserId $UserPrincipalName
$log += "$(Get-Date -Format HH:mm:ss) — Sessions revoked"
Write-Host "  [1/5] Sessions revoked" -ForegroundColor Yellow

# Step 2: Disable the account (prevents new sign-ins)
Update-MgUser -UserId $UserPrincipalName -AccountEnabled:$false
$log += "$(Get-Date -Format HH:mm:ss) — Account disabled"
Write-Host "  [2/5] Account disabled" -ForegroundColor Yellow

# Step 3: Reset password (invalidates cached credentials)
$newPw = -join ((65..90) + (97..122) + (48..57) + (33..38) | Get-Random -Count 20 | ForEach-Object { [char]$_ })
$pwProfile = @{ Password = $newPw; ForceChangePasswordNextSignIn = $true }
Update-MgUser -UserId $UserPrincipalName -PasswordProfile $pwProfile
$log += "$(Get-Date -Format HH:mm:ss) — Password reset (new pw in secure log)"
Write-Host "  [3/5] Password reset" -ForegroundColor Yellow

# Step 4: Check and remove suspicious inbox rules
Connect-ExchangeOnline -UserPrincipalName admin@yourtenant.onmicrosoft.com -ShowBanner:$false
$rules = Get-InboxRule -Mailbox $UserPrincipalName
$suspiciousRules = $rules | Where-Object {
    $_.ForwardTo -or $_.RedirectTo -or $_.ForwardAsAttachmentTo -or $_.DeleteMessage -eq $true
}
foreach ($rule in $suspiciousRules) {
    Remove-InboxRule -Mailbox $UserPrincipalName -Identity $rule.Identity -Confirm:$false
    $log += "$(Get-Date -Format HH:mm:ss) — Removed inbox rule: $($rule.Name) (Forward: $($rule.ForwardTo))"
    Write-Host "  [4/5] Removed suspicious rule: $($rule.Name)" -ForegroundColor Yellow
}
if (-not $suspiciousRules) {
    $log += "$(Get-Date -Format HH:mm:ss) — No suspicious inbox rules found"
    Write-Host "  [4/5] No suspicious inbox rules found" -ForegroundColor Green
}

# Step 5: Check for SMTP forwarding
$mbx = Get-Mailbox $UserPrincipalName
if ($mbx.ForwardingSmtpAddress) {
    Set-Mailbox $UserPrincipalName -ForwardingSmtpAddress $null -ForwardingAddress $null
    $log += "$(Get-Date -Format HH:mm:ss) — Removed SMTP forwarding: $($mbx.ForwardingSmtpAddress)"
    Write-Host "  [5/5] Removed SMTP forwarding" -ForegroundColor Yellow
} else {
    $log += "$(Get-Date -Format HH:mm:ss) — No SMTP forwarding configured"
    Write-Host "  [5/5] No SMTP forwarding found" -ForegroundColor Green
}

Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

# Write containment log
$log += "", "Completed: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC' -AsUTC)"
$logPath = "C:\IR\Cases\$CaseID\Notes\containment_$($UserPrincipalName.Replace('@','_')).txt"
$log | Out-File $logPath -Force
Write-Host "`nContainment complete. Log: $logPath" -ForegroundColor Green
Expand for Deeper Context

In the IR context, PowerShell serves three roles:

Live evidence collection. When KAPE is not available on the target system, PowerShell can collect the same artifacts — event logs, running processes, network connections, scheduled tasks, service configurations, registry keys — through native cmdlets. The collection is less structured than KAPE output, but it provides immediate evidence without deploying any tool.

Containment actions. Disabling a compromised user account, revoking active sessions, blocking an IP address, isolating a device, removing a malicious inbox rule — these containment actions are executed through PowerShell cmdlets and Microsoft Graph API calls. The investigator who can execute containment in PowerShell does not need to navigate three different admin portals to contain a single incident.

Automation. Repetitive investigation tasks — checking 50 mailboxes for a specific inbox rule, querying sign-in logs for 20 compromised accounts, collecting Prefetch files from 10 endpoints — can be scripted in PowerShell and executed in minutes instead of hours.

---

PowerShell remoting setup

PowerShell remoting enables command execution on remote systems. This is how you collect evidence from an endpoint across the network without physically accessing it (when Velociraptor is not deployed). Understanding remoting is critical because it underpins multiple IR workflows: remote KAPE deployment (IR1.2), remote evidence collection when dedicated tools are unavailable, batch containment across multiple accounts, and automated scope determination across the fleet.

PowerShell remoting uses the WinRM (Windows Remote Management) protocol, which communicates over HTTP (port 5985) or HTTPS (port 5986). In Active Directory domain environments, WinRM is typically enabled by default or through Group Policy. In standalone environments, it must be enabled manually on the target. The connection uses Kerberos authentication in domain environments or NTLM with CredSSP in workgroup environments.

WinRM in non-domain environments. For standalone systems or workgroup environments, additional configuration is required. The target must be added to the TrustedHosts list on the forensic workstation, and the connection uses NTLM instead of Kerberos:

For M365 investigation, install the required PowerShell modules:

Understanding the M365 PowerShell modules and when each is used:

ExchangeOnlineManagement (IR9, IR14) — provides cmdlets for mailbox investigation and containment. Search-UnifiedAuditLog searches the Purview audit trail. Get-InboxRule lists inbox rules (attacker-created forwarding). Get-Mailbox retrieves mailbox configuration (forwarding addresses, delegation, audit status). Remove-InboxRule removes attacker-created rules. Set-Mailbox modifies mailbox configuration (disable forwarding, enable enhanced auditing). This module is used in every BEC, AiTM, and email compromise investigation. Install it and test the connection before you need it — ExchangeOnlineManagement module updates occasionally break authentication, and discovering this during an active investigation wastes critical time.

Microsoft.Graph (IR8, IR11) — provides cmdlets for Entra ID investigation and identity containment. Revoke-MgUserSignInSession breaks stolen session tokens (the single most important containment action for AiTM attacks). Update-MgUser disables accounts and resets passwords. Get-MgUser retrieves user profile and authentication methods. Get-MgApplication and Get-MgServicePrincipal investigate OAuth app registrations and service principals — critical for detecting consent phishing persistence (T1098.003) and service principal abuse (T1078.004). Microsoft.Graph is the successor to the legacy AzureAD module and provides access to the full Microsoft Graph API surface. Connection requires scopes that determine which API operations are permitted — specify the minimum scopes needed for each investigation task.

AzureAD module (legacy). The AzureAD module is deprecated in favor of Microsoft.Graph. However, many existing IR scripts and community playbooks still reference AzureAD cmdlets (Get-AzureADUser, Set-AzureADUser, Get-AzureADServicePrincipal). Install it for backward compatibility with existing scripts. New investigation scripts should use Microsoft.Graph exclusively. The cmdlet names map predictably: Get-AzureADUserGet-MgUser, Set-AzureADUserUpdate-MgUser, etc.

Module version management. PowerShell modules update frequently, and version mismatches cause authentication failures during investigations. Before each investigation, verify module versions and update if needed:

---

Core IR cmdlets

These PowerShell commands appear throughout the course. Knowing them before starting Phase 2 and Phase 3 saves time during investigation exercises.

Live process collection (endpoint):

Containment actions (identity):

Containment actions (email):

Evidence collection (mailbox audit):

Each of these commands is used in the investigation modules. IR8 uses the identity containment cmdlets. IR9 uses the mailbox forensics cmdlets. IR13-IR16 use all of them in context. Having them tested and validated before starting those modules eliminates setup friction during investigation exercises.

Worked investigation finding — PowerShell containment execution:

Finding: Contain-Identity.ps1 executed against jmorrison@northgateeng.com at 14:52 UTC (23 minutes after initial alert). Containment log records: sessions revoked at 14:52:03, account disabled at 14:52:07, password reset at 14:52:11, 1 suspicious inbox rule removed ("." — forwarding all incoming email to ext.backup@protonmail.com), no SMTP forwarding configured. Total containment time: 8 seconds from script execution to completion.

Proves: The compromised identity was fully contained within 23 minutes of the initial alert. All active sessions were terminated (breaking the attacker's stolen token). The account was disabled (preventing re-authentication). A malicious inbox rule was identified and removed (eliminating persistent email forwarding to an external address). Does not prove: Whether the attacker established other persistence mechanisms (service principal credentials, OAuth app grants, conditional access policy modifications) that survive the identity containment sequence. Next step: Check for service principal credentials added by the attacker (Get-MgServicePrincipal credential audit). Check for OAuth app consent grants (Get-MgOauth2PermissionGrant). Check for conditional access policy modifications in the Entra ID audit log. These persistence mechanisms survive password resets and session revocations.

---

Endpoint evidence collection (when KAPE is unavailable)

In situations where KAPE cannot be deployed — restricted endpoints, systems without USB access, environments where running third-party tools requires approval — PowerShell collects the same evidence natively. The collection is less structured than KAPE output but provides the critical investigation artifacts.

This script collects the same categories of evidence that KAPE's !SANS_Triage target collects — scheduled tasks, services, startup items, local accounts, DNS cache, event logs, and PowerShell history — but through native cmdlets rather than file-level collection. The output is immediately analysis-ready CSV files that open in Timeline Explorer or Excel.

The key difference from KAPE: PowerShell collects live system state (running services, active DNS cache, current scheduled task definitions), while KAPE collects the underlying files (registry hives, Prefetch binaries, $MFT). Both produce valuable evidence. In an ideal investigation, you use both — KAPE for the forensic artifacts and PowerShell for the live state that may differ from what the files show (a scheduled task that was just created may not yet be reflected in all file-level artifacts).

---

Automation: batch investigation across multiple targets

When an investigation reveals that multiple systems may be affected, PowerShell automation scales the evidence collection across all targets simultaneously.

This script runs a single investigation query (checking for a specific scheduled task) across four endpoints simultaneously. The entire operation completes in seconds. Without automation, the analyst would need to log into each system individually, run the query, record the result, and move to the next — a process that takes 5-10 minutes per system. With 50 endpoints to check, that is 4-8 hours of manual work versus 30 seconds of scripted execution.

Phase 4 scenarios use PowerShell automation extensively for scope determination. When the initial investigation identifies an indicator of compromise, the responder needs to know: how many other systems are affected? PowerShell answers that question at enterprise scale.

---

Entra ID investigation cmdlets

Microsoft Graph PowerShell provides investigation capabilities for identity compromise that are not available in the web portal.

These cmdlets are central to IR11 (Entra ID and Azure AD Investigation). The service principal credential check is particularly important — service principal secrets persist through user password resets, meaning the attacker retains access even after you contain the compromised user account. This is one of the most commonly missed persistence mechanisms in BEC investigations.

---

Device containment via Defender API

For organizations with Defender for Endpoint, PowerShell can trigger device containment actions through the Microsoft 365 Defender API without navigating the portal.

The device isolation API is faster than navigating the Defender portal, especially during a multi-endpoint incident where you need to isolate several machines in rapid succession. The Comment field creates an audit trail — every containment action should reference the case ID for chain of custody.

---

Troubleshooting PowerShell IR setup

"Connect-ExchangeOnline fails with authentication error." Ensure the ExchangeOnlineManagement module is version 3.0+ (run Get-Module ExchangeOnlineManagement -ListAvailable). Older versions use deprecated authentication methods. If using MFA, the module handles modern authentication automatically — do not pass credentials, let it prompt for interactive login.

"Connect-MgGraph scope error." The first time you connect with a new scope, the Graph application requires admin consent. In a Developer Tenant, you are the admin — consent when prompted. In production, the Graph app registration must have the required API permissions assigned and admin-consented.

"Invoke-Command fails with WinRM access denied." PowerShell remoting requires the target machine to have WinRM enabled and the caller to have admin credentials on the target. In domain environments, this works with domain admin credentials. For non-domain machines, the target must be added to the TrustedHosts list: Set-Item WSMan:\localhost\Client\TrustedHosts -Value "TARGET-PC" -Force.

"Get-WinEvent returns no results for PowerShell ScriptBlock logging." ScriptBlock logging (Event ID 4104) must be enabled via Group Policy or registry: HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging\EnableScriptBlockLogging = 1. If this policy is not enabled, no ScriptBlock events are generated — and a critical evidence source is missing. IR5 covers enabling comprehensive PowerShell logging as part of event log analysis.

---

The IR script library

Maintain a library of tested, documented PowerShell scripts for common IR tasks. These scripts live in C:\IR\Tools\Scripts\ on the forensic workstation and in the jump bag. Each script is a self-contained tool for a specific investigation or containment action.

This script executes the complete BEC/AiTM identity containment sequence in the correct order: revoke sessions first (stops active attacker access immediately), then disable the account (prevents re-authentication), then reset the password (invalidates all cached credentials), then remove malicious inbox rules and forwarding (eliminates persistent email access). Each step is logged with a timestamp and the case ID for chain of custody. The script is used in IR8 (Identity Compromise), IR13 (Ransomware — if credential theft is involved), and IR14 (BEC).

---

PowerShell vs dedicated tools: when to use each

PowerShell is the universal fallback — it works when KAPE is unavailable, when Velociraptor is not deployed, when the Defender portal is down, and when you need to execute containment actions in seconds rather than minutes. But it is not always the best choice for evidence collection.

Evidence collection: KAPE produces structured output organized by artifact category, preserves file metadata and timestamps, handles locked files via raw disk access, and integrates with EZTools for automated parsing. PowerShell native collection (Get-Process, Get-ScheduledTask, Get-WinEvent) produces live system state in CSV format but does not access locked files, does not preserve original file timestamps, and requires manual correlation rather than automated parsing. Use KAPE when available. Use PowerShell when KAPE is not available or when you need specific live system data (DNS cache, active connections, running processes) that KAPE's file-based collection does not capture.

Containment: PowerShell is the primary containment tool. The Contain-Identity.ps1 script above executes the complete identity containment sequence in 30 seconds. Performing the same actions through the Entra ID portal, Exchange admin center, and Defender portal takes 5-10 minutes of manual navigation. During an active BEC where the attacker is reading emails and redirecting payments, those 5-10 minutes matter. Always use PowerShell for containment actions — speed is the priority.

Automation: PowerShell is the only option for batch operations across multiple targets. Checking 50 mailboxes for a specific inbox rule, querying sign-in logs for 20 compromised accounts, collecting evidence from 10 endpoints — all require scripted automation. Manual portal-based investigation does not scale beyond 3-5 targets. Use PowerShell automation for any operation that touches more than 5 targets.

Compliance Myth
"PowerShell is an attacker tool — security teams should disable it."
Production reality: PowerShell is the primary management tool for Windows and M365 environments. Disabling it cripples both administration and incident response. The correct approach is not to disable PowerShell but to log it comprehensively (ScriptBlock logging, Module logging, Transcription) so that both attacker and defender activity is visible. Constrained Language Mode and WDAC (Windows Defender Application Control) policies can restrict PowerShell to approved scripts while maintaining full capability for administrators. IR5 covers PowerShell logging in depth; IR7 covers detecting attacker PowerShell usage through those logs.
POWERSHELL IR CAPABILITIES — THREE ROLES COLLECT Processes, connections, event logs Scheduled tasks, services, registry When KAPE/Velociraptor unavailable CONTAIN Disable accounts, revoke sessions Remove inbox rules, block IPs Fastest containment path available AUTOMATE Script repetitive IR tasks Batch evidence collection 50 mailboxes in 2 minutes, not 2 hours Already installed on every Windows system and available for every M365 tenant
Figure IR1.7: PowerShell's three IR roles. Collection when dedicated tools are unavailable. Containment at command-line speed. Automation for scale.

Build it: Test your IR PowerShell capabilities

Run the process collection and network connection commands on your for...

Run the process collection and network connection commands on your forensic workstation. Export to CSV and open in Timeline Explorer. If you have a Developer Tenant, connect to Exchange Online and list the mailboxes — these are the mailboxes you will investigate in IR9. Install the Microsoft Graph module and test the connection — this is the authentication path for IR8 and IR11. Every module you can run now is a module you don't need to debug during an investigation.

Beyond this investigation

The techniques taught in this subsection apply beyond the specific scenario presented here. The same evidence sources, tools, and analytical methods are used across ransomware, BEC, insider threat, and APT investigations — the context changes but the methodology is consistent.

Decision point

You discover evidence that the attacker has been in the environment for 90 days. The CISO asks: 'Why did our SOC not detect this sooner?' How do you answer constructively?

Answer with facts, not defensiveness. 'The attacker used [specific techniques] that our current detection rules do not cover. The investigation identified [N] detection gaps — [list the specific ATT&CK techniques that were not detected]. The IR-to-DE handoff includes these gaps as detection engineering sprint items. Estimated time to close: [N weeks].' This answer is honest (we missed it), specific (here is what we missed and why), and forward-looking (here is how we fix it). The PIR action items transform the detection failure into a measurable improvement program.