Skip to content

Instantly share code, notes, and snippets.

@9000cats
Last active November 23, 2024 21:25
Show Gist options
  • Save 9000cats/32ad48b3c524ab11f775e8ceb8c1e1e5 to your computer and use it in GitHub Desktop.
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)
<#
.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