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.
- 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:
HRESULT MAPIInitialize(LPVOID lpMapiInit);
In the example, it is performed in the following way:
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:
hr = MAPIAdminProfiles(0, &m_pIProfAdmin);
…
DWORD dwStart = GetTickCount();
sprintf_s( m_profileName, 100, "Temp%lX", dwStart );
(void) m_pIProfAdmin->DeleteProfile( (LPTSTR)&m_profileName, 0 );
hr = m_pIProfAdmin->CreateProfile( (LPTSTR)&m_profileName, NULL, 0, 0 );
…
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:
hr = m_pSvcAdmin->CreateMsgService( "MSUPST MS", "MAPI Pst Msg Store", 0, 0 )
hr = m_pSvcAdmin->GetMsgServiceTable(0, &m_ptblSvc);
COM::AutoPtr<SRowSet,RowEraser> prows;
COM::AutoPtr<SRowSet,RowEraser> pRows;
enum {iSvcName, iSvcUID, iEnrtyID, cptaSvc};
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)
{
m_MsgStoreUID = *((LPMAPIUID)pRow->lpProps[iSvcUID].Value.bin.lpb);
if(strcmp(pRow->lpProps[iSvcName].Value.lpszA, pszMsgService) == 0)
{
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());
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.
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:
COM::AutoPtr<SPropValue,MapiEraser> pVal_EID;
::HrGetOneProp(store->GetMsgStore(),PR_IPM_SUBTREE_ENTRYID,&pVal_EID);
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**>(¤tFolder));
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:
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:
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:
std::vector<SPropValue> propArray;
SPropValue currentProp;
currentProp.ulPropTag = PR_MESSAGE_CLASS_W;
currentProp.Value.lpszW = (LPWSTR)pMsg->szMsgClass.c_str();
propArray.push_back(currentProp);
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:
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:
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);
ADRLIST adrList = { 0 };
adrList.cEntries = 1;
adrList.aEntries[0].rgPropVals = &propArray.front();
adrList.aEntries[0].ulReserved1 = 0;
adrList.aEntries[0].cValues = (ULONG)propArray.size();
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:
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
- Use Microsoft Visual Studio 2005 or later to open PstWriter.sln in the src folder.
- Build the solution.
- Install Microsoft Outlook 2007 or later (it provides Unicode MAPI) for the proper work.
Testing
- Build the solution using the instructions above.
- Run TestApp.exe with the command line parameter, which defines the path to the result PST file. For example, TextApp.exe “D:\1.pst”.
- As a result, if there were no errors, a file is created in the defined folder.
- 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:
- The folders hierarchy (Result\Inbox\asf).
- The message with To, CC, Bcc, and From fields, all dates, subject, and the text body.
- All recipients (except the mailing address) also include the display name.
- The message is marked as read, private, and important.
Supported Windows Versions
- Windows XP (SP3)
- Windows 7 (x86, x64)
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 member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.