A demo addin to customize the autocomplete stream on-the-fly, for Outlook 2007 (x86).
The version we've been working on is Outlook 2007, x86 edition, version 12.0.6744.5000. You should at least consider using 12.0 as newer versions may include changes on the internal file and/or code organization. However, our approach should be portable, with proper changes, to newer versions of Outlook.
To build and run this sample, you need:
- Visual Studio compiler. Included project is for VS2012.
- Outlook 12.0/x86 (preferably 6744 build)
The list of addresses that you see when autocompletion triggers in any "address" type
field, such as "To:", "Cc:", "Bcc:", etc, is stored in a Outlook.NK2 file in the folder
%USERPROFILE%\AppData\Roaming\Microsoft\Outlook
.
Outlook reads the file at request, e.g: when the user is going to compose a new mail message. After the user sends the message, Outlook will re-write the file with the "Record Weight" field changed. Weights are modified, and file reordered by this field, according to the frequency or popularity of each address.
Many tools exist to modify the file "offline", such as NK2Edit [5]. Our purpose is to change the name cache on-the-fly, seeing the results in realtime.
The NK2 file structure is known and documented by Microsoft itself [1]. The in-memory version follows the same general structure: a metadata header, followed by the database rowset. The structures to hold the rows and rowset are publicly documented in MSDN [2][3].
From our research, Outlook does not enforce any particular ordering in the file. Basically, it will show the entries according to the order in the file. Our project includes code for sorting by weight criteria or swapping rows to alter the order; both can be triggered with the provided UI.
How we got the pointer to the in-memory rowsets to be able to modify them online?
This is the flow of Outlook when reading the NK2 autocomplete cache file, in pseudocode:
handle = GlobalAlloc();
buf = GlobalLock(handle);
ReadFile(buf);
GlobalUnlock(buf);
stream = CreateStreamOnHGLOBAL(buf);
/* Read 12-byte header */
stream.Read_DWORD(); /* Read and verifies Nk2 header 0xBAADF00D */
stream.Read_DWORD(); /* Read and verifies 0x0000000A (major version) */
stream.Read_DWORD(); /* Read and verifies 0 (minor version) */
stream.Read_row_count(); /* Read number of rows */
stream.Read_all_rows();
/* read extra information (unneeded) */
repeat 3
stream.Read_DWORD();
GlobalFree(buf);
srowset_ptr = this+0x24;
We find the SRowset
pointer in a function parameter (EBP+0xC)
of an internal call,
once we read past the 12-byte metadata header.
Our steps to get the pointer are (see Connect.cpp).
- Using Deviare-InProc we hook into
OLE32.dll!CreateStreamOnHGlobal
call. - On
ole32.dll!CreateStreamOnHGlobal
call, we look for the NK2 header in the created stream; in that case, we subsequently hook Read and Release methods of the IStream interface. - For every
IStream::Read
call, we wait for reading past the initial 12-byte metadata header. Once there, we store the pointer by traversing the EBP chain to look for the second argument(EBP+0xC)
of the calling function (according to standard-call convention). - When the
IStream::Release
is called for the NK2 stream, we construct theCNicknameCache
class instance passing theSRowSet
structure pointer we obtained in the process. Our object acts as a simple wrapper for the database exposing a serie of methods such as obtain the row count, sort by tag, get/set properties, swap rows and retrieve a particular row (SRow
structure pointer). - At this point we're ready to fiddle with the rowset structure without problem. Outlook will
write it to disk when at application exit.
- Make sure Outlook is properly installed.
- Open and build the solution.
- Register the output DLL with Outlook using
REGSVR32
at an elevated command prompt. - Open Outlook. When you open the compose mail window, a Nektra ribbon tab with the UI button should be available.
- Click the button to open the editor and modify the list entries as you wish.
- Play with the autocomplete dropdown on the CC/BCC input fields to see the effect of your modification on the input data and the field ordering.
After Outlook saves your modifications, you can use an external NK2 viewer to see your modified entries. [5]
To uninstall the sample from Outlook, unregister it with REGSVR32 /U <dllname>
at an elevated command prompt.
[1] http://portalvhds6gyn3khqwmgzd.blob.core.windows.net/files/NK2/NK2WithBinaryExample.pdf [2] SRowset https://msdn.microsoft.com/en-us/library/ms629453(v=vs.85).aspx [3] SRow https://msdn.microsoft.com/en-us/library/ms629452(v=vs.85).aspx [4] Nickname Cache https://msdn.microsoft.com/en-us/library/ms629452(v=vs.85).aspx [5] http://www.nirsoft.net/utils/outlook_nk2_edit.html