Click here to Skip to main content
15,894,405 members
Articles / Productivity Apps and Services / Microsoft Office

Reading an Outlook MSG File in C#

Rate me:
Please Sign up or sign in to vote.
4.88/5 (116 votes)
8 Jul 2010CPOL3 min read 1.3M   28.4K   190   290
How to read an Outlook msg file in C# without the Outlook object model
Demo application form

Introduction

This article is going to focus on how to dissect a msg file generated by Outlook. It covers how to read the basic properties of the mail message, attachments and any msg attachments (these need to be handled differently).

Using the Code

The code is pretty simple to use. You construct a new instance of the OutlookStorage.Message class, sending it the path to a msg file or a Stream containing an IStorage. The Stream constructor is provided so that it is easy to integrate with the Outlook drag and drop code in another of my articles and this is shown in the demo application.

C#
private static void main()
{
    //create new Outlook message from file
    OutlookStorage.Message outlookMsg = new OutlookStorage.Message(@"C:\test.msg");
    DisplayMessage(outlookMsg);
}

private static void DisplayMessage(OutlookStorage.Message outlookMsg)
{    
    Console.WriteLine("Subject: {0}", outlookMsg.Subject);
    Console.WriteLine("Body: {0}", outlookMsg.BodyText);
    
    Console.WriteLine("{0} Recipients", outlookMsg.Recipients.Count);    
    foreach (OutlookStorage.Recipient recip in outlookMsg.Recipients)
    {
        Console.WriteLine(" {0}:{1}", recip.Type, recip.Email);
    }
    
    Console.WriteLine("{0} Attachments", outlookMsg.Attachments.Count);
    foreach (OutlookStorage.Attachment attach in outlookMsg.Attachments)
    {
        Console.WriteLine(" {0}, {1}b", attach.Filename, attach.Data.Length);
    }

    Console.WriteLine("{0} Messages", outlookMsg.Messages.Count);
    foreach (OutlookStorage.Message subMessage in outlookMsg.Messages)
    {
        DisplayMessage(subMessage);
    }
}

Save a Msg and All Attachments to the File System

This is an example on how to save a message and all associated attachments to the application path.

C#
private static void main()
{
    //create new Outlook message from file
    OutlookStorage.Message outlookMsg = new OutlookStorage.Message(@"C:\test.msg");
}

private static void SaveMessage(OutlookStorage.Message outlookMsg)
{    
    outlookMsg.Save(outlookMsg.Subject.Replace(":", ""));    

    foreach (OutlookStorage.Attachment attach in outlookMsg.Attachments)
    {
        byte[] attachBytes = attach.Data;
        FileStream attachStream = File.Create(attach.Filename);
        attachStream.Write(attachBytes, 0, attachBytes.Length);
        attachStream.Close();
    }

    foreach (OutlookStorage.Message subMessage in outlookMsg.Messages)
    {
        SaveMessage(subMessage);
    }
}

Understanding the Code

To read the msg file produced by Outlook, there are two concepts to understand. The first is that an msg file is logically a MAPI object with MAPI properties and the second is that phyiscally the MAPI object and its properties are stored in an IStorage. Microsoft has kindly provided a specification on how the MAPI properties are mapped to the IStorage, so at this point I will defer to that and just go over the catches that popped up when figuring out how to save a sub message out of its parent.

Saving a Sub Message

Saving a sub message out of the parent message has a few catches. The property stream header needs to be padded and the name to id mapping storage needs to be copied to the sub message storage.

Fixing the Property Stream

MAPI property values can be stored in a sub storage, a sub stream or in the case of fixed size values (like an integer) a special sub stream called the property stream. The property stream consists of a variable length header and then an array of 16 byte pairs with a property identifier in the first 8 bytes and the property value in the second 8.

It is the variable length header that you should take note of. It is 8 bytes for an attachment or recipient storage, 32 bytes for a top level msg and 24 bytes for a sub msg. This means that if you want to extract a sub message and save it without its parent you need to pad the end of the header with 8 null bytes.

The Name to Id Mapping

The other catch for saving a sub message is the name to id mapping storage which only exists on the top level msg, but contains the mappings for the entire tree. So when saving a sub message, this storage needs to be copied to it before saving for it to be valid.

Conclusion

Everything is encapsulated in the OutlookStorage.cs file as I don't like to release stuff with dependencies and prefer to just be able to drop a CS into my projects to get a particular piece of functionality. There is a region in there under a separate licence for the code to decompress the compressed RTF, but it is clearly marked.

History

  • 8th July, 2010
    • Fixed memory leak
    • Fixed the "COM object that has been separated from its underlying RCW cannot be used" exception
    • Added better determination of attachment file name
  • 28th January, 2009: Original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Guava Development
Australia Australia
I am the Founder of Guava Development a Software Services Company located in Perth, Western Australia dedicated to improving productivity and reducing costs through the targeted and innovative application of software assisted workflows and packages.

I have been working in the industry for 10 years. My day job usually involves programming with C# but I have been known to mess around with just about everything.

Comments and Discussions

 
Generalvb.net Version of this code Pin
Carsten Giesen27-Oct-09 7:00
Carsten Giesen27-Oct-09 7:00 
GeneralExample is throwing the following error. Pin
GodSendHockey25-Oct-09 11:33
GodSendHockey25-Oct-09 11:33 
GeneralRe: Example is throwing the following error. Pin
Member 98864056-Mar-13 6:47
Member 98864056-Mar-13 6:47 
GeneralCOM object that has been separated from its underlying RCW cannot be used PinPopular
BrianAtSyston2-Oct-09 1:31
BrianAtSyston2-Oct-09 1:31 
GeneralRe: COM object that has been separated from its underlying RCW cannot be used Pin
mel250622-Dec-09 22:42
mel250622-Dec-09 22:42 
GeneralRe: COM object that has been separated from its underlying RCW cannot be used Pin
Darren Stoppler30-May-10 11:59
Darren Stoppler30-May-10 11:59 
GeneralRe: COM object that has been separated from its underlying RCW cannot be used Pin
Brian Bober30-May-10 23:55
Brian Bober30-May-10 23:55 
GeneralRe: COM object that has been separated from its underlying RCW cannot be used [modified] Pin
Brian Bober1-Jun-10 16:22
Brian Bober1-Jun-10 16:22 
As mentioned in response to one of the existing replies I had this issue as well (code going through 5000 emails), and though I couldn't get it to go away completely, I found out two useful things:
1) If I try to access the same email two separate times, the issue happens more often. For instance, if I treat OutlookStorage.Message as randomly accessible data for each file sequentially (still not trying to access more than one email at a time), the error happens more often. I looked in sysinternals' File Monitor and saw the same file being accessed over a dozen times. Instead, I changed my code to just wrap the OutlookStorage.Message in a model which makes a read-only duplicate of all the data, summarily deleting the OutlookStorage.Message (setting it to null) so it can be garbage collected. This didn't completely eliminate the errors, but it happens less often, usually right away so I can just keep refreshing the server's page until I don't get the error.
2) I was using ASPX and not Windows forms and running through Microsoft Visual Studio debug tool, which created the fake server for testing. I found that if I got the issue then I usually was best off stopping the server and allowing MSVC to initiate a new server.


Edit:

I think I found the solution!

Solution seems to be to comment line 1412 in OutlookStorage.cs.

I tracked this down to the Dispose method in OutlookStorage.cs (line 1412) GetStreamBytes (line 1208) both having a Marshal.ReleaseComObject, so the COM object is being de-referenced too many times. What happens is that if you read multiple files, you are incrementing the reference and then and immediate dereference, then when the OutlookStorage class is garbage collected, the COM object is again dereferenced in Dispose(). The random nature of this happening sometimes and not other is really just a matter of whether the garbage collector has time to catch up. If it catches up, then the program using the sample code provided here is going to have a fatal exception. For instance, when I added code to fill a Hashtable between reads of emails, it happened more often. My suspicion is the GC had more of a chance to catch up.

Comment out only line 1412 as shown below. Don't comment out the other lines or you get a memory leak!
ReferenceManager.RemoveItem(this.storage);
//Marshal.ReleaseComObject(this.storage);
GC.SuppressFinalize(this);

Of course, I don't know if there are any unintended side-effects. For instance, I'm not using GetStreamAsString so I don't know if that still works with this solution.

Perhaps someone with more knowledge of interop can take my solution a little farther and see if it is completely safe or if some adjustments are needed.

modified on Wednesday, June 2, 2010 12:42 AM

GeneralRe: COM object that has been separated from its underlying RCW cannot be used Pin
Brian Bober2-Jun-10 5:02
Brian Bober2-Jun-10 5:02 
QuestionOutlook folders ? Pin
arnott0716-Sep-09 4:23
arnott0716-Sep-09 4:23 
GeneralEmbeded attachments Pin
scalony2-Sep-09 22:56
scalony2-Sep-09 22:56 
GeneralMemory leak problem Pin
kadirardic25-Aug-09 1:37
kadirardic25-Aug-09 1:37 
GeneralRe: Memory leak problem Pin
student00719-Nov-09 6:22
student00719-Nov-09 6:22 
QuestionHow to get Sent property of .msg file Pin
kadirardic17-Aug-09 23:10
kadirardic17-Aug-09 23:10 
AnswerRe: How to get Sent property of .msg file Pin
Marcus Olsson26-Apr-10 22:46
Marcus Olsson26-Apr-10 22:46 
GeneralRe: How to get Sent property of .msg file Pin
Marcus Olsson26-Apr-10 23:29
Marcus Olsson26-Apr-10 23:29 
GeneralOpen up the attachments Pin
ajackson2617-Aug-09 9:04
ajackson2617-Aug-09 9:04 
GeneralRe: Open up the attachments Pin
ajackson2617-Aug-09 11:20
ajackson2617-Aug-09 11:20 
GeneralPossible Mem leak Pin
IrishWizKid13-Jul-09 6:37
IrishWizKid13-Jul-09 6:37 
GeneralPossible Mem Leak Pin
IrishWizKid13-Jul-09 6:15
IrishWizKid13-Jul-09 6:15 
GeneralRe: Possible Mem Leak Pin
Member 213923225-Nov-09 2:53
Member 213923225-Nov-09 2:53 
GeneralThreading Pin
IrishWizKid13-Jul-09 5:27
IrishWizKid13-Jul-09 5:27 
GeneralRe: Threading Pin
David Ewen13-Jul-09 12:35
professionalDavid Ewen13-Jul-09 12:35 
QuestionAttachment Filename and ContentId null Pin
Ernesto Tejeda27-Jun-09 7:16
Ernesto Tejeda27-Jun-09 7:16 
AnswerRe: Attachment Filename and ContentId null Pin
Ernesto Tejeda28-Jun-09 16:54
Ernesto Tejeda28-Jun-09 16:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.