Click here to Skip to main content
15,880,543 members
Articles / Database Development

Writing Email to the File of the PST Format

,
Rate me:
Please Sign up or sign in to vote.
4.67/5 (6 votes)
6 Aug 2010CPOL8 min read 49.1K   995   24   11
The aim of this article is to create the library for writing e-mail messages in the *.pst format (used by Microsoft Outlook) to the file.

Table of Contents

Introduction

The aim of this article is to create the library for writing e-mail messages in the *.pst format (used by Microsoft Outlook) to the file. This article will be interesting for those developers who develop various mail applications, which include the export to *.pst format functionality. This article is divided into two parts in order to make it easier for understanding and for better structuring. The first part introduces the common notions such as initialization, writing the message properties (sender, recipient, etc.); writing the text body of the message, and creation of the folders structure. The second part deals with more complicated notions such as writing the message attachment, RTF and HTML bodies of the message. The developed library is a sample and is not intended for usage in real applications. Its aim is to demonstrate the common principles of working with MAPI.

The Structure of the Article

The Extended MAPI part is devoted to the common information about the interface, its features and implementation.

The Implementation part deals with the key moments of implementation and the common architecture of the sample project.

The Example part describes how to build the project, use the library, and also provides the example of usage and the results of testing.

Technologies Used

  • C++. The library and the example are written in ?++ language using exceptions.
  • STLPort. A standard library (for more information, see http://www.stlport.org/).
  • Extended MAPI. MAPI (Messaging Application Programming Interface) is a messaging architecture and a Component Object Model based API for Microsoft Windows.

The Structure of the Project

  • bin - Binary files
  • lib - Library files
  • obj - Object files
  • src - Source files
  • | - PstWriter – Main library that provides the export functionality
  • | - TestApp – Test application that writes test message to the test PST file

Brief Survey of MAPI

Messaging Application Programming Interface (MAPI) is a messaging architecture and a Component Object Model based API for Microsoft Windows. MAPI allows client programs to become (e-mail) messaging-enabled, -aware, or -based by calling MAPI subsystem routines that interface with certain messaging servers. While MAPI is designed to be independent of the protocol, it is usually used with MAPI/RPC, the proprietary protocol that Microsoft Outlook uses to communicate with Microsoft Exchange.

Simple MAPI is a subset of 12 functions which enable developers to add basic messaging functionality. Extended MAPI allows complete control over the messaging system on the client computer, creation and management of messages, management of the client mailbox, service providers, and so forth. Simple MAPI ships with Microsoft Windows as part of Outlook Express/Windows Mail while the full Extended MAPI ships with Office Outlook and Exchange.

Implementation

This part describes in detail the work of the library with MAPI. The description and code pieces are also provided. The full version of the code is attached to the article. The code in the article does not include the check for errors and other parts, which are easy for understanding and do not play a significant role while working with MAPI.

MAPI Initialization

First, what you should do while working with MAPI is to initialize it. If you do not do this, all calls will return the error. To initialize MAPI, call the function:

C++
HRESULT MAPIInitialize(LPVOID lpMapiInit);

In the example, it is performed in the following way:

C++
MAPIINIT init = { 0, MAPI_MULTITHREAD_NOTIFICATIONS};
HRESULT hr = MAPIInitialize(&init);

The MAPI_MULTITHREAD_NOTIFICATIONS parameter means that MAPI should generate notifications using a thread dedicated to notification handling instead of the first thread used to call MAPIInitialize. It’s advisable to always define this parameter in order to avoid problems with threading. Also the MAPIUninitialize call should correspond to every MAPIInitialize call. The first should be called after the work with MAPI is finished. And this pair of methods should be called for each thread that uses MAPI.

The Opening of the File

The PST file is opened in several stages. All the work with the file is performed through MAPI and the application does not have direct access to it.

First, you should get an administration object for the service access:

C++
// Get a profile administration object.
hr = MAPIAdminProfiles(0, &m_pIProfAdmin);
…
DWORD dwStart = GetTickCount();
sprintf_s( m_profileName, 100,  "Temp%lX", dwStart );
// Creating profile
(void) m_pIProfAdmin->DeleteProfile( (LPTSTR)&m_profileName, 0 );
hr = m_pIProfAdmin->CreateProfile( (LPTSTR)&m_profileName, NULL, 0, 0 );
…
// Get a service administration object.
hr = m_pIProfAdmin->AdminServices((LPTSTR)&m_profileName, 0, 0, 0, &m_pSvcAdmin);

The m_profileName defines the name of the profile in this case. In order not to open the existing profile, the profile name is generated in a random way and the attempt to delete it is performed.

Then it is necessary to create and configure the service, which will work with our file:

C++
//Creating service
hr = m_pSvcAdmin->CreateMsgService( "MSUPST MS", "MAPI Pst Msg Store", 0, 0 )

// Configure the MS Personal Information Store per NewPST entries.
hr = m_pSvcAdmin->GetMsgServiceTable(0, &m_ptblSvc);

COM::AutoPtr<SRowSet,RowEraser> prows;
COM::AutoPtr<SRowSet,RowEraser> pRows;

enum {iSvcName, iSvcUID, iEnrtyID, cptaSvc};

//Now we should find created service.
SizedSPropTagArray (cptaSvc, ptaSvc) = { cptaSvc, 
	{ PR_SERVICE_NAME, PR_SERVICE_UID, PR_ENTRYID} };
HrQueryAllRows(m_ptblSvc, (LPSPropTagArray)&ptaSvc, NULL, NULL, 0, &pRows);

int cRows = pRows->cRows;

for(LPSRow pRow = pRows->aRow; pRow < pRows->aRow + cRows; ++pRow)
{
//Here is our service
m_MsgStoreUID = *((LPMAPIUID)pRow->lpProps[iSvcUID].Value.bin.lpb);
if(strcmp(pRow->lpProps[iSvcName].Value.lpszA, pszMsgService) == 0)
{
// Configuring PST Message Store
ULONG count = 0;
const ULONG nProps = 2;
SPropValue   rgval[nProps];

rgval[count].ulPropTag = PR_DISPLAY_NAME_W;
rgval[count].Value.lpszW = const_cast<wchar_t*>(storeDisplayName.c_str());

rgval[count].ulPropTag = PR_PST_PATH;
rgval[count].Value.lpszA = const_cast<char*>(path.c_str());

// Writing parameters
m_pSvcAdmin->ConfigureMsgService( &m_MsgStoreUID, 0, 0, nProps, rgval);
}
}

If everything is fine, the new PST file is created. It has the path path and the storeDisplayName display name (the Microsoft Outlook property).

The last thing that you should do is to get the object, which will be used for access to the so called Message Store.

C++
MAPILogonEx(0, m_profileName, NULL, MAPI_NEW_SESSION | 
	MAPI_EXTENDED | MAPI_NO_MAIL | MAPI_TIMEOUT_SHORT, &m_pses);

MapiTable ptable;
m_pses->GetMsgStoresTable(0, &ptable);

SizedSPropTagArray(3, columns) = { 3, { PR_DEFAULT_STORE, PR_ENTRYID, PR_DISPLAY_NAME } };

HrQueryAllRows(ptable, (LPSPropTagArray) &columns, NULL, NULL, 0, &prows);

IMsgStore * msgStore = 0;
m_pses->OpenMsgStore(0, prows->aRow[0].lpProps[1].Value.bin.cb, 
	(LPENTRYID)prows->aRow[0].lpProps[1].Value.bin.lpb, NULL, MDB_WRITE | 
	MAPI_DEFERRED_ERRORS | MAPI_BEST_ACCESS | MDB_NO_MAIL, & msgStore);

As a result, we receive the IMsgStore * msgStore, which will be used in practically all other MAPI calls.

Data Writing

The Creation of the Folders Hierarchy

When writing the message, you should define its path like folder1\folder2\folder3. Such path defines the location in the database hierarchy, where the message will be placed. To create the folder, you should open the root folder in the database and then create the required one.

So, first you should find the root folder:

C++
COM::AutoPtr<SPropValue,MapiEraser> pVal_EID;
::HrGetOneProp(store->GetMsgStore(),PR_IPM_SUBTREE_ENTRYID,&pVal_EID);

// Open the IPM subtree folder
ULONG ulObjType = 0;
MapiFolder currentFolder;
store->GetMsgStore()->OpenEntry(pVal_EID->Value.bin.cb,
	(LPENTRYID)pVal_EID->Value.bin.lpb,NULL,0x10,&ulObjType,
	reinterpret_cast<IUnknown**>(&currentFolder));

The store->GetMsgStore() returns the Message Store. The previous part of the article dedicated to the initialization was aimed to get it. As a result, you obtain the currentFolder object, which refers to the root directory of the database.

To create the subdirectory, perform the following:

C++
currentFolder->CreateFolder(FOLDER_GENERIC, 
(LPTSTR)name.c_str(), NULL, NULL, 1 | OPEN_IF_EXISTS | MAPI_UNICODE, &folder);

where name is the name of the subdirectory and folder is the object, which will refer to the created folder. The names of flags, which are passed to the method, are informative enough.

All created objects, which represent folders, are saved in the implementation of the library in order to improve performance. Though there is nothing to prevent you from opening them each time you write the message for memory saving.

Writing the Message

Some message abstraction is used in the library. For simplicity, it is represented as the structure with the set of properties. To simplify the representation of the message structure, it is divided into several logical blocks. These are the common properties (flags, subject, sender), recipients, dates, and message bodies (in this article, it is the text body). Other types of bodies (HTML and RTF) and the attachments are not discussed in this article.

The Creation of the Message

The creation of the message is performed by the call of the corresponding method from the object, which represents the folder, in which the message should be created. In the example, it looks like the following:

C++
fld->GetFolder()->CreateMessage(0,0,&pMessage);

As a result of the successful call, we get the pointer to the message object.

Writing the Message Properties

All message properties are written in the same way. First, the properties array is created, where properties are written as pairs – tag and value. Here is an example of writing several properties:

C++
std::vector<SPropValue> propArray;
SPropValue currentProp;

currentProp.ulPropTag = PR_MESSAGE_CLASS_W;
currentProp.Value.lpszW = (LPWSTR)pMsg->szMsgClass.c_str();
propArray.push_back(currentProp);
//Message flags
currentProp.ulPropTag = PR_MESSAGE_FLAGS;
currentProp.Value.ul = pMsg->flags;
propArray.push_back(currentProp);

After the properties array is filled, you should write it to the message. It is performed in the following way:

C++
if (propArray.size() != 0)
{
COM::AutoPtr<SPropProblemArray,MapiEraser> Problems;
return pMessage->SetProps((ULONG)propArray.size(), &propArray.at(0), &Problems);
}

All necessary properties of the message are written in the same way. For more information, see the library code. Any other non-standard properties, which are not used in the library, can be added in a similar way. You just should find its type and tag in MSDN.

Writing the Message Recipients

Message recipients are written in a rather different way than other properties. There is a separate ModifyRecipients method for writing recipients in the message object. This method is called in the following way:

C++
//Filling property array
std::vector<SPropValue> propArray;
SPropValue currentProp;
std::wstring recipientDisplayName = !itRecip->name.empty() ? 
			itRecip->name : itRecip->address;
currentProp.ulPropTag = PR_DISPLAY_NAME_W;
currentProp.Value.lpszW = (LPWSTR)recipientDisplayName.c_str();
propArray.push_back(currentProp);

currentProp.ulPropTag = PR_EMAIL_ADDRESS_W;
currentProp.Value.lpszW = (LPWSTR)itRecip->address.c_str();
propArray.push_back(currentProp);

currentProp.ulPropTag = PR_RECIPIENT_TYPE;
currentProp.Value.ul = itRecip->type;
propArray.push_back(currentProp);

//Filling special structure for changing recipients by property array
ADRLIST adrList = { 0 };
adrList.cEntries = 1;
adrList.aEntries[0].rgPropVals = &propArray.front();
adrList.aEntries[0].ulReserved1 = 0;
adrList.aEntries[0].cValues = (ULONG)propArray.size();
//Making changes in message object
pMessage->ModifyRecipients( MODRECIP_ADD, &adrList );

Writing the Message Body

The message body is often represented as a rather fair-sized data. That is why it is written not as a property, but as a so called “stream”. First, you should create (open) a stream in order to write data to the message. This procedure is implemented in the library in the CPstWriter::WriteMessageStream method. Its code looks like the following:

C++
long CPstWriter::WriteMessageStream 
    (IMessage * pMessage, const char * data, size_t dataSize, unsigned long streamTag)
{
…
MapiStream pStream;
pMessage->OpenProperty (streamTag, &IID_IStream, STGM_WRITE,
MAPI_CREATE | MAPI_MODIFY, ( LPUNKNOWN* ) & pStream);

ULONG ulWritten = 0;
pStream->Write (data, (ULONG)dataSize, &ulWritten);

pStream->Commit( STGC_DEFAULT );
…
}

It means that you just create a stream with the tie to a definite streamTag. After this, the writing of data to this stream is performed and changes are committed.

To write the text (unicode) message body, you should call the mentioned above method with the streamTag = PR_BODY_W parameter.

How to Build and Run this Solution

  1. Use Microsoft Visual Studio 2005 or later to open PstWriter.sln in the src folder.
  2. Build the solution.
  3. Install Microsoft Outlook 2007 or later (it provides Unicode MAPI) for the proper work.

Testing

write-pst-file/test2.JPG

  1. Build the solution using the instructions above.
  2. Run TestApp.exe with the command line parameter, which defines the path to the result PST file. For example, TextApp.exe “D:\1.pst”.

    write-pst-file/test1.JPG

  3. As a result, if there were no errors, a file is created in the defined folder.
  4. If you open it in Microsoft Outlook, you see the following:

As it can be seen from the screenshot, the following objects and properties were successfully created:

  1. The folders hierarchy (Result\Inbox\asf).
  2. The message with To, CC, Bcc, and From fields, all dates, subject, and the text body.
  3. All recipients (except the mailing address) also include the display name.
  4. The message is marked as read, private, and important.

Supported Windows Versions

  • Windows XP (SP3)
  • Windows 7 (x86, x64)

License

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


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

Written By
Software Developer apriorit
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWriting the header Pin
wombleme29-Oct-12 14:14
wombleme29-Oct-12 14:14 
QuestionManaged C# wrapper ? Pin
Stef Heyenrath11-Jan-12 21:09
Stef Heyenrath11-Jan-12 21:09 
AnswerRe: Managed C# wrapper ? Pin
Vladimir Dorodov20-Dec-12 7:49
Vladimir Dorodov20-Dec-12 7:49 
AnswerMessage Closed Pin
28-Apr-14 8:16
Member 1038791128-Apr-14 8:16 
QuestionMAPIInitialize fails when using Outlook 2010 64 on 64 bit Windows 7 Pin
Stef Heyenrath9-Jan-12 3:25
Stef Heyenrath9-Jan-12 3:25 
AnswerRe: MAPIInitialize fails when using Outlook 2010 64 on 64 bit Windows 7 Pin
Alexander Maximov11-Jan-12 20:43
Alexander Maximov11-Jan-12 20:43 
GeneralRe: MAPIInitialize fails when using Outlook 2010 64 on 64 bit Windows 7 Pin
Stef Heyenrath11-Jan-12 21:08
Stef Heyenrath11-Jan-12 21:08 
GeneralIssue in creating .MSG file Pin
Ajit4180711-Sep-12 0:35
Ajit4180711-Sep-12 0:35 
QuestionConfigureMsgService error Pin
tiutababo7-Apr-11 17:36
tiutababo7-Apr-11 17:36 
Hi,

Your code works great. However, I've encountered some problems with the mapi calls.

When I'm connected to a exchange server, and is concurrently accessing the ost store, configureMsgService returns me with an access denied error (HRESULT == -2147024891). This occurs in PstStorage.cpp, when pstwriter calls the constructor for CPstStorage.

When I'm unconnected to the exchange server, it works fine.

If I use a temporary profile to access pst files, and concurrently creating the new pst store, it works fine.

I'll appreciate any help/suggestions. Thanks.
GeneralOooops.... Pin
persian music5-Aug-10 12:07
persian music5-Aug-10 12:07 
GeneralRe: Oooops.... Pin
Apriorit Inc6-Aug-10 0:06
Apriorit Inc6-Aug-10 0:06 

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.