Introduction

A project came up at work the other day requiring the ability to drag and drop any number of mail messages or mail message attachments from Otlook into a WinForms application... Easy I thought, this has to be a common problem. I will just jump on CodeProject, find an example, and with a bit of tweaking, be running in no time. Now, if you are here, I am sure you now know how little information there is available on this topic, so I thought I would add to the very small pool with a complete example of how to drag and drop mail items or attachments from Outlook into a WinForms application without using the Outlook object model.

I am going to skip over explaining all the creating a form, allowing drag and drop, etc., stuff that you can find a million places on the Internet, and just focus on the code to make drag and drop from Outlook work. If you feel lost, go find another article with more basic drag and drop information, get a fully functional drag and drop app, then come back here and work from that code.

Using the Code

When I started writing the code, I decided the easiest way to work the functionality into the existing application was to create a new class that implemented the IDataObject interface that is normally provided when dragging and dropping onto a WinForm. The new class was to catch any calls to Outlook specific data formats, and pass all other calls through to the original IDataObject. Below, you can see how easy it is to use the class in a DragDrop event handler. The FileGroupDescriptor format returns a string array containing the names of each file dropped instead of the usual MemoryStream you would be used to if you have tackled this yourself, and the FileContents returns a MemoryStream array containing the binary contents of each file dropped.

Understanding the Code

To understand what the OutlookDataObject class above is doing to get the file information, there are two things to take note of. The first is that information for the file names is returned from Outlook in a MemoryStream, which is actually a representation of the FILEGROUPDESCRIPTORA or FILEGROUPDESCRIPTORW structures. The second is that to get the file contents, you need the ability to specify an index to get anything past the first file, and the standard IDataObject does not expose this ability. All this is explained in detail below.

Getting the File Names

There are two versions of the file details returned from the IDataObject in the FileGroupDescriptor and FileGroupDescriptorW formats which map to the FILEGROUPDESCRIPTORA and FILEGROUPDESCRIPTORW structures, respectively. In this article, I will focus on the FileGroupDescriptor format which is the ASCII version; FileGroupDescriptorW (W for wide) is the Unicode version, and you will need to use it when working with non-ASCII file names, but they are handled in the same way.

//use the IDataObject to get the FileGroupDescriptor as a MemoryStream
MemoryStream fileGroupDescriptorStream = (MemoryStream)e.Data.GetData("FileGroupDescriptor");

Most examples you will see involve taking the MemoryStream above and converting each non-null byte from index 76 onwards to a char and appending that to a string. While this works adequately for one file drop, it gets a bit tricky when dropping more than that. The correct way is to take the returned bytes and cast it to a FILEGROUPDESCRIPTORA structure, which holds a count of items and an array of FILEDESCRIPTORA structures, which holds the file details. The definitions of these structures can be seen below.

Now that the foundations are set, let's get into the code to actually convert the returned MemoryStream into something usable like a string array of file names. This involves putting the raw bytes into unmanaged memory and using Marshal.PtrToStructure to bring it back in as a structure. There is some extra marshalling in the code because the fgd array of the FILEGROUPDESCRIPTORA structure doesn't get populated as the PtrToStructure method doesn't work with variable length arrays.

At this point, we have now converted the MemoryStream into a string array that contains the name of each file dropped, which is a lot easier to work with. Outlook messages get the name of their subject with ".msg" on the end, and for Outlook message attachments, the file name of the attachment.

Getting the File Contents

The file contents sit behind the FileContents format. If you drag and drop a single attachment, then the default IDataObject works as expected and will return a MemoryStream containing that file's data. Things get more complex when dragging multiple attachments or Outlook email messages for different reasons. Multiple attachments pose an issue because the OS calls for drop data allow for an index to be specified, but the C# implementation of the IDataObject doesn't expose this directly. Mail messages are an issue because the OS call returns an IStorage which is a compound file type, and again, the C# implementation of the IDataObject lets us down by not handling this type of return, so you get a null.

Specifying an Index

To get at the content of multiple dropped files, an index needs to be specified to indicate which file contents are required. The default IDataObject doesn't allow this, but it can be cast to a COM IDataObject which will accept a FORMATETC structure that has an index property that can be set to indicate the file contents required.

As you can see in the example above, by changing the value of the lindex property of the FORMATETC structure, we can change the index of the file contents to retrieve. The result of the call is sitting in the STGMEDIUM structure; this contains a pointer to the actual result in the unionmember property, and the type of result at the pointer in the tymed property. There are three types of returns available to the STGMEDIUM, and each one is explained below.

The Stream Result (TYMED_ISTREAM)

If the tymed property of the STGMEDIUM is TYMED_ISTREAM, then the result is a stream. This is normally handled by the default IDataObject, but when working with the COM IDataObject, the handling code needs to be written again.

//marshal the returned pointer to a IStream object
IStream iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//get the STATSTG of the IStream to determine how many bytes are in it
iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iStream.Stat(out iStreamStat, 0);
int iStreamSize = (int)iStreamStat.cbSize;
//read the data from the IStream into a managed byte array
byte[] iStreamContent = newbyte[iStreamSize];
iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);
//wrapped the managed byte array into a memory stream
Stream filestream = new MemoryStream(iStreamContent);

The Storage Result (TYMED_ISTORAGE)

If the tymed property of the STGMEDIUM is TYMED_ISTORAGE, then the result is a storage which is a compound file type. This is a little more complex to process than the stream as it needs to be copied into a memory backed IStorage so its data can then be read from the backing memory store.

The HGlobal Result (TYMED_HGLOBAL)

If the tymed property of the STGMEDIUM is TYMED_HGLOBAL, then the result is stored in a HGlobal. For the purposes of Outlook drag and drop, this type should never be returned, but for completeness, I use a little bit of Reflection on the original IDataObject to have that class handle it.

Conclusion

Well, hopefully, that all helps someone. I have a few other Outlook tricks that I will be doing articles for; one is how to extract and save a message attachment without using the object model, another is how to use the code in this article to enable drag and drop of Outlook messages and attachments into IE (with appropriate security, so only good for the intranet).

Share

About the Author

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.

Thank you very much.... its working fine for Windows to work in WPF need few changes,Change the interface from System.Windows.Forms.IDataObject to System.Windows.IDataObject and change the "innerData" to "_innerData"

Hi , very good article please tell us , how did you find or build this solution and is there any way that we can check outlook folder and extract its siblings and save them as .msg and save its attachements as well.

First of all - what a fantastic piece of code! I made it into an ActiveX control and it works perfectly on a web page.

I am storing the email dropped into my control as a .msg file and all it's attachments as separate files.However, the original email still has these attachments as part of it's content. This doubles the amount of storage space required. Naturally, after extracting the attachments (and imbedded messages) into separate files - I would like to remove them from the original email. Does anyone have any insight into accomplishing this? I can matinuplate 'attachments' and 'messages' dictionaries but that has no impact on the actual 'storage' object. I tried storage.DestroyElement but that gives an error of STG_E_ACCESSDENIED.

My project is going into production in 3 weeks and this seems to be the only major issue left! I'd apprecicate any help or direction you can give.