This section contains most commonly asked questions about OutlookSpy as well as some OutlookSpy tips and tricks that can make your life much more enjoyable.

Outlook Object Model

1. Accessing anything in the Outlook Object Model. OutlookSpy seems to provide direct access to only a few objects in the Outlook Object Model. How do I access other objects? E.g. I want to look at the first recipient of a mail item, how do I do that?

2. Executing Outlook commands not available through its Object Model. I have noticed that there are quite a few things you can do through the Outlook UI, unfortunately a lot of that functionality is not programmatically accessible. I know that I can simulate clicks on the buttons, but how do I find those buttons programmatically to begin with?

Extended MAPI

1. Comparing two messages. I have two messages (one is created by my code and another one is a native Outlook message); they look really similar, but Outlook doesn't seem to like the message I have created.

2. Figuring out which message properties have changed . I am changing things through the Outlook UI, but I have hard time figuring out which Extended MAPI properties actually change. E.g. I am adding a new entry to Outlook's distribution list, which Extended MAPI properties does Outlook modify?

3. Opening any object by its entry id. I can see a binary property and I suspect it is some kind of an entry id, how do I find out what it really is? Or: I can see a notification on the IMsgStore|Advise tab; it gives me an entry id, but I have no idea which object it belongs to.

4. How do I access invisible Outlook folders? I can access any visible Outlook folder by just selecting it through the Outlook UI and clicking the IMAPIFolder button, but how about other (invisible) folders?

5. Constructing arbitrary restrictions in Extended MAPI. Programmatically creating restrictions in Extended MAPI is never fun, can OutlookSpy help?

6. Using OutlookSpy features outside of OutlookSpy. I am creating a search folder using Extended MAPI and I want to see all the notifications immediately. Unfortunately there is no way to look at that folder right away. How can I do that? Does OutlookSpy provide any means to force it to display object properties window programmatically?

7. Copying properties from one object to another. I have two objects with different sets of properties and I want to copy one or more property from one object to another.

8. Importing/exporting messages. I know I can save a message in Outlook in the MSG format and then drag it to any folder either on the same or a different machine, but how about the messages invisible in Outlook (e.g. folder view descriptor messages)?

Writing Exchange Client Extensions

1. Exchange Client Extensions in Outlook 97/98 that can add bitmap/text buttons. I can see that OutlookSpy (which looks like an Exchange Client Extension) has no problems creating a toolbar with buttons that have both bitmap and text.

2. I cannot decide whether a COM addin or an Exchange Client Extension would suit my requirements better (or using IOutlookExtCallback). It looks like COM addin provides access to the Outlook Object Model, while Extended MAPI is much easier in case of Exchange Client Extension. Why can't I have both?

 


Accessing anything in the Outlook Object Model. OutlookSpy seems to provide direct access to only a few objects in the Outlook Object Model. How do I access other objects? E.g. I want to look at the first recipient of a mail item, how do I do that?

You can easily browse from a parent object to its child object in OOM. In case of a sub-property, select it and click Browse button. For example, if you have MailItem window open, selecting Attachments property and clicking Browse button will display a new window with all properties and functions of the Attachments object. In case of Recipients, you can select the Recipients property, click Browse. In the new window go to the Functions tab, select Item function, click Call. Enter 1 as a parameter, click Ok. OutlookSpy will display the first Recipient of a given message.

An even easier way would be (if you don't mind some scripting) to go to the Script tab, type the script below, and click Run:

BrowseObject(MailItem.Recipients.Item(1))


Executing Outlook commands not available through its Object Model. I have noticed that there are quite a few things you can do through the Outlook UI, unfortunately a lot of that functionality is not programmatically accessible. I know that I can simulate clicks on the buttons, but how do I find those buttons programmatically to begin with?

If any given OOM object has a property named CommandBars, OutlookSpy will display an additional tab - CommandBars. An example would be opening an Explorer or an Inspector object. The CommandBars tab will display all CommandBars and their child buttons in a treeview. Once you drill down to the button you are after, simply select it and click Browse button. For example, you can find out that the the id of the "Send/Receive" button is 5488. You can use this value to programmatically find that button and simulate a click on it.

Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 5488)

Btn.Execute

 


Comparing two messages. I have two messages (one is created by my code and another one is a native Outlook message); they look really similar, but Outlook doesn't seem to like the message I have created.

You can directly compare two messages - open both messages in OutlookSpy by clicking IMessage buttons. In one IMessage window, go to the Compare tab. Select PR_ENTRYID from the second message and drag it to the Compare tab of the first message. OutlookSpy will loop through all properties of each message and compare their values. OutlookSpy can display properties with different values, missing properties, extra, etc.


Figuring out which message properties have changed . I am changing things through the Outlook UI, but I have hard time figuring out which Extended MAPI properties actually change. E.g. I am adding a new entry to Outlook's distribution list, which Extended MAPI properties does Outlook modify?

When you open an IMessage window, OutlookSpy will install an advise sink(IMsgStore::Advise)  to trap all notifications related to the currently displayed message. Go to the Watch tab of the IMessage window, select all properties you want to monitor and click the "->" button. Now whenever the message is modified (either through the Outlook UI after you save the changes or due to something else modifying the message), OutlookSpy will monitor fnevObjectModified notifications related to the message and compare old values of all properties with their new values after the modification. Modified properties will be logged in the window on the Watch tab.


Opening any object by its entry id. I can see a binary property and I suspect it is some kind of an entry id, how do I find out what it really is? Or I can see a notification on the IMsgStore|Advise tab; it gives me an entry id, but I have no idea which object it belongs to.

IMAPISession, IAddrBook, IMsgStore and IMAPIFolder windows have an OpenEntry tab. You can drag any PT_BINARY or PT_MV_BINARY property there and OutlookSpy will try to use it in a call to the OpenEntry() method of the corresponding object. Or, if you only have a hex representation of an entry id (e.g. as a result of a notification), you can click "Enter Entry ID Manually.." button and paste the hex value there.

Or, as a much quicker shortcut, you can select any PT_BINARY or PT_MV_BINARY property on a GetProp tab, right click on it and select IMAPISession::OpenEntry() from the popup menu.


How do I access invisible Outlook folders? I can access any visible Outlook folder by just selecting it through the Outlook UI and clicking the IMAPIFolder button, but how about other (invisible) folders?

You can browse to any folder of your choice (both visible and invisible in the Outlook UI) by opening the IMsgStore window and clicking the "Open Root Container" button. OutlookSpy will call IMsgStore::OpenEntry(0, NULL, ...). This will return the root folder of an IMsgStore. You can then go to the GetHierarchyTable tab of the root folder and open any of its child subfolders.


Constructing arbitrary restrictions in Extended MAPI. Programmatically creating restrictions in Extended MAPI is never fun, can OutlookSpy help?

You can let Outlook do the job: click "Tools | Advanced Find" in Outlook, create the appropriate restriction using the Outlook UI, click "Find". Outlook will create a temporary search folder. 

Without closing the Advanced Find window (otherwise Outlook will delete the temporary search folder), browse to that folder from IMsgStore - click "Open Root Container", in the IMAPIFolder window go to the GetHierarchyTable tab, select the root search folder (e.g."Search Root" or "Finder" depending on whether you are using Exchange or PST), click "OpenEntry". In the new IMAPIFolder window go to the GetHierarchyTable again, open the last folder.

Search folder windows in OutlookSpy have an additional tab - GetSearchCriteria. That tab will display the search restriction you created using the Outlook UI. You can pretty much copy the text representation of the restriction and paste it into your code.


Using OutlookSpy features outside of OutlookSpy. I am creating a search folder using Extended MAPI and I want to see all the notifications immediately. Unfortunately there is no way to look at that folder right away. How can I do that? Does OutlookSpy provide any means to force it to display object properties window programmatically?

OutlookSpy does have an API that can be used to display its browser for any COM object it understands. You will need to create an instance of a OutSpy.Utils COM object and call Utils.BrowseObject(Object, ParentWnd) method. E.g. in VB:

set Utils = CreateObject("OutSpy.Utils")
Utils.BrowseObject(MyObject)


Copying properties from one object to another. I have two objects with different sets of properties and I want to copy one or more property from one object to another.

You can simply select any number of properties on one object (be that IMessage, IMAPIFolder or any other Extended MAPI object derived from IMAPIProp) and drop them on the GetProps tab of any another IMAPIProp descendant. OutlookSpy will call IMAPIProp::CopyProps() on the first object and then save the second object.


Importing/exporting messages. I know I can save a message in Outlook in the MSG format and then drag it to any folder either on the same or a different machine, but how about the messages invisible in Outlook (e.g. folder view descriptor messages or messages in folders other than the IPM tree)?

Saving a message in OutlookSpy is easy - just open any IMessage window and click the "Save as MSG file" button. You can then drag the saved message back to any of the Outlook's folders to create either a regular or an invisible (associated message) - simply open any IMAPIFolder window and drag the MSG file from Windows Explorer and drop it on the listbox either on the GetContentsTable or Associated Contents tab.


Exchange Client Extensions in Outlook 97/98 that can add bitmap/text buttons. I can see that OutlookSpy (which looks like an Exchange Client Extension) has no problems creating a toolbar with buttons that have both bitmap and text.

You cannot create additional toolbars and/or add buttons that have both an icon and text using Exchange Client Extensions API. For that you would need to use the Outlook Object Model. Retrieve the root of the Outlook Object Model (Application) using IOutlookExtCallback (see MSDN) and add the toolbars buttons using OOM, just like you would normally do when writing an Outlook COM add-in.

This is easy in Outlook 2000 and up. Adding toolbars/buttons in Outlook 97/98 is also easy, the problem is however sinking the click events. Microsoft Office 97 simply does not expose any events on the buttons. OutlookSpy works around that by installing a global message hook using SetWindowsHookEx() and monitoring all WM_LBUTTONDOWN Windows messages. For such messages it iterates through all of its buttons and checks if the click is within one of the buttons (using Left, Top, Width and Height properties of a CommandBarButton object). 


I cannot decide whether a COM addin or an Exchange Client Extension would suit my requirements better . It looks like COM addin provides access to the Outlook Object Model, while Extended MAPI is much easier in case of Exchange Client Extension. Why can't I have both?

You sure can have the best of both worlds. Exchange Client Extensions can easily access the Outlook Object Model. For that you will need to use IOutlookExtCallback interface, see MSDN for details:

http://msdn.microsoft.com/library/en-us/dnout98/html/msdn_outextend.asp

A thing to keep in mind is that IOutlookExtCallback::GetObject() will return a different OOM object depending on the context of the call. E.g. if you use the IOutlookExtCallback interface from the IExchExt::Install() callback when eecontext is EECONTEXT_TASK, IOutlookExtCallback::GetObject() will return Application object, if eecontext is EECONTEXT_VIEWER, you will get back the corresponding MAPIFolder object. In case of the IExchExtMessageEvents::OnWriteComplete() callback, you will get the corresponding message object (MailItem, ContactItem, etc). It is a good idea to take a look at the Class property of the returned object (all OOM objects have this property) to make sure you got what you expected. E.g. for MailItem, Class property is 43, Application.Class is 0, etc.

Here is some C++ code courtesy of Michael Tissington (http://www.oaklodge.com/technology). Since each OOM object exposes Application property, after calling IOutlookExtCallback::GetObject(), you can retrieve its Application property using late binding if all you need is Application:

// Cache the Outlook object
if (g_pOutlook == NULL)
{
IOutlookExtCallback *pOutlookExt = NULL;

if (SUCCEEDED(pmecb->QueryInterface(IID_IOutlookExtCallback, (LPVOID *) &pOutlookExt)))
{
LPUNKNOWN pUnk = NULL;

if (SUCCEEDED(pOutlookExt->GetObject(&pUnk)))
{
LPDISPATCH pDispatch = NULL;

if (SUCCEEDED(pUnk->QueryInterface(IID_IDispatch, (LPVOID *)&pDispatch)))
{
OLECHAR * szApplication = L"Application";
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
DISPID dspid;
VARIANT vtResult;

pDispatch->GetIDsOfNames(IID_NULL, &szApplication, 1, LOCALE_SYSTEM_DEFAULT, &dspid);
pDispatch->Invoke(dspid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparamsNoArgs, &vtResult, NULL, NULL);
pDispatch->Release();

g_pOutlook = vtResult.pdispVal;
}

pUnk->Release();
}

pOutlookExt->Release();
}
}