Scripts/Get-Emails.ps1
Function Get-Email { <# .SYNOPSIS Get a specific email. .DESCRIPTION Get a specific email based on userId and Internet Message Id and saves the output to a eml or txt file. .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\EmailExport .PARAMETER UserIds The unique identifier of the user. .PARAMETER internetMessageId The InternetMessageId parameter represents the Internet message identifier of an item. .PARAMETER output Output is the parameter specifying the eml or txt output type. Default: eml .PARAMETER inputFile The inputFile parameter specifies the .txt file containing multiple Internet Message Identifiers. You can include multiple Internet Message Identifiers in the file. Ensure each ID is placed on a new line. .PARAMETER attachment The attachment parameter specifies whether the attachment should be saved or not. Default: False .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Debug: Verbose logging for debugging purposes Default: Standard .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "<d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>" Retrieves an email from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier <d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com> to a eml file. .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "<d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>" -attachment Retrieves an email and the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier <d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com> to a eml file. .EXAMPLE Get-Email -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "<d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>" -OutputDir C:\Windows\Temp Retrieves an email and saves it to C:\Windows\Temp folder. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)]$userIds, [string]$internetMessageId, [ValidateSet("eml", "txt")] [string]$Output = "eml", [string]$outputDir = "Output\EmailExport", [switch]$attachment, [string]$inputFile, [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug $summary = @{ TotalProcessed = 0 SuccessfulDownloads = 0 FailedDownloads = 0 DuplicatesFound = 0 AttachmentsProcessed = 0 StartTime = Get-Date ProcessingTime = $null Errors = @() } if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] UserIds: $userIds" -Level Debug Write-LogFile -Message "[DEBUG] InternetMessageId: $internetMessageId" -Level Debug Write-LogFile -Message "[DEBUG] Output: $Output" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: $outputDir" -Level Debug Write-LogFile -Message "[DEBUG] Attachment: $($attachment.IsPresent)" -Level Debug Write-LogFile -Message "[DEBUG] InputFile: $inputFile" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: $LogLevel" -Level Debug $graphModule = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue if ($graphModule) { Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug foreach ($module in $graphModule) { Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug } } else { Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug } } Write-LogFile -Message "=== Starting Email Export ===" -Color "Cyan" -Level Standard $requiredScopes = @("Mail.Readwrite") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Graph authentication completed" -Level Debug try { $context = Get-MgContext if ($context) { Write-LogFile -Message "[DEBUG] Graph context information:" -Level Debug Write-LogFile -Message "[DEBUG] Account: $($context.Account)" -Level Debug Write-LogFile -Message "[DEBUG] Environment: $($context.Environment)" -Level Debug Write-LogFile -Message "[DEBUG] TenantId: $($context.TenantId)" -Level Debug Write-LogFile -Message "[DEBUG] Scopes: $($context.Scopes -join ', ')" -Level Debug } } catch { Write-LogFile -Message "[DEBUG] Could not retrieve Graph context details" -Level Debug } } if (!(Test-Path -Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force > $null } $fileCounter = 1 $processedMessages = @{} $duplicateMessages = @() $notCollected = @() $ProgressPreference = 'SilentlyContinue' if ($inputFile) { if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing input file: $inputFile" -Level Debug } try { $internetMessageIds = Get-Content $inputFile Write-LogFile -Message "[INFO] Found $($internetMessageIds.Count) messages in the input file to process" -Level Standard -Color "Green" } catch { write-LogFile -Message "[ERROR] Failed to read the input file. Ensure it is a text file with the message IDs on new lines: $_" -Level Minimal -Color "Red" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Input file read error details:" -Level Debug Write-LogFile -Message "[DEBUG] File path: $inputFile" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug } return } foreach ($id in $internetMessageIds) { $summary.TotalProcessed++ $id = $id.Trim() Write-LogFile -Message "[INFO] Processing Internet Message ID: $id" -Level Standard if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing message details:" -Level Debug Write-LogFile -Message "[DEBUG] Raw ID from file: '$($id)'" -Level Debug Write-LogFile -Message "[DEBUG] Trimmed ID: '$($id)'" -Level Debug Write-LogFile -Message "[DEBUG] Progress: $($summary.TotalProcessed) of $($internetMessageIds.Count)" -Level Debug } try { $getMessage = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userIds/messages?filter=internetMessageId eq '$id'" $message = $getMessage.value[0] if ($null -eq $message) { Write-LogFile -Message "[WARNING] No message found for Internet Message ID: $($id). This might happen when the email is removed from the mailbox." -Level Minimal -Color "Yellow" $notCollected += $id $summary.FailedDownloads++ if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Message not found details:" -Level Debug Write-LogFile -Message "[DEBUG] Searched ID: $id" -Level Debug Write-LogFile -Message "[DEBUG] API response value count: $($getMessage.value.Count)" -Level Debug } continue } $messageId = $message.Id if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Message found successfully:" -Level Debug Write-LogFile -Message "[DEBUG] Message ID: $messageId" -Level Debug Write-LogFile -Message "[DEBUG] Subject: $($message.Subject)" -Level Debug Write-LogFile -Message "[DEBUG] Received: $($message.receivedDateTime)" -Level Debug Write-LogFile -Message "[DEBUG] From: $($message.from.emailAddress.address)" -Level Debug Write-LogFile -Message "[DEBUG] Has attachments: $($message.hasAttachments)" -Level Debug } if ($processedMessages.ContainsKey($messageId)) { $duplicateMessages += @{ 'MessageId' = $messageId 'FirstInternetMessageId' = $processedMessages[$messageId] 'SecondInternetMessageId' = $id } $summary.DuplicatesFound++ Write-LogFile -Message "[INFO] Duplicate message detected! Message ID $messageId was previously processed with Internet Message ID $($processedMessages[$messageId])" -Color "Yellow" -Level Standard if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Duplicate detection details:" -Level Debug Write-LogFile -Message "[DEBUG] Duplicate message ID: $messageId" -Level Debug Write-LogFile -Message "[DEBUG] First seen with: $($processedMessages[$messageId])" -Level Debug Write-LogFile -Message "[DEBUG] Current ID: $id" -Level Debug Write-LogFile -Message "[DEBUG] Total duplicates found so far: $($summary.DuplicatesFound)" -Level Debug } continue } $processedMessages[$messageId] = $id $subject = $message.Subject -replace '[\\/:*?"<>|]', '_' $extension = if ($output -eq "txt") { "txt" } else { "eml" } if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] File preparation:" -Level Debug Write-LogFile -Message "[DEBUG] Original subject: $($message.Subject)" -Level Debug Write-LogFile -Message "[DEBUG] Sanitized subject: $subject" -Level Debug Write-LogFile -Message "[DEBUG] File extension: $extension" -Level Debug Write-LogFile -Message "[DEBUG] Current file counter: $fileCounter" -Level Debug } try { $ReceivedDateTime = [datetime]::Parse($message.receivedDateTime).ToString("yyyyMMdd_HHmmss") do { $filePath = "$outputDir\$($fileCounter.ToString('D3'))-$ReceivedDateTime-$subject.$extension" $fileCounter++ } while (Test-Path $filePath) } catch { Write-LogFile -Message "[WARNING] Could not parse received date time, excluding from filename" -Level Standard -Color "Yellow" do { $filePath = "$outputDir\$($fileCounter.ToString('D3'))-$subject.$extension" $fileCounter++ } while (Test-Path $filePath) if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Date parsing failed, using file path without date: $filePath" -Level Debug Write-LogFile -Message "[DEBUG] Date parsing error: $($_.Exception.Message)" -Level Debug } } $contentUri = "https://graph.microsoft.com/v1.0/users/$userIds/messages/" + $messageId + "/`$value" Invoke-MgGraphRequest -Method GET $contentUri -OutputFilePath $filePath $summary.SuccessfulDownloads++ $fileCounter++ Write-LogFile -Message "[SUCCESS] Saved message to: $filePath" -Color "Green" -Level Standard if ($attachment.IsPresent){ $attachmentProcessed = Get-Attachment -Userid $Userids -internetMessageId $id if ($attachmentProcessed) { $summary.AttachmentsProcessed++ } } } catch { $summary.FailedDownloads++ $summary.Errors += "Failed to process $id : $_" Write-LogFile -Message "[WARNING] Failed to collect message with ID '$id': $_" Write-LogFile -Message "[ERROR] Failed to process message: $_" -Color "Red" -Level Minimal $notCollected += $id if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Message processing error details:" -Level Debug Write-LogFile -Message "[DEBUG] Failed message ID: $id" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug Write-LogFile -Message "[DEBUG] Total failures so far: $($summary.FailedDownloads)" -Level Debug } } } } else { if (-not $internetMessageId) { write-LogFile -Message "[ERROR] Either internetMessageId or inputFile must be provided." -Level Minimal -Color "Red" return } try { $summary.TotalProcessed++ $getMessage = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userIds/messages?filter=internetMessageId eq '$internetMessageId'" $message = $getMessage.value[0] if ($null -eq $message) { Write-LogFile -Message "[WARNING] No message found for Internet Message ID: $($internetMessageId). This might happen when the email is removed from the mailbox." -Level Minimal -Color "Yellow" $summary.FailedDownloads++ if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Single message not found:" -Level Debug Write-LogFile -Message "[DEBUG] Searched ID: $internetMessageId" -Level Debug Write-LogFile -Message "[DEBUG] API response value count: $($getMessage.value.Count)" -Level Debug } return } $ReceivedDateTime = [datetime]::Parse($message.receivedDateTime).ToString("yyyyMMdd_HHmmss") $messageId = $message.id $subject = $message.Subject -replace '[\\/:*?"<>|]', '_' if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Single message details:" -Level Debug Write-LogFile -Message "[DEBUG] Message ID: $messageId" -Level Debug Write-LogFile -Message "[DEBUG] Subject: $($message.Subject)" -Level Debug Write-LogFile -Message "[DEBUG] Sanitized subject: $subject" -Level Debug Write-LogFile -Message "[DEBUG] Received: $($message.receivedDateTime)" -Level Debug Write-LogFile -Message "[DEBUG] Formatted date: $ReceivedDateTime" -Level Debug Write-LogFile -Message "[DEBUG] From: $($message.from.emailAddress.address)" -Level Debug Write-LogFile -Message "[DEBUG] Has attachments: $($message.hasAttachments)" -Level Debug } $extension = if ($output -eq "txt") { "txt" } else { "eml" } do { $filePath = "$outputDir\$($fileCounter.ToString('D3'))-$ReceivedDateTime-$subject.$extension" $fileCounter++ } while (Test-Path $filePath) $contentUri = "https://graph.microsoft.com/v1.0/users/$userIds/messages/" + $messageId + "/`$value" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Single message download:" -Level Debug Write-LogFile -Message "[DEBUG] Content URI: $contentUri" -Level Debug Write-LogFile -Message "[DEBUG] Output file path: $filePath" -Level Debug Write-LogFile -Message "[DEBUG] File extension: $extension" -Level Debug } Invoke-MgGraphRequest -Method GET $contentUri -OutputFilePath $filePath $summary.SuccessfulDownloads++ Write-LogFile -Message "[INFO] Output written to $filePath" -Color "Green" -Level Standard if ($isDebugEnabled) { $fileInfo = Get-Item $filePath -ErrorAction SilentlyContinue Write-LogFile -Message "[DEBUG] Single message download completed:" -Level Debug Write-LogFile -Message "[DEBUG] Download time: $([math]::round($downloadPerformance.TotalSeconds, 2)) seconds" -Level Debug if ($fileInfo) { Write-LogFile -Message "[DEBUG] File size: $($fileInfo.Length) bytes" -Level Debug Write-LogFile -Message "[DEBUG] File created: $($fileInfo.CreationTime)" -Level Debug } } if ($attachment.IsPresent){ $attachmentProcessed = Get-Attachment -Userid $Userids -internetMessageId $internetMessageId if ($attachmentProcessed) { $summary.AttachmentsProcessed++ } } } catch { $summary.FailedDownloads++ $summary.Errors += "Failed to process $internetMessageId : $_" Write-LogFile -Message "[WARNING] The 'Mail.Readwrite' is an application-level permission, requiring an application-based connection through the 'Connect-MgGraph' command for its use." -Color "Yellow" -Level Minimal Write-LogFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Level Minimal -Color "Red" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Single message processing error details:" -Level Debug Write-LogFile -Message "[DEBUG] Failed message ID: $internetMessageId" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } return } } $summary.ProcessingTime = (Get-Date) - $summary.StartTime Write-LogFile -Message "`n=== Email Export Summary ===" -Color "Cyan" -Level Standard Write-LogFile -Message "Processing Statistics:" -Level Standard Write-LogFile -Message " Total Messages Processed: $($summary.TotalProcessed)" -Level Standard Write-LogFile -Message " Successfully Downloaded: $($summary.SuccessfulDownloads)" -Level Standard Write-LogFile -Message " Failed Downloads: $($summary.FailedDownloads)" -Level Standard Write-LogFile -Message " Duplicates Found: $($summary.DuplicatesFound)" -Level Standard if ($attachment.IsPresent) { Write-LogFile -Message " Attachments Processed: $($summary.AttachmentsProcessed)" -Level Standard } if ($duplicateMessages.Count -gt 0) { Write-LogFile -Message "`nDuplicate Messages:" -Color "Yellow" -Level Standard foreach ($dup in $duplicateMessages) { Write-LogFile -Message " Message ID: $($dup.MessageId)" -Level Standard Write-LogFile -Message " First seen with: $($dup.FirstInternetMessageId)" -Level Standard Write-LogFile -Message " Also found with: $($dup.SecondInternetMessageId)" -Level Standard } } if ($notCollected.Count -gt 0) { Write-LogFile -Message "`nFailed to Collect:" -Color "Yellow" -Level Standard foreach ($id in $notCollected) { Write-LogFile -Message " - $id" -Level Standard } } Write-LogFile -Message "`nOutput Statistics:" -Level Standard Write-LogFile -Message " Directory: $outputDir" -Level Standard Write-LogFile -Message " Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Color "Green" -Level Standard Write-LogFile -Message "===================================" -Color "Cyan" -Level Standard } Function Get-Attachment { <# .SYNOPSIS Get a specific attachment. .DESCRIPTION Get a specific attachment based on userId and Internet Message Id and saves the output. .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\Emails .PARAMETER UserIds The unique identifier of the user. .PARAMETER internetMessageId The InternetMessageId parameter represents the Internet message identifier of an item. .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Default: Standard .EXAMPLE Get-Attachment -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "<d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>" Retrieves the attachment from fortunahodan@bonacu.onmicrosoft.com with the internet message identifier <d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>. .EXAMPLE Get-Attachment -userIds fortunahodan@bonacu.onmicrosoft.com -internetMessageId "<d6f15b97-e3e3-4871-adb2-e8d999d51f34@az.westeurope.microsoft.com>" -OutputDir C:\Windows\Temp Retrieves an attachment and saves it to C:\Windows\Temp folder. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)]$userIds, [Parameter(Mandatory=$true)]$internetMessageId, [string]$outputDir = "Output\EmailExport", [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) if (!(Test-Path -Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force > $null } try { $getMessage = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userIds/messages?filter=internetMessageId eq '$internetMessageId'" -ErrorAction stop } catch { write-logFile -Message "[INFO] Ensure you are connected to Microsoft Graph by running the Connect-MgGraph -Scopes Mail.Readwrite command before executing this script" -Color "Yellow" -Level Minimal Write-logFile -Message "[WARNING] The 'Mail.Readwrite' is an application-level permission, requiring an application-based connection through the 'Connect-MgGraph' command for its use." -Color "Red" -Level Minimal Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal throw } $messageId = $getMessage.value.Id $hasAttachment = $getMessage.value.HasAttachments $ReceivedDateTime = $getMessage.value.ReceivedDateTime.ToString("yyyyMMdd_HHmmss") $subject = $getMessage.value.Subject if ($hasAttachment -eq "True"){ Write-LogFile -Message "[INFO] Processing attachments for message: $internetMessageId" -Color "Green" -Level Standard $response = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userIds/messages/$messageId/attachments" Write-LogFile -Message "[INFO] Found $($response.value.Count) attachment(s) for email: $internetMessageId" -Level Standard foreach ($attachment in $response.value){ $filename = $attachment.Name Write-logFile -Message "[INFO] Name: $filename" -Level Standard Write-logFile -Message "[INFO] Size: $($attachment.Size)" -Level Standard $uri = "https://graph.microsoft.com/v1.0/users/$userIds/messages/$messageId/attachments/$($attachment.Id)/\$value" $response = Invoke-MgGraphRequest -Method Get -Uri $uri $filename = $filename -replace '[\\/:*?"<>|]', '_' $filePath = Join-Path $outputDir "$ReceivedDateTime-$subject-$filename" $base64B = ($attachment.contentBytes) $decoded = [System.Convert]::FromBase64String($base64B) # Check PowerShell version and use appropriate parameter if ($PSVersionTable.PSVersion.Major -ge 6) { Set-Content -Path $filePath -Value $decoded -AsByteStream } else { Set-Content -Path $filePath -Value $decoded -Encoding Byte } Write-logFile -Message "[INFO] Output written to '$subject-$filename'" -Color "Green" -Level Standard return $true } } else { return $false } } Function Show-Email { <# .SYNOPSIS Show a specific email in the PowerShell Window. .DESCRIPTION Show a specific email in the PowerShell Window based on userId and Internet Message Id. .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Default: Standard .EXAMPLE Show-Email -userIds {userId} -internetMessageId {InternetMessageId} Show a specific email in the PowerShell Window. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)]$userIds, [Parameter(Mandatory=$true)]$internetMessageId, [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) $requiredScopes = @("Mail.Readwrite") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes Write-logFile -Message "[INFO] Running Show-Email" -Color "Green" -Level Standard try { $message = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userIds/messages?filter=internetMessageId eq '$internetMessageId'" -ErrorAction stop } catch { Write-logFile -Message "[WARNING] The 'Mail.Readwrite' is an application-level permission, requiring an application-based connection through the 'Connect-MgGraph' command for its use." -Color "Red" -Level Minimal Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal throw } $message.Value } |