free web page counters

Windows Mobile Pocket PC Smartphone Programming

==>Click here for the SiteMap<==. Original contents with decent amount of source codes.

Tuesday, February 21, 2006

How to programmatically query mail account information in Windows Mobile devices, for example, incoming server and username?

====>SiteMap of this Blog<===

How to programmatically query mail account information in Windows Mobile devices, for example, incoming server and username?

Pocket Outlook manages a few mail accounts. Out of box there is an account called "Outlook E-Mail", which is used to synchronize with Desktop Outlook. For Pocket PC Phone edition and Smartphones, out of box there is another account called "Text Messages", which is used to store SMS. Besides, a user can create new accounts using Pocket Outlook's New Account Wizard and manually typing required information, such as incoming server, outgoing server, username, mail transport (POP3 or IMAP4, or customized ones), and the etc.

Developers can programmatically create mail accounts using omnipotent DMProcessConfigXML. The CSP (Configuration Service Provider) to use is EMAIL2. Read the great example in MSDN on how to achieve this purpose.

There is not much to talk about how to create a mail account. What I am trying to tackle here is how to query back the mail account information, for example, trying to enumerate all the mail accounts that a user has setup in Pocket Outlook, and get the authentication and server information. Such information is useful and handy, if you have your own pieces of application that deals with mail accounts, and you want to prevent users from having to manually type all the information in twice (once in the mail setup and once in your application).

For 5.0 devices, Microsoft provides a new managed namespace to deal with Outlook: Microsoft.WindowsMobile.PocketOutlook. EmailAccountCollection class defines the set of all e-mail accounts that are currently configured on the mobile device. However, the class EmailAccount only provides account name and transport name, which is far less than enough for our purpose. Besides, we also need to target Pocket PC 2003 devices, which does not the managed environment yet.

DMProcessConfigXML is the natural way (and seemingly the only way) to go. The trouble is, you need to feed this nice API with a GUID, like the following:

<wap-provisioningdoc>
  <characteristic type="EMAIL2">
    <characteristic-query type="{D671C70B-8EE3-4881-8045-2AEE6F731B55}"/>
  </characteristic>
</wap-provisioningdoc>

What the heck? Why not simply using the human-readable human-memorable account name, which is unique anyway? Well, the guys in Redmond apparently developed a better appetite to consume GUIDs, which can be supplied, indeed, infinitely. Of course, not infinitely in strict mathematical sense. So in addition to CSLID, IID, LIBID, ..., we have another GUID, the ID to identify each mail account.

Now we need to solve the simple problem of digging out the GUID for each account. How about trying the usual hack to search in registry? No! It does not work. No such information is stored in registry.

Do not give up yet. So let us resort to our friend MAPI here. Hate it or love it, the more than 10-year-old MAPI still powers messaging applications in Windows kingdom, under the hood of CDO or later Outlook PIA. With MAPI being so ancient, you can easily smell its unusualness from its way of using complex structures to define properties and make everything a table of records, every record a number of properties.

And a little bit of knowledge how Pocket Outlook's mail account is linked to an MAPI store. Each mail account is associated with a MAPI store, and a transport. A store is a store, for storing email folders and email messages. The transport is responsible for synchronizing a mail account by talking to the remote server. Outlook itself manages UI and coordinates the transport and the store. Enough with the abstract stuff, two concrete samples: "Outlook E-Mail" is associated with a store called "ActiveSync", "Text Messages" is associated with a store called "SMS".

Back to the main topic: What we need to do is to using MAPI to achieve two purposes:

  • Enumerate all mail accounts
  • Find GUID for each account

The first purpose is easy to achieve. We just need to enumerate all MAPI stores. The straightforward approach is to open the message store table and iterate through all records; for each record, we load the MAPI store name.

The second purpose is a little harder to fulfill. Digging in "cemapi.h", the main header file for CEMAPI (a trimmed version of MAPI for CE device), one line caught my eye:


    // This property is to uniquely identify a store. It's a guid.
    #define PR_CE_UNIQUE_STORE_ID PROP_TAG(PT_CLSID, 0x8113)

Aha! Here we are. This must be the GUID we need. See the comment, its name and the type, exactly a GUID. Since this is a property associated with a store, we can query it in two ways:

  • While iterating through the records in the message store table, we request a column PR_CE_UNICODE_STORE_ID when inspecting each record;
  • Or, we can load the PR_ENTRY_ID for each MAPI store, and open the store then query the property by calling GetProps.

The first approach saves both coding and runtime computation, so below is the code following first approach.

Notice "ActiveSync" and "SMS" are not inspected by the code. This is anticipated, as they do not have "username" or "incoming server" in the normal sense. Also notice EMAIL2 won't give you the password :)

#include "cfgmgrapi.h"
// Query a mail account information using DMProcessConfigXML API
// IN: the account GUID
HRESULT QueryAccountInfo(LPCTSTR szAccountGUID)
{
   LPTSTR szProvXMLOut = NULL;
   TCHAR szProvXMLIn[512];

   memset(szProvXMLIn, 0, 512*sizeof(TCHAR));
   wcscat(szProvXMLIn, TEXT("<wap-provisioningdoc>
      <characteristic type=\"EMAIL2\"><characteristic-query type=\""));
   wcscat(szProvXMLIn, szAccountGUID);
   wcscat(szProvXMLIn, TEXT("\"/>"));
   wcscat(szProvXMLIn, TEXT("</characteristic></wap-provisioningdoc>"));

   HRESULT hr = DMProcessConfigXML(szProvXMLIn,
      CFGFLAG_PROCESS, &szProvXMLOut);
   if (!FAILED(hr)) {
      //...
   } else {
      // ... do error handling
   }
   ALERT(szProvXMLOut);

   DELETE_STR(szProvXMLOut);

   return hr;
}


// Query all stores
HRESULT QueryMsgStore(IMAPISession* pMAPISession)
{
   HRESULT hr = S_OK;
   IMAPITable* pMsgStoresTable = NULL;
   LPSRowSet pRows = NULL;

   enum {
      ePR_DISPLAY_NAME,
      ePR_CE_UNIQUE_STORE_ID,
      NUM_COLS
   };

   // These tags represent the attachment prop information
   // we would like to pick up
   SizedSPropTagArray(NUM_COLS, Columns) =
   {  
      NUM_COLS,
      PR_DISPLAY_NAME,
      PR_CE_UNIQUE_STORE_ID,
   };

   // get the message store table
   hr = pMAPISession->GetMsgStoresTable(0, &pMsgStoresTable);
    EXIT_ON_FAILED(hr);

   // Get the display name and all store IDs
   // for each message store
    hr = pMsgStoresTable->SetColumns((LPSPropTagArray)&Columns, 0);
    EXIT_ON_FAILED(hr);
  
   // loop through
   while (TRUE) {
      hr = pMsgStoresTable->QueryRows(1, 0, &pRows);
      if (FAILED(hr) || pRows->cRows != 1) {
         if (!FAILED(hr)) hr = E_FAIL;
          EXIT_ON_FAILED(hr);
      };

      if (PR_DISPLAY_NAME ==
         pRows->aRow[0].lpProps[ePR_DISPLAY_NAME].ulPropTag
         &&
         PR_CE_UNIQUE_STORE_ID ==
         pRows->aRow[0].lpProps[ePR_CE_UNIQUE_STORE_ID].ulPropTag) {

         // this is the display name column
         TCHAR *pszStoreName = pRows->aRow[0].lpProps[0].Value.LPSZ;
         ALERT(pszStoreName);

         // Now we got the GUID
         LPGUID pGUID = pRows->aRow[0].
            lpProps[ePR_CE_UNIQUE_STORE_ID].Value.lpguid;

         TCHAR szAccountGUID[40] = {0};
         memset(szAccountGUID, 0, 40*sizeof(TCHAR));
         wsprintf(szAccountGUID,
            TEXT("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"),
            pGUID->Data1, pGUID->Data2, pGUID->Data3,
            pGUID->Data4[0], pGUID->Data4[1],
            pGUID->Data4[2], pGUID->Data4[3],
            pGUID->Data4[4], pGUID->Data4[5],
            pGUID->Data4[6], pGUID->Data4[7]);
         QueryAccountInfo(szAccountGUID);
      }

      if (pRows) {
         FreeProws(pRows);
         pRows = NULL;
      }
   }

FuncExit:
   if (pRows) FreeProws(pRows);
   RELEASE_COMOBJ(pMsgStoresTable);

   return hr;
}

Category: [Outlook / Transport / MAPI]

====>SiteMap of this Blog<===