Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Outlook PST Email Extraction

0.00/5 (No votes)
30 Mar 2016 1  
Extract Microsoft Outlook messages from a PST into a folder structure

Introduction

Eventually, there may come a time where we want to convert our Outlook PST (Personal Storage File) files to live on our hard drives as Outlook .msg files instead of within a PST file itself. Perhaps your company prevents the use of PST files or perhaps it has just grown out of control. Moving messages from the PST file to the hard drive also may give us better searchability, using Windows Search. This article will quickly show you how to move email messages from a PST file to your hard drive.

Background

To deal with Outlook in a .NET application, we need an interface named MAPI. MAPI stands for Messaging Application Programming Interface and is provided by Microsoft for Outlook programming. Using MAPI, we can access folders like Inbox, Drafts, Sent Items and so on that reside in a PST file. We can even create sessions and namespace to fetch items from Outlook or, say, from a PST file. Currently, MAPI is the only namespace provided by Microsoft to extract PST files. For further information on MAPI, I suggest newbies visit the relevant MSDN article.

Using the Code

To get started:

  1. Open Visual Studio and create a new Console application.
  2. When the console application is ready, assuming you have Outlook installed (this is a necessity for this article), you'll need to add a reference to the Microsoft.Office.Interop.Outlook.dll assembly (mine was found at C:\Program Files (x86)\Microsoft Visual Studio 10.0\Visual Studio Tools for Office\PIA\Office14).
  3. Copy/paste the following code and execute it.

The code will prompt you for a file path to write the main items to, then to choose a PST file already added to your Outlook instance, recursively iterate all folders and subfolders, and finally extract each mailItem to the root folder defined.

Alternatively, if you'd rather choose a disconnected PST file, you could update the code to manually add and remove the PST file by using the AddStore/RemoveStore method of the outlookNs namespace object and define a path for it. The rest of the code would be basically the same.

using System;
using Microsoft.Office.Interop.Outlook;
using System.Text.RegularExpressions;
 
namespace PSTMover
{
    class Program
    { 
        /// <summary>
        /// Starting method for console app.
        /// </summary>
        /// <param name="args">The arguments.</param>
        static void Main(string[] args)
        {
            Console.WriteLine("Enter folder path to write 
            PST Mail Items to (i.e. c:\\temp\\Email): ");
            String OutputRootPath = Console.ReadLine();
            if (System.IO.Directory.Exists(OutputRootPath) == false)
                return;
            WritePSTFilesToFolder(OutputRootPath);
        }
 
        /// <summary>
        /// Writes the PST files to folder.
        /// </summary>
        /// <param name="PSTPath">The PST path.</param>
        /// <param name="OutputPath">The output path.</param>
        private static void WritePSTFilesToFolder(String OutputPath)
        {
            Application app = new Application();
            NameSpace outlookNs = app.GetNamespace("MAPI");
            MAPIFolder RootFolder = outlookNs.PickFolder();
            if(RootFolder!=null) //It may be NULL if you press the Cancel button
                // Traverse through all folders in the PST file
                foreach (MAPIFolder SubFolder in RootFolder.Folders)
                {
                    Iterate(SubFolder, OutputPath);
                }
        }
 
        /// <summary>
        /// Iterates and recurses the specified root folder.
        /// </summary>
        /// <param name="rootFolder">The root folder.</param>
        /// <param name="OutputPath">The output path.</param>
        private static void Iterate(MAPIFolder RootFolder, String OutputPath)
        {
            //First, write any email items that may exist at root folder level
            OutputPath = OutputPath + RemoveFileNameSpecialChars(RootFolder.Name) + @"\";
            WriteEmails(RootFolder, OutputPath);
 
            //Recurse Subfolders
            foreach (MAPIFolder SubFolder in RootFolder.Folders)
            {   
                Iterate(SubFolder, OutputPath);
            }
        }
 
        /// <summary>
        /// Writes the emails to the folder
        /// </summary>
        /// <param name="Folder">The folder.</param>
        /// <param name="OutputPath">The output path.</param>
        private static void WriteEmails(MAPIFolder Folder, String OutputPath)
        {
            Items items = Folder.Items;
            foreach (object item in items)
            {
                if (item is MailItem)
                {
                    // Retrieve the Object into MailItem
                    MailItem mailItem = item as MailItem;
                    Console.WriteLine("Saving message {0} .... into {1}", mailItem.Subject, OutputPath);
                    // Save the message to disk in MSG format
                    if (System.IO.Directory.Exists(OutputPath) == false)
                    {
                        System.IO.Directory.CreateDirectory(OutputPath);
                    }
                    try
                    {
                        String Subject = mailItem.Subject;
                        if (Subject == null)
                            Subject = "NULL";
                        String FilePathName = OutputPath + RemoveFileNameSpecialChars
                        (Subject.Replace('\u0009'.ToString(), "")) + ".msg"; //removes tab chars
                        mailItem.SaveAs(FilePathName, OlSaveAsType.olMSG);
                        System.IO.File.SetCreationTime(FilePathName, mailItem.ReceivedTime);
                        System.IO.File.SetLastWriteTime(FilePathName, mailItem.ReceivedTime);
                    }
                    catch (System.Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }
        }
 
        /// <summary>
        /// Returns a string after removing characters that are NOT: 
        /// alphanumeric, space, dash, apostrophe, period or comma  
        /// </summary>
        /// <param name="FileName">original file name</param>
        /// <returns>String</returns>
        public static String RemoveFileNameSpecialChars(string FileName)
        {
            Regex regex = new Regex(@"[\w\s-'.,]");
 
            String validName = FileName;
            //identify invalid chars and replace those within the FileName string
            for (int i = 0; i < FileName.Length; i++)
            {
                Boolean matched = regex.IsMatch(FileName[i].ToString());
                if (matched == false)
                {
                    validName = validName.Replace(FileName[i].ToString(), "");
                }
            }
 
            return validName;
        } 
    }
}

History

  • 30th March, 2016 -- Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here