Created
December 3, 2025 13:50
-
-
Save cicero343/68e525402eaba9f0549e8372b1edd7da to your computer and use it in GitHub Desktop.
Export Outlook emails, headers, and attachments for phishing investigation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Exports Outlook emails, headers, and attachments for evidence collection. | |
| .DESCRIPTION | |
| Uses Outlook COM automation to launch an interactive GUI for selecting folders and emails, then exports: | |
| - .msg copies of selected emails | |
| - Internet headers (.txt) | |
| - Attachments, with optional SHA256 hashing | |
| All outputs are organised under ~/Downloads/PhishingEvidence. | |
| .NOTES | |
| - Requires Outlook installed | |
| - Can run directly in PowerShell without saving as a script | |
| - Set $ComputeAttachmentHashes = $false to disable attachment hashing | |
| #> | |
| # Toggle: compute SHA256 hashes of attachments? | |
| $ComputeAttachmentHashes = $true # set to $false to disable hashing | |
| # Attach to Outlook | |
| $outlook = New-Object -ComObject Outlook.Application | |
| $session = $outlook.Session | |
| # Helper: recursively get all mail folders (no strict typing) | |
| function Get-MailFolders { | |
| param( | |
| $Folder | |
| ) | |
| # 0 = olMailItem (folders that normally hold mail) | |
| if ($Folder.DefaultItemType -eq 0) { | |
| $Folder | |
| } | |
| foreach ($sub in $Folder.Folders) { | |
| Get-MailFolders -Folder $sub | |
| } | |
| } | |
| # 1) Collect all mail folders from all stores (mailboxes, PSTs, etc.) | |
| $mailFolders = @() | |
| foreach ($root in $session.Folders) { | |
| $mailFolders += Get-MailFolders -Folder $root | |
| } | |
| if (-not $mailFolders) { | |
| Write-Host "No mail folders found." -ForegroundColor Yellow | |
| return | |
| } | |
| # Build a simple list for folder selection (no COM objects in the grid) | |
| $folderList = $mailFolders | ForEach-Object { | |
| [PSCustomObject]@{ | |
| Name = $_.Name | |
| FolderPath = $_.FolderPath | |
| ItemsCount = $_.Items.Count | |
| } | |
| } | |
| # 2) Let you pick one or more folders | |
| $chosenFolders = $folderList | | |
| Sort-Object FolderPath | | |
| Out-GridView -Title "Select Outlook folder(s) to search (Ctrl+click / Ctrl+A for multiple)" -PassThru | |
| if (-not $chosenFolders) { | |
| Write-Host "No folders selected, exiting." | |
| return | |
| } | |
| # Map back to actual COM folder objects | |
| $selectedFolderObjects = @() | |
| foreach ($cf in $chosenFolders) { | |
| $f = $mailFolders | Where-Object { $_.FolderPath -eq $cf.FolderPath } | |
| if ($f) { $selectedFolderObjects += $f } | |
| } | |
| if (-not $selectedFolderObjects) { | |
| Write-Host "Could not resolve selected folders back to Outlook objects." -ForegroundColor Red | |
| return | |
| } | |
| # 3) Collect mail items from the selected folders | |
| $items = @() | |
| foreach ($folder in $selectedFolderObjects) { | |
| $storeId = $folder.StoreID | |
| $folderItems = $folder.Items | |
| $max = $folderItems.Count | |
| Write-Host "Scanning folder: $($folder.FolderPath) ($max items)..." | |
| for ($i = 1; $i -le $max; $i++) { | |
| try { | |
| $item = $folderItems.Item($i) | |
| } catch { | |
| continue | |
| } | |
| try { | |
| if ($item.MessageClass -like "IPM.Note*") { | |
| $items += [PSCustomObject]@{ | |
| Subject = $item.Subject | |
| Sender = $item.SenderName | |
| Received = $item.ReceivedTime | |
| FolderPath = $folder.FolderPath | |
| EntryID = $item.EntryID | |
| StoreID = $storeId | |
| } | |
| } | |
| } catch { | |
| continue | |
| } | |
| } | |
| } | |
| if (-not $items) { | |
| Write-Host "No mail items found in the selected folder(s)." -ForegroundColor Yellow | |
| return | |
| } | |
| # 4) Let you pick email(s) from those folders | |
| $chosen = $items | | |
| Sort-Object Received -Descending | | |
| Out-GridView -Title "Select email(s) to export evidence for" -PassThru | |
| if (-not $chosen) { | |
| Write-Host "No emails selected, exiting." | |
| return | |
| } | |
| # 5) Export evidence | |
| $outRoot = Join-Path $env:USERPROFILE "Downloads\PhishingEvidence" | |
| New-Item -ItemType Directory -Path $outRoot -ErrorAction SilentlyContinue | Out-Null | |
| foreach ($row in $chosen) { | |
| try { | |
| $mail = $session.GetItemFromID($row.EntryID, $row.StoreID) | |
| } catch { | |
| Write-Host "Failed to get mail item for '$($row.Subject)': $($_.Exception.Message)" -ForegroundColor Red | |
| continue | |
| } | |
| $stamp = (Get-Date).ToString("yyyyMMddTHHmmss") | |
| # ---- Sender domain for case folder name ---- | |
| $senderAddress = $mail.SenderEmailAddress | |
| $senderDomain = "UnknownDomain" | |
| if ($senderAddress -and ($senderAddress -match "@")) { | |
| $senderDomain = $senderAddress.Split("@")[-1] | |
| } | |
| # Sanitize pieces for filesystem | |
| $safeSubject = ($mail.Subject -replace '[\\/:*?"<>|]', '_') | |
| if (-not $safeSubject) { $safeSubject = "NoSubject" } | |
| $senderDomainSlug = ($senderDomain -replace '[\\/:*?"<>|]', '_') | |
| # Folder path slug still useful context | |
| $folderSlug = ($row.FolderPath -replace '[\\/:*?"<>|]', '_') | |
| # Case directory: timestamp + sender domain + subject | |
| $caseDirName = "${stamp}_${senderDomainSlug}_${safeSubject}" | |
| $caseDir = Join-Path $outRoot $caseDirName | |
| New-Item -ItemType Directory -Path $caseDir -ErrorAction SilentlyContinue | Out-Null | |
| Write-Host "" | |
| Write-Host "Processing:" | |
| Write-Host " Folder: $($row.FolderPath)" | |
| Write-Host " From: $($mail.SenderName) <$senderAddress>" | |
| Write-Host " Sender domain: $senderDomain" | |
| Write-Host " Subject: $($mail.Subject)" | |
| Write-Host " Case folder: $caseDir" | |
| # Save .msg | |
| try { | |
| $msgPath = Join-Path $caseDir "message.msg" | |
| $mail.SaveAs($msgPath, 3) # 3 = olMSG | |
| } catch { | |
| Write-Host " (Could not save .msg file: $($_.Exception.Message))" -ForegroundColor Yellow | |
| } | |
| # Internet headers | |
| try { | |
| $headers = $mail.PropertyAccessor.GetProperty( | |
| "http://schemas.microsoft.com/mapi/proptag/0x007D001E" | |
| ) | |
| $headers | Set-Content (Join-Path $caseDir "headers.txt") | |
| } catch { | |
| Write-Host " (Could not read headers: $($_.Exception.Message))" -ForegroundColor Yellow | |
| } | |
| # Attachments (+ optional hashing) | |
| try { | |
| if ($mail.Attachments.Count -gt 0) { | |
| $attDir = Join-Path $caseDir "Attachments" | |
| New-Item -ItemType Directory -Path $attDir -ErrorAction SilentlyContinue | Out-Null | |
| $hashFile = Join-Path $caseDir "attachment_hashes.txt" | |
| $hashLines = @() | |
| foreach ($att in $mail.Attachments) { | |
| $attPath = Join-Path $attDir $att.FileName | |
| $att.SaveAsFile($attPath) | |
| if ($ComputeAttachmentHashes) { | |
| try { | |
| $hash = Get-FileHash -Path $attPath -Algorithm SHA256 | |
| $line = "{0}`t{1}`t{2}" -f $att.FileName, "SHA256", $hash.Hash | |
| $hashLines += $line | |
| } catch { | |
| $hashLines += "{0}`t{1}`t{2}" -f $att.FileName, "SHA256", "HASH_FAILED: $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| if ($ComputeAttachmentHashes -and $hashLines.Count -gt 0) { | |
| "FileName`tAlgorithm`tHash" | Set-Content $hashFile | |
| $hashLines | Add-Content $hashFile | |
| } | |
| } | |
| } catch { | |
| Write-Host " (Could not save one or more attachments: $($_.Exception.Message))" -ForegroundColor Yellow | |
| } | |
| Write-Host " Done." | |
| } | |
| Write-Host "" | |
| Write-Host "All evidence saved under: $outRoot" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment