Is it possible to read a .PST file using C#? I would like to do this as a standalone application, not as an Outlook addin (if that is possible).
If have seen other SO questions similar to this mention MailNavigator but I am looking to do this programmatically in C#.
I have looked at the Microsoft.Office.Interop.Outlook namespace but that appears to be just for Outlook addins. LibPST appears to be able to read PST files, but this is in C (sorry Joel, I didn't learn C before graduating).
Any help would be greatly appreciated, thanks!
EDIT:
Thank you all for the responses! I accepted Matthew Ruston's response as the answer because it ultimately led me to the code I was looking for. Here is a simple example of what I got to work (You will need to add a reference to Microsoft.Office.Interop.Outlook):
using System;
using System.Collections.Generic;
using Microsoft.Office.Interop.Outlook;
namespace PSTReader {
class Program {
static void Main () {
try {
IEnumerable<MailItem> mailItems = readPst(#"C:\temp\PST\Test.pst", "Test PST");
foreach (MailItem mailItem in mailItems) {
Console.WriteLine(mailItem.SenderName + " - " + mailItem.Subject);
}
} catch (System.Exception ex) {
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
private static IEnumerable<MailItem> readPst(string pstFilePath, string pstName) {
List<MailItem> mailItems = new List<MailItem>();
Application app = new Application();
NameSpace outlookNs = app.GetNamespace("MAPI");
// Add PST file (Outlook Data File) to Default Profile
outlookNs.AddStore(pstFilePath);
MAPIFolder rootFolder = outlookNs.Stores[pstName].GetRootFolder();
// Traverse through all folders in the PST file
// TODO: This is not recursive, refactor
Folders subFolders = rootFolder.Folders;
foreach (Folder folder in subFolders) {
Items items = folder.Items;
foreach (object item in items) {
if (item is MailItem) {
MailItem mailItem = item as MailItem;
mailItems.Add(mailItem);
}
}
}
// Remove PST file from Default Profile
outlookNs.RemoveStore(rootFolder);
return mailItems;
}
}
}
Note: This code assumes that Outlook is installed and already configured for the current user. It uses the Default Profile (you can edit the default profile by going to Mail in the Control Panel). One major improvement on this code would be to create a temporary profile to use instead of the Default, then destroy it once completed.
The Outlook Interop library is not just for addins. For example it could be used to write a console app that just reads all your Outlook Contacts. I am pretty sure that the standard Microsoft Outlook Interop library will let you read the mail - albeit it will probably throw a security prompt in Outlook that the user will have to click through.
EDITS: Actually implementing mail reading using Outlook Interop depends on what your definition of 'standalone' means. The Outlook Interop lib requires Outlook to be installed on the client machine in order to function.
// Dumps all email in Outlook to console window.
// Prompts user with warning that an application is attempting to read Outlook data.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace OutlookEmail
{
class Program
{
static void Main(string[] args)
{
Outlook.Application app = new Outlook.Application();
Outlook.NameSpace outlookNs = app.GetNamespace("MAPI");
Outlook.MAPIFolder emailFolder = outlookNs.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem item in emailFolder.Items)
{
Console.WriteLine(item.SenderEmailAddress + " " + item.Subject + "\n" + item.Body);
}
Console.ReadKey();
}
}
}
I went through and did the refactoring for subfolders
private static IEnumerable<MailItem> readPst(string pstFilePath, string pstName)
{
List<MailItem> mailItems = new List<MailItem>();
Microsoft.Office.Interop.Outlook.Application app = new Microsoft.Office.Interop.Outlook.Application();
NameSpace outlookNs = app.GetNamespace("MAPI");
// Add PST file (Outlook Data File) to Default Profile
outlookNs.AddStore(pstFilePath);
string storeInfo = null;
foreach (Store store in outlookNs.Stores)
{
storeInfo = store.DisplayName;
storeInfo = store.FilePath;
storeInfo = store.StoreID;
}
MAPIFolder rootFolder = outlookNs.Stores[pstName].GetRootFolder();
// Traverse through all folders in the PST file
Folders subFolders = rootFolder.Folders;
foreach (Folder folder in subFolders)
{
ExtractItems(mailItems, folder);
}
// Remove PST file from Default Profile
outlookNs.RemoveStore(rootFolder);
return mailItems;
}
private static void ExtractItems(List<MailItem> mailItems, Folder folder)
{
Items items = folder.Items;
int itemcount = items.Count;
foreach (object item in items)
{
if (item is MailItem)
{
MailItem mailItem = item as MailItem;
mailItems.Add(mailItem);
}
}
foreach (Folder subfolder in folder.Folders)
{
ExtractItems(mailItems, subfolder);
}
}
As already mentioned in one of your linked SO questions, I'd also recommend using the Redemption library. I'm using it in a commercial application for processing Outlook mails and performing various tasks with them. It's working flawlessly and prevents showing up the annoying security alerts. It would mean using COM Interop, but that shouldn't be a problem.
There's a library in that package called RDO which is replacing the CDO 1.21, which lets you access PST files directly. Then it's as easy as writing (VB6 code):
set Session = CreateObject("Redemption.RDOSession")
'open or create a PST store
set Store = Session.LogonPstStore("c:\temp\test.pst")
set Inbox = Store.GetDefaultFolder(6) 'olFolderInbox
MsgBox Inbox.Items.Count
You can use pstsdk.net: .NET port of PST File Format SDK library which is open source to read pst file without Outlook installed.
Try Pstxy.
It provide .Net API to read Outlook PST & OST file without need for Outlook installed.
It has a free version to extract mail content (text, html & rtf). The plus version support attachments, too.
For those mentioning that they don't see the Stores collection:
The Stores collection was added in Outlook 2007. So, if you're using an interop library created from an earlier version (in an attempt to be version independent - this is ver common) then this would be why you won't see the Stores collection.
Your only options to get the Stores are to do one of the following:
Use an interop library for Outlook 2007 (this means your code won't work for earlier versions of Outlook).
Enumerate all top level folders with Outlook object model, extract the StoreID of each folder, and then use CDO or MAPI interfaces to get more information about each store.
Enumerate the InfoStores collection of CDO session object, and then use the fields collection of InfoStore object in order to get more information about each store.
Or (the hardest way) use extended MAPI call (In C++): IMAPISession::GetMsgStoresTable.
We are going to use this, to provide a solution that doesn't rely on outlook.
http://www.independentsoft.de/pst/index.html
It is very expensive, but we hope that will lower development time and increase quality.
I found some resources directly from Microsoft which may be helpful for completing this task. A search on MSDN reveals the following.
How to use the Microsoft Outlook Object Library to retrieve a message from the Inbox by using Visual C#
How to use the Microsoft Outlook Object Library to retrieve an appointment by using Visual C#
Note that when you're adding a reference to Microsoft.Office.Interop.Outlook, the documentation insists that you do so via the .NET tab instead of the COM tab.
The MAPI API is what you are looking for. Unfortunately it is not available in .Net so I'm afraid you will have to resort to calling unmanaged code.
A quick Google reveals several wrappers available, maybe they work for you?
This might also be helpful: http://www.wischik.com/lu/programmer/mapi_utils.html
This .NET connector for Outlook might get you started.
Yes, with Independentsoft PST .NET is possible to read/export password protected and encrypted .pst file.
Really usefull code. If you have pst and store your messages in its root (without any directory), then you can use the following in method readPst:
MAPIFolder rootFolder = outlookNs.Stores[pstName].GetRootFolder();
Items items = rootFolder.Items;
foreach (object item in items)
{
if (item is MailItem)
{
MailItem mailItem = item as MailItem;
mailItems.Add(mailItem);
}
}
Yes you can use MS Access and then you either import your pst content or just link it (slow!).
Related
I am trying to search for specific emails that have a particulary subject.
Outlook.Folder inbox = new Outlook.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
Outlook.Items items = inbox.Items;
Outlook.MailItem mailItem = null;
object folderItem;
string subjectName = string.Empty;
string filter = "[Subject] > 's' And [Subject] <'u'";
folderItem = items.Find(filter);
while (folderItem != null)
{
mailItem = folderItem as Outlook.MailItem;
if (mailItem != null)
{
subjectName += "\n" + mailItem.Subject;
}
folderItem = items.FindNext();
}
subjectName = "The follow e-mail messages were found: " + subjectName;
MessageBox.Show(subjectName);
I am getting an error :
"Severity Code Description Project File Line Suppression State
Error CS0426 The type name 'ActiveExplorer' does not exist in the type 'Application'"
You need to create a new Application instance first if you develop a standalone application where Outlook is automated or use the built-in property if you develop a VSTO based add-in instead of the following code:
Outlook.Folder inbox = new Outlook.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
A standalone application should create a new Outlook instance:
Outlook.Application app = new Outlook.Application();
Outlook.Explorer explorer = app.ActiveExplorer();
Outlook.Namespace ns = app.GetNamespace("MAPI");
Outlook.Folder inbox = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
In case of VSTO add-in you can use the Application property of the ThisAddin class:
Outlook.Explorer explorer = Application.ActiveExplorer();
Outlook.Namespace ns = app.GetNamespace("MAPI");
Outlook.Folder inbox = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
You can read more about the Find/FindNext or Restrict methods of the Items class in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Also you may find the AdvancedSearch method helpful. The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
I am not sure why you are even accessing ActiveExplorer - you are not using it, and if Outlook was not running prior, there won't be any open explorers (and inspectors), so ActiveExplorer will return null anyway.
Also keep in mind that Application.Session will be null unless Outlook is already running - you need to log in first.
Thirdly, you are not calling a constructor - that would be new Outlook.Application().Blah (note ()).
Change your code to
Outlook.Application app = new Outlook.Application();
Outlook.Namespace session = app.GetNamespace("MAPI");
session.Logon();
Outlook.Folder inbox = session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
I am performing custom action on all incoming replies through VSTO add-in. The add-in will compare ConversationID of incoming reply with existing email. It works fine if I have to search inside one folder but my problem is email can be in any folder in store. Here is my code.
void items_ItemAdd(object Item)
{
Outlook.Application application = new Outlook.Application();
string filter = "RE: ";
Outlook.MailItem mail = (Outlook.MailItem)Item;
Outlook.Folder folder = mail.Parent as Outlook.Folder;
if (Item != null)
{
if (mail.MessageClass == "IPM.Note" && mail.Subject.ToUpper().Contains(filter.ToUpper()))
{
var RequiredMail = (from e in folder.Items.Cast<Outlook.MailItem>().OrderBy(X => X.ReceivedTime).Where(C => C.ConversationID == mail.ConversationID) select mail).FirstOrDefault();
// Perform custom action
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
Also, I have read that searching for an email using Linq is not very efficient. Is there any other more efficient way to get RequiredMail?
Any help will be highly appreciated.
Thank you.
First of all, you must be aware that ItemAdd event may not be fired if more than sixteen items are added to the collection. This is a known issue in Outlook. The following series of articles describes possible workarounds for that:
Outlook NewMail event unleashed: the challenge (NewMail, NewMailEx, ItemAdd)
Outlook NewMail event: solution options
Outlook NewMail event and Extended MAPI: C# example
Outlook NewMail unleashed: writing a working solution (C# example)
Mixing LINQ and COM objects is not a really good idea. You should release underlying COM objects instantly to prevent any known issues.
If you need to search for items in all folders you may use the AdvancedSearch method of the Application class which allows to perform a search based on a specified DAV Searching and Locating (DASL) search string.
The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Read more about that in the Advanced search in Outlook programmatically: C#, VB.NET article.
I am using Outlook.Application and Outlook.MailItem object for opening Outlook in my C# desktop application. My outlook doesn't display attachments although when I send the mail to myself, I receive mail with attachments. But it is not showing before sending mail (when outlook is open). I am using Outlook 2007. Below is my code:
Outlook.Application oApp = new Outlook.Application();
Outlook.NameSpace oNS = oApp.GetNamespace("mapi");
// Log on by using a dialog box to choose the profile.
oNS.Logon(Missing.Value, Missing.Value, true, true);
// Create a new mail item.
Outlook.MailItem oMsg = (Outlook.MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem);
......
//Check if we need to add attachments
if (_files.Count > 0)
{
foreach (string attachment in _files)
{
oMsg.Attachments.Add(attachment,Outlook.OlAttachmentType.olByValue,null,null);
}
}
oMsg.Save();
oMsg.Display(false);
Of course, Type.Missing is used to omit the parameter and use the default value in COM add-ins.
Also I'd suggest breaking the chain of calls and declaring each property or method call on a separate line of code. It will allow to release each underlying COM object instantly.
Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. This is particularly important if your add-in attempts to enumerate more than 256 Outlook items in a collection that is stored on a Microsoft Exchange Server. If you do not release these objects in a timely manner, you can reach the limit imposed by Exchange on the maximum number of items opened at any one time. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about that in the Systematically Releasing Objects article in MSDN.
I understand that user can not move messages cross EAS account boundaries. Moving messages inside the same EAS account is perfectly fine when done manually from Outlook windows, but fails when done through automation objects. What's wrong here?
Outlook.MailItem item = Outlook.Namespace.GetItemFromID(MailItemEntryEid, MailItemStoreEid);
Outlook.MAPIFolder folder = Outlook.Namespace.GetFolderFromID(MAPIFolderEntryEid, MailItemStoreEid);
Outlook.MailItem newItem = item.Move(folder);
both item and folder objects constructed correctly, and belong to the same EAS store, however .Move on the last line is failing with this error:
(0x80040102): Sorry, Exchange ActiveSync doesn't support what you're trying to do.
If I do item.Delete() that moved the item to the Deleted Items folder
The "0x80004005" or "0x80040102" error message when you access a mailbox on an Exchange Server 2010 server by using a MAPI client article describes a similar issue. Do you work with public folders?
Anyway, you can use the Copy method instead and then Delete the source item.
Try to use MailItem.Copy followed by MailItem.Move before calling Save:
set Item = Application.ActiveExplorer.Selection(1)
set Target = Application.Session.GetDefaultFolder(olFolderDrafts)
set newItem = Item.Copy
set newItem = newItem.Move(Target)
Okay, I have no problem identifying the .PST file using the Outlook Interop assemblies in a C# app. But as soon as I hit a password protected file, I am prompted for a password.
We are in the process of disabling the use of PSTs in our organization and one of the steps is to unload the PST files from the users' Outlook profile.
I need to have this app run silently and not prompt the user. Any ideas? Is there a way to create the Outlook.Application object with no UI and then just try to catch an Exception on password protected files?
// create the app and namespace
Application olApp = new Application();
NameSpace olMAPI = olApp.GetNamespace("MAPI");
// get the storeID of the default inbox
string rootStoreID = olMAPI.GetDefaultFolder(OlDefaultFolders.olFolderInbox).StoreID;
// loop thru each of the folders
foreach (MAPIFolder fo in olMAPI.Folders)
{
// compare the first 75 chars of the storeid
// to prevent removing the Inbox folder.
string s1 = rootStoreID.Substring(1, 75);
string s2 = fo.StoreID.Substring(1, 75);
if (s1 != s2)
{
// unload the folder
olMAPI.RemoveStore(fo);
}
}
olApp.Quit();
Yes you can automate outlook from another app.
There is a Logon method on the NameSpace object so that you can logon to the profile then you can do anything that you want. But I think that it will just pop up the prompt again as it automation but ..There is a 3rd libray that may help you as well to do this as it does it via mapi instead. checkout profman.dll in the redemption libray