'Converting EML's to MSG's

We have a webapplication that allows users to view emails in a table and double click on them to open them in outlook.

For that we use the (simplified) piece of code:

 var email = Session.OpenSharedItem(filename) as MailItem;

This works for .msg messages, but there are also .eml files listed in the tables. OpenSharedItem method cannot open .eml files (https://msdn.microsoft.com/en-us/library/bb176433(v=office.12).aspx)

So we would like to convert those .eml files to .msg files.

So far we have only found answers in paid third party libraries like Redemption which we cannot do. Are there any other solutions available?

Edit: Made more clear that we cannot use paid third party libraries.



Solution 1:[1]

If you're able to shell out, outlook.exe can just run emls without conversion like so

outlook.exe /eml "path\to\file.eml"

This even works if the email is editable (X-Unsent= 1).
There may even be a way to do an equivalent without shell which would be nice.

Alternatively though, you can do this programatically, but requires conversion; in fact you have to if you want any editable email to automatically insert the user's signature (as that would be an oft, not an msg/eml).
Below I have a powershell script that takes an eml and conditionally either saves out an msg or an oft, and then opens it.

You can make it just convert without opening, or just open without converting (but you do still need to make temporary files: OOM does not accept making MailItems from memory); I'm just covering all bases for posterity.
What I use it for is the eml file extension is associated to open with the script on the clients machines, they double-click an eml file, it opens in outlook.

Even though it's powershell, C# is pretty interchangable; I mean I wrote it reading only examples and documentation for C# (maybe some vbs for the old CDO).
I dont know C#, and feel putting this here will be magnitudes more help to someone seeing this post than not writing anything (since I had to scour the internet and do it from scratch).
I'll glady accept any edit if someone ports it

$location = $PSScriptRoot
Start-Transcript "$location\LOG.txt" -Append

#ADODB only works if MIME is at the top
. {
    'MIME-Version: 1.0'
    Get-Content $args[0] <#-AsByteStream#>
} $args[0] | Set-Content "$location\tmp" <#-AsByteStream#>

#parse the eml
$eml = New-Object -ComObject ADODB.Stream
$eml.Open()
$eml.LoadFromFile("$location\tmp")
$eml.Flush()
$email = New-Object -ComObject "CDO.Message"
$email.DataSource.OpenObject($eml, "_Stream")
$email.DataSource.Save()
$eml.Close()

#!moved this to the bottom to demonstrate no-shellingout and msg conversion
#if the email is not editable, just open it normally
#if ($email.Fields('urn:schemas:mailheader:X-Unsent').Value -ne '1') {
#   & "${env:ProgramFiles}\Microsoft Office\root\Office16\OUTLOOK.EXE" /eml $args[0]
#   exit
#}

#build the template
$outlook = New-Object -ComObject Outlook.Application
$output = $outlook.CreateItem(<#olMailItem#>0)
$output.Sender = $email.From
$output.To = $email.To
$output.CC = $email.CC
$output.BCC = $email.BCC
$output.Subject = $email.Subject
if ($email.ReplyTo) {
    $output.ReplyRecipients.Add($email.ReplyTo) | Out-Null
}
$output.BodyFormat = <#olFormatHTML#>2
$output.HTMLBody = $email.HTMLBody

#for each of the attachments
. {for ($part = $email.HTMLBodyPart; $part = $part.Parent) {
    $part.BodyParts | Where-Object {
        $_.Fields('urn:schemas:httpmail:content-disposition-type').Value -match '^(inline|attachment)$'
    }
}} | %{
    #get the name
    $name = ($_.FileName -replace '^.*[/\\]','').trim('.')
    #make one if it didnt have one
    if (!$name) {
        $name = (
            'Untitiled attachment' +
            ($_.Fields('urn:schemas:httpmail:content-media-type').Value `
                -replace '[/\\]'    ,'.' `
                -replace '^\.+|\.+$','' `
                -replace '^(.)'     ,' $1'
            )
        )
    }
    #save the attachment to file
    $_.
        GetDecodedContentStream().
        SaveToFile("$location\$name", <#adSaveCreateOverWrite#>2)

    #TODO unicode,bigendianunicode,utf8,utf7,utf32,ascii,default,oem
    if ($_.Charset -imatch 'UTF-8') {
        Get-Content "$location\$name" | Out-File 'tmp' -encoding utf8
        Move-Item 'tmp' "$location\$name" -Force
    }
    #pull it into the email
    $attachment = $output.Attachments.Add("$location\$name").PropertyAccessor
    Remove-Item "$location\$name"

    #set up its properties
    if ($_.Fields('urn:schemas:mailheader:content-id').Value) {
        $attachment.SetProperty(
            <#PR_ATTACH_CONTENT_ID(unicode)#>'http://schemas.microsoft.com/mapi/proptag/0x3712001F',
            $_.Fields('urn:schemas:mailheader:content-id').Value.trim('<>')
        )
    }
    if ($_.Fields('urn:schemas:httpmail:content-media-type').Value) {
        $attachment.SetProperty(
            <#PR_ATTACH_MIME_TAG(unicode)#>'http://schemas.microsoft.com/mapi/proptag/0x370E001F',
            $_.Fields('urn:schemas:httpmail:content-media-type').Value
        )
    }
}

#save and open the email
if ($email.Fields('urn:schemas:mailheader:X-Unsent').Value -eq '1') {
    $output.SaveAs("email.oft", <#olTemplate#>2)
    $output.Close(<#olDiscard#>1)
    $outlook.CreateItemFromTemplate("email.oft").Display()
}
else {
    $output.SaveAs("email.msg", <#olMSG#>3)
    $output.Close(<#olDiscard#>1)
    $outlook.Session.OpenSharedItem("email.msg").Display()
}
#!as per the above #!; I would normally not have the x-unset test here
#!and would only save the template, but calling it simply 'tmp', so no leftover files are made
Remove-Item "$location\tmp"

I think I've covered everything. This works with attachments, embedded images and css, but assumes HTML email and utf8 for attachments that are text.
That's what I needed, and although it wouldn't be hard to add support for other stuff, if you are private, or can use paid 3rd party, Dmitry seems to have done a fantastically holistic thing with Redemption (I have seen his avatar A LOT in my recent travels) and will be much simpler for you.

Solution 2:[2]

You can use IConverterSession object (native Outlook MIME converter), but it is only accessible in C++ or Delphi. Also note that as of Outlook 2016, an instance of that object can only be created when running inside the outlook.exe address space (e.g. from a COM addin).

You can also create your own converter and create an EML file one MIME header at a time using a third party library (I used Lumisoft in the past).

Using Redemption (I am its author) is an option, it can run in a service, unlike the Outlook Object Model), and the conversion is as simple as

  set Session = CreateObject("Redemption.RDOSession")
  set Msg = Session.GetMessageFromMsgFile("c:\temp\test.msg")
  Msg.SaveAs "c:\temp\test.eml", 1031

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Dmitry Streblechenko