Last active
November 23, 2024 21:25
-
-
Save 9000cats/32ad48b3c524ab11f775e8ceb8c1e1e5 to your computer and use it in GitHub Desktop.
PowerShell script that will convert .eml email files to .msg format, using PowerShell and .NET, and without COM objects (New Outlook doesn't support COM objects)
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 | |
This PowerShell script converts an .eml email file to a .msg format, preserving email metadata such as sender, recipients (To, CC, BCC), subject, and body (text and HTML), as well as attachments. | |
.DESCRIPTION | |
The script opens a dialog for the user to select an .eml file, reads the email using MimeKit, and converts it to a .msg format using the MsgKit library. | |
The script ensures that all major email components, including attachments, are properly transferred. This script requires PowerShell 7 or later. | |
.PARAMETER EML File | |
The input .eml email file to be converted. | |
.PARAMETER MSG File | |
The output .msg email file created from the conversion. | |
.NOTES | |
Version: 1.0 | |
Date: 9/26/2024 | |
Authors: Warren Held, ChatGPT o1-preview, ChatGPT 4o | |
Compatibility: PowerShell 7+, Windows OS, and requires the following libraries: | |
- MimeKit | |
- BouncyCastle | |
- OpenMcdf | |
- MsgKit | |
.EXAMPLE | |
Convert an .eml file to .msg format: | |
.\Convert-Email.ps1 | |
#> | |
# Check for PowerShell version 7 or later | |
if ($PSVersionTable.PSVersion.Major -lt 7) { | |
Write-Error "This script requires PowerShell 7 or later. Please run it in PowerShell 7." | |
exit | |
} | |
# Load necessary assemblies for OpenFileDialog | |
Add-Type -AssemblyName System.Windows.Forms | |
# Open a file dialog to select the .eml file | |
$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog | |
$openFileDialog.Filter = "EML files (*.eml)|*.eml" | |
$openFileDialog.Title = "Select the .eml file to convert" | |
$dialogResult = $openFileDialog.ShowDialog() | |
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) { | |
$emlFilePath = $openFileDialog.FileName | |
} else { | |
Write-Host "No file selected. Exiting." | |
exit | |
} | |
try { | |
# Specify the paths to the assemblies | |
$mimeKitVersion = "4.7.1" | |
$bouncyCastleVersion = "1.8.9" | |
$openMcdfVersion = "2.3.1" | |
$msgKitVersion = "2.8.2" | |
$mimeKitPath = "$env:PROGRAMFILES\PackageManagement\NuGet\Packages\MimeKit.$mimeKitVersion\lib\netstandard2.0\MimeKit.dll" | |
$bouncyCastlePath = "$env:PROGRAMFILES\PackageManagement\NuGet\Packages\BouncyCastle.$bouncyCastleVersion\lib\BouncyCastle.Crypto.dll" | |
$openMcdfPath = "$env:PROGRAMFILES\PackageManagement\NuGet\Packages\OpenMcdf.$openMcdfVersion\lib\netstandard2.0\OpenMcdf.dll" | |
$msgKitPath = "$env:PROGRAMFILES\PackageManagement\NuGet\Packages\MsgKit.$msgKitVersion\lib\netstandard2.0\MsgKit.dll" | |
# Function to install a package if the assembly does not exist | |
function Install-PackageIfNeeded { | |
param ( | |
[string]$assemblyPath, | |
[string]$packageName, | |
[string]$packageVersion, | |
[string]$source = "https://www.nuget.org/api/v2" | |
) | |
if (-not (Test-Path $assemblyPath)) { | |
Write-Error "Assembly not found at $assemblyPath" -ErrorAction Continue | |
Write-Host "Installing $packageName version $packageVersion..." | |
# Install the package with SkipDependencies and specify the version | |
Install-Package -Name $packageName -RequiredVersion $packageVersion -Source $source -SkipDependencies -Force -Scope AllUsers | |
} else { | |
# Write-Host "$packageName assembly found at $assemblyPath" | |
} | |
} | |
# Install packages if needed | |
Install-PackageIfNeeded -assemblyPath $mimeKitPath -packageName 'MimeKit' -packageVersion $mimeKitVersion | |
Install-PackageIfNeeded -assemblyPath $bouncyCastlePath -packageName 'BouncyCastle' -packageVersion $bouncyCastleVersion | |
Install-PackageIfNeeded -assemblyPath $openMcdfPath -packageName 'OpenMcdf' -packageVersion $openMcdfVersion | |
Install-PackageIfNeeded -assemblyPath $msgKitPath -packageName 'MsgKit' -packageVersion $msgKitVersion | |
# Load the assemblies | |
Add-Type -Path $mimeKitPath | |
Add-Type -Path $bouncyCastlePath | |
Add-Type -Path $openMcdfPath | |
Add-Type -Path $msgKitPath | |
# Load the .eml file as a MimeMessage | |
$fileStream = [System.IO.File]::OpenRead($emlFilePath) | |
$mimeMessage = [MimeKit.MimeMessage]::Load($fileStream) | |
$fileStream.Close() | |
# Define the output .msg file path | |
$msgFilePath = [System.IO.Path]::ChangeExtension($emlFilePath, ".msg") | |
# Extract sender information | |
$fromMailbox = $mimeMessage.From.Mailboxes | Select-Object -First 1 | |
$senderEmail = $fromMailbox.Address | |
$senderName = $fromMailbox.Name | |
# Create Sender object | |
$msgSender = New-Object MsgKit.Sender($senderEmail, $senderName) | |
# Extract or generate MessageId | |
$messageId = if ($mimeMessage.MessageId) { $mimeMessage.MessageId } else { [Guid]::NewGuid().ToString() } | |
# Decide on boolean values | |
$saveSentMessageFolder = $false | |
$readReceiptRequested = $false | |
$deliveryReceiptRequested = $false | |
# Create a new MsgKit.Email message with the required parameters | |
$message = New-Object MsgKit.Email( | |
$msgSender, | |
$messageId, | |
$saveSentMessageFolder, | |
$readReceiptRequested, | |
$deliveryReceiptRequested | |
) | |
# Set message properties | |
$message.Subject = $mimeMessage.Subject | |
$message.SentOn = $mimeMessage.Date.DateTime | |
$message.BodyText = $mimeMessage.TextBody | |
$message.BodyHtml = $mimeMessage.HtmlBody | |
# Try to get the received date from headers | |
$receivedDate = $mimeMessage.Date.DateTime # Default to sent date | |
$receivedHeaders = $mimeMessage.Headers.Where({$_.Field -eq "Received"}) | |
if ($receivedHeaders.Count -gt 0) { | |
$lastReceivedHeader = $receivedHeaders[-1].Value | |
if ($lastReceivedHeader -match '; (.+)$') { | |
$dateString = $Matches[1].Trim() | |
$parsedDate = [DateTime]::MinValue | |
if ([DateTime]::TryParse($dateString, [ref]$parsedDate)) { | |
$receivedDate = $parsedDate | |
} else { | |
Write-Warning "Could not parse received date: $dateString. Using sent date instead." | |
} | |
} | |
} | |
$message.ReceivedOn = $receivedDate | |
# Get the Recipients collection from the Email object | |
$recipients = $message.Recipients | |
# Common enum values for recipients | |
$addressType = [MsgKit.Enums.AddressType]::SMTP | |
$objectType = [MsgKit.Enums.MapiObjectType]::MAPI_MAILUSER | |
$displayType = [MsgKit.Enums.RecipientRowDisplayType]::MessagingUser | |
# Add 'To' recipients | |
foreach ($toRecipient in $mimeMessage.To) { | |
$recipientEmail = $toRecipient.Address | |
$recipientName = $toRecipient.Name | |
$recipients.AddTo( | |
$recipientEmail, | |
$recipientName, | |
$addressType, | |
$objectType, | |
$displayType | |
) | |
} | |
# Add 'CC' recipients | |
foreach ($ccRecipient in $mimeMessage.Cc) { | |
$recipientEmail = $ccRecipient.Address | |
$recipientName = $ccRecipient.Name | |
$recipients.AddCc( | |
$recipientEmail, | |
$recipientName, | |
$addressType, | |
$objectType, | |
$displayType | |
) | |
} | |
# Add 'BCC' recipients | |
foreach ($bccRecipient in $mimeMessage.Bcc) { | |
$recipientEmail = $bccRecipient.Address | |
$recipientName = $bccRecipient.Name | |
$recipients.AddBcc( | |
$recipientEmail, | |
$recipientName, | |
$addressType, | |
$objectType, | |
$displayType | |
) | |
} | |
# Initialize the Attachments collection if necessary | |
if ($null -eq $message.Attachments) { | |
$message.Attachments = New-Object 'System.Collections.Generic.List[MsgKit.Attachment]' | |
} | |
# Handle attachments | |
foreach ($mimeAttachment in $mimeMessage.Attachments) { | |
# Create a MemoryStream to store the attachment's content | |
$stream = New-Object System.IO.MemoryStream | |
$mimeAttachment.Content.DecodeTo($stream) | |
$stream.Position = 0 | |
# Extract the attachment filename | |
$fileName = $mimeAttachment.FileName | |
# Add the attachment to the MsgKit message's Attachments collection using the Add method | |
# The Add method takes a stream, filename, rendering position, inline flag, and content ID | |
$message.Attachments.Add($stream, $fileName) | |
} | |
# Save the message as a .msg file | |
$message.Save($msgFilePath) | |
Write-Host "Successfully converted `"$emlFilePath`" to `"$msgFilePath`"" | |
} catch { | |
Write-Error "An error occurred: $_" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment