Thursday, September 4, 2008

Avoiding multiple password prompt for IMAPISession::OpenMsgStore

If you have a multi threaded application and you login to the same profile from multiple threads to do some work, you may be prompted again and again for the password while calling IMAPISession::OpenMsgStore for password protected PST file or exchange mailbox.

Lets say your main thread login to the MAPI and open a password protected PST file (The user was prompted for the password). After processing you close the message store (not logging off from the session). Now if some other thread again opens the same PST file, the user will be presented with the password prompt again.

To avoid this is, on you main thread, don't release the IMsgStore pointer until you log off from the session. If you do so, then on other threads, although you will be creating a new MAPI session, you will not prompt for the password

I had a similar issue in which user was prompted for the password twice for the same PST, which was solved by this trick.

AshuSoft - Website Launch

Ever since I learned about windows and the internet, I wanted to have website of my own.

The long wait is finally over and my website www.ashusoft.com has been launched.

It features mostly email/messaging based tools like

AB Mass Mailer Pro

A tool to send larger number of files and folders by email
AB Mass Mailer Lite

Free and stripped down version of Mass Mailer Pro.
AB Attachment Extractor

Tool to browse (just like windows explorer) & automatically extract attachment from incoming mails
WinJunction

Tool to create Soft Link.
Stock Market Ticker

Tool to view live stock prices (Indian Stock Market Only)



None of the softwares published on this site were developed with the intention of selling. These were developed by Ashutosh for his personal use. But when he checked out similar products on the internet, there wasn't anyone that he could find. So, he decided to publish/sell them on the internet

Every product has a story which tells why it was written.
  • Stock Market Ticker - Earlier I used to trade a lot in stock market and my Demat account didn't had a very good interface. For the one which had good UI didn't work with my company's network due to network/proxy configuration. So, I made this application to keep track of stock market while working in office. Now I don't use it.
  • AB Mass Mailer - I had to send around 2 GB of photographs to my sister. So, I wrote this application. I could have easily uploaded it to some server, but I was free that time and choose to make a tool so that it can be used in any case.
  • AB Attachment Extractor - Well this was developed when I had to file my annual tax return (Income Tax) and I had to extract Demat account statements from my emails (I used to do Intra day trading everyday. So, there was one statement for each day. Just imaging opening over 200 mails and saving the attachment)
  • Win Junction- I wrote this application to organize my music library. I wanted to have a music/movie folder in more than one place.
Besides these there are lots of other softwares developed by me (of course for my personal use). I will be posting them as and when I get time.

AshuSoft - Stock Market Ticker

Stock Market Ticker has been released

Some of it's cool features are
  • Watch stock prices without getting disturbed
  • Shows LIVE stock prices in a sleek toolbar like window, which can be placed anywhere on the screen

AshuSoft - Mass Mailer Lite

Mass Mailer lite - A tool to send large number of files by email.
View Screen Shot: Image1 & Image2
This tool is a stripped down version of AB Mass Mailer Pro

Some of it's features are

  • Easily send large number of files or directory by mail
  • Supports SMTP(POP/IMAP) account only
  • Supports the use of authentication and SSL for outgoing mail.
  • Compatible with every mail server.
  • Once provided the information, can send any amount of data by mail without user interaction
  • Add headers to the mails so that the files can be easily identified & retrieved by AB Attachment Extractor

AshuSoft - AB Attachment Browser & Extractor

AB Attachment Extractor (& Browser) has been released





Some of it's cool features are

  1. Browse attachments in your Mailbox/PST just like windows explorer
  2. Can remove the attachments from the mail, leaving the mail intact to reduce the mailbox/PST size
  3. Extract (save) attachments out of any number of mails (selected) or the complete folder or even complete mailbox
  4. Automatically extract and save (or delete) attachments from incoming mail
  5. Create filter to extract attachments/files so that attachments are extracted (saved or removed or deleted) from selected mails
  6. Can also extract embedded attachments out of the mails, these are the one which are part of the email body
  7. Ideal software to receive files send by AB Mass Mailer

AshuSoft - AB Mass Mailer Pro

AB Mass Mailer Pro has been released.

View Screen Shots

Some of the it's cool features are

  1. Easily send large number of files or directory by mail, no matter what the size is – even GB or TB won’t matter
  2. Automatically gets your email account from Microsoft Outlook.
  3. Supports both Exchange & SMTP account (POP/IMAP)
  4. Supports the use of authentication and SSL for outgoing mail.
  5. Compatible with every mail server.
  6. Presents you an address book to select the recipients, just like outlook does
  7. Automatically creates archives of small/transmittable size using archivers like WinRAR or 7-Zip(inbuilt)
  8. Can password protect the files
  9. Once provided the information, can send any amount of data by mail without user interaction
  10. Add headers to the mails so that the files can be easily identified and retrieved by AB Attachment Extractor

Monday, August 25, 2008

The case of the failed IMAPISession::OpenMsgStore

If you have ever worked with Extended MAPI, you definitely have made call to IMAPISession::OpenMsgStore.

This is one function which has given me sleepless nights, as it just fails for no reason. Some of the situations I found in which it fails are mentioned below

  1. If you use the Account Management API's and then make a call OpenMsgStore for a exchange store, and if you cancel the password prompt the first time, you won't be getting password prompt again, no matter what you do and how many times you call OpenMsgStore. Even logging off from MAPI Session, un-initializing MAPI Session and again logging back doesn't help. For all subsequent calls, it just returns the error 0x8004011D (MAPI_E_FAILONEPROVIDER).

    The only solution to this issue is to re-start your application.


  2. If you call OpenMsgStore for a password protected PST for which the password is not saved, you are prompted to enter the password. If you cancel the password prompt, you get an error 0x80040113 (MAPI_E_USER_CANCEL). For subsequent call to OpenMsgStore you don't get the password prompt. It simply returns the same error.

    The only workaround to this problem is to log off from the MAPI session and log back in.


  3. The last one, if you have a outlook profile in which you have an exchange account, also a PST file and an POP/IMAP account with the PST as your default store for receiving the mails and the POP/IMAP account as the default for sending mails.

    Now in this case, if outlook is closed and you call OpenMsgStore with the MDB_ONLINE flag as mentioned here, it fails with the error 0x8004011D (MAPI_E_FAILONEPROVIDER).

    Currently there is no workaround for this issue.

I have opened support case for all the above issue with Microsoft, for which I am sure are Bugs.

Tuesday, July 29, 2008

Expanding a (Personal) Distribution List using MAPI

To expand (get the individual email ids) the personal distribution list (MAPIPDL) follow these steps

  • Open the address book using the session pointer
  • open the PDL using IAddrBook::OpenEntry specifying its entry ID, which will give you an IDistList interface pointer
  • Call IDistList::GetContentsTable to get a pointer to the table of the this list
  • Optionally call SetColumns if you want only some properties
  • Call HrQueryAllRows to get a SRowSet containing the data you requested
Sample code is attached here
This sample is part of an application (posted without modification) which I wrote some time back. So, it does call some other members function but the meaning should be clear.

This sample may not be written in the best possible way, but it runs fine even if the address book doesn't support UNICODE (which was a requirement of my application)

Friday, July 4, 2008

Using the Account Management APIs (IOlkAccountManager interface) to get Outlook email Accounts


Introduction


This article shows how to use the Account Management APIs to get the email accounts configured in the outlook.

Background

The reason for writing this article is that Microsoft didn't provide an header file for the interfaces and also not all the properties of the account are documented by Microsoft.

Using the Code

If you want to skip the technical details and just want to use the feature, follow these steps.
  • Add thes files in your project
    • AccountHelper.h // contains declaration of class CAccountHealper
    • AccountHelper.cpp //implementation of class CAccountHealper
    • AcctMgmt.h //Contains the CLSID/IID and the property tags declaration
    • AcctMgmt.cpp // contains the code whic actually extracts the account data
  • Include the file AccountHelper.h in your .h or cpp file
  • Call GetAccounts passing it the Outlook profile name as shown below
 
TCHAR szProfile[] = _T("ProfileName"); // Add you outlook profile name here
AccountData *pAccountData = NULL; // This will contain the email account data
DWORD cAccountData = 0; // This will contain the count of the email account

if(FAILED(GetAccounts(szProfile, &cAccountData, &pAccountData)))
{
AfxMessageBox(_T("Failed to get account data"));
return;
}
else
{
for(DWORD i = 0 ; i <cAccountData; i++)
{
_tprintf(_T("Is Exchange account : %d"),pAccountData[i].lIsExchange);
_tprintf(_T("Email ID is : %s"),pAccountData[i].szEmailID);
_tprintf(_T("Incoming Server (POP/IMAP) is : %s"),pAccountData[i].szIncomingServer);
_tprintf(_T("Outgoing Server is : %s"),pAccountData[i].szOutgoingServer);
_tprintf(_T("Account Stamp : %s"),pAccountData[i].szAccountStamp);
//similarly all other properties
}
}

Inside the hood

If you want to implement this yourself, you need to do these things

  1. first implement this interface IOlkAccountHelper. Lets assume that the class name is CAccountHealper
  2. Login to MAPI, store the profile name in a variable you will need it to pass back to mapi in the class CAccountHealper
  3. Create an instance of IID_IOlkAccountManager
  4. Create an object of the class CAccountHealper
  5. Call the IOlkAccountManager:Init passing the CAccountHealper object you created
  6. Call IOlkAccountManager::EnumerateAccounts to get an interface to IOlkEnum
  7. Call IOlkEnum::GetCount to get the accounts count.
  8. Call IOlkEnum::Reset
  9. In a loop call IOlkEnum::GetNext to retrieve all the accounts

The code for the GetAccount which actually does these looks like this.
 
HRESULT GetAccounts(LPWSTR lpwszProfile, DWORD* pcAccounts, AccountData** ppAccounts)
{
HRESULT hRes = S_OK;
LPMAPISESSION lpSession;
LPOLKACCOUNTMANAGER lpAcctMgr = NULL;

hRes = MAPILogonEx(0,
(LPTSTR)lpwszProfile,
NULL,
fMapiUnicode | MAPI_EXTENDED | MAPI_EXPLICIT_PROFILE |
MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI,
&lpSession);
if(FAILED(hRes))
{
AfxMessageBox(L"Failed to login to the selected profile");
}

hRes = CoCreateInstance(CLSID_OlkAccountManager,
NULL,
CLSCTX_INPROC_SERVER,
IID_IOlkAccountManager,
(LPVOID*)&lpAcctMgr);
if(SUCCEEDED(hRes) && lpAcctMgr)
{
CAccountHelper* pMyAcctHelper = new CAccountHelper(lpwszProfile, lpSession);
if(pMyAcctHelper)
{
LPOLKACCOUNTHELPER lpAcctHelper = NULL;
hRes = pMyAcctHelper->QueryInterface(IID_IOlkAccountHelper, (LPVOID*)&lpAcctHelper);
if(SUCCEEDED(hRes) && lpAcctHelper)
{
hRes = lpAcctMgr->Init(lpAcctHelper, ACCT_INIT_NOSYNCH_MAPI_ACCTS);
if(SUCCEEDED(hRes))
{
LPOLKENUM lpAcctEnum = NULL;

hRes = lpAcctMgr->EnumerateAccounts(&CLSID_OlkMail,
NULL,
OLK_ACCOUNT_NO_FLAGS,
&lpAcctEnum);
if(SUCCEEDED(hRes) && lpAcctEnum)
{
DWORD cAccounts = 0;

hRes = lpAcctEnum->GetCount(&cAccounts);
if(SUCCEEDED(hRes) && cAccounts)
{
AccountData* pAccounts = new AccountData[cAccounts];

hRes = lpAcctEnum->Reset();
if(SUCCEEDED(hRes))
{
DWORD i = 0;
for (i = 0; i < lpunk =" NULL;" hres =" lpAcctEnum-">GetNext(&lpUnk);
if(SUCCEEDED(hRes) && lpUnk)
{
LPOLKACCOUNT lpAccount = NULL;

hRes = lpUnk->QueryInterface(IID_IOlkAccount, (LPVOID*)&lpAccount);
if(SUCCEEDED(hRes) && lpAccount)
{
ACCT_VARIANT pProp = {0};
HRESULT hRes = S_OK; //suppress the use of outer hRes
//Account ID
hRes = lpAccount->GetProp(PROP_ACCT_ID, &pProp);
if(SUCCEEDED(hRes) && pProp.Val.dw)
{
pAccounts[i].lAccountID = pProp.Val.dw;
}
//Account Name
hRes = lpAccount->GetProp(PROP_ACCT_NAME, &pProp);
if(SUCCEEDED(hRes) && pProp.Val.pwsz)
{
pAccounts[i].szAccountName = pProp.Val.pwsz;
}

//Is Exchange account flag
hRes = lpAccount->GetProp(PROP_ACCT_IS_EXCH, &pProp);
if(SUCCEEDED(hRes) && pProp.Val.dw)
{
pAccounts[i].lIsExchange = pProp.Val.dw;
}
else
{
pAccounts[i].lIsExchange = 0;
}

//Account Send Stamp
hRes = lpAccount->GetProp(PROP_ACCT_SEND_STAMP, &pProp);
if(SUCCEEDED(hRes) && pProp.Val.pwsz)
{
pAccounts[i].szSendStamp = pProp.Val.pwsz;
lpAccount->FreeMemory((LPBYTE)pProp.Val.pwsz);
}
//similarly retrieve all other properties.
//the sample code has the complete code
//it's been removed from here just for clarity

}

if(lpAccount)
lpAccount->Release();
lpAccount = NULL;
}

if(lpUnk)
lpUnk->Release();
lpUnk = NULL;
}

*pcAccounts = cAccounts;
*ppAccounts = pAccounts;
}
}
}

if(lpAcctEnum)
lpAcctEnum->Release();
}
}
if(lpAcctHelper)
lpAcctHelper->Release();
}

if(pMyAcctHelper)
pMyAcctHelper->Release();
}

if(lpAcctMgr)
lpAcctMgr->Release();
lpSession->Logoff(0,0,0);
lpSession->Release();
return hRes;
}
In the demo application I have added two buttons "Show Account Dialog" & "Show Add Account Wizard"

The new method added to the interface has this signature
 HRESULT IOlkAccountManager::DisplayAccountList (
HWND hwnd,
DWORD dwFlags,
LPCWSTR lpwszReserved,
DWORD dwReserved,
const CLSID * pclsidReserved1,
const CLSID * pclsidReserved2
);

To use this I have added this code when the above mentioned buttons are clicked

LPOLKACCOUNTMANAGER lpAcctMgr = NULL;

hRes = CoCreateInstance(CLSID_OlkAccountManager,
NULL,
CLSCTX_INPROC_SERVER,
IID_IOlkAccountManager,
(LPVOID*)&lpAcctMgr);
if(SUCCEEDED(hRes) && lpAcctMgr)
{
CAccountHelper* pMyAcctHelper = new CAccountHelper(szProfileName, lpSession);
if(pMyAcctHelper)
{
LPOLKACCOUNTHELPER lpAcctHelper = NULL;
hRes = pMyAcctHelper->QueryInterface(IID_IOlkAccountHelper, (LPVOID*)&lpAcctHelper);
if(SUCCEEDED(hRes) && lpAcctHelper)
{
hRes = lpAcctMgr->Init(lpAcctHelper, ACCT_INIT_NOSYNCH_MAPI_ACCTS);
if(SUCCEEDED(hRes))
{
hRes = lpAcctMgr->DisplayAccountList(this->m_hWnd,dwFlags,0,0,0,0);
if(hRes == MAPI_E_INVALID_PARAMETER)
{
AfxMessageBox(L"dwReserved, pclsidReserved1 or pclsidReserved2 were non-NULL.");
}
else if(hRes == E_ACCT_UI_BUSY)
{
AfxMessageBox(L"The account dialog class could not be created.");
}
else if( hRes == MAPI_E_USER_CANCEL)
{
AfxMessageBox(L"The Account Settings dialog box returned an error.");
}
else if(hRes == MAPI_E_CALL_FAILED)
{
AfxMessageBox(L"The Add New E-Mail property sheet returned an error.");
}
}
else
{
AfxMessageBox(L"Failed to initialzie IOlkAccountManager");
}
lpAcctHelper>Release();
}
else
{
AfxMessageBox(L"Failed to get Account Healper interface");
}
pMyAcctHelper->Release();
}
else
{
AfxMessageBox(L"Oops!!! Out of memory");
}
lpAcctMgr->Release();
}
else
{
AfxMessageBox(L"Ohhhhhhh No!!! failed to get IOlkAccountManager interace");
}



Special handling of exchange account

Exchange accounts doesn't have an incoming or outgoing server. Also the email ID for the exchange account is not in the normal form we see like. To get mailbox server or email ID of the exchagne account you need to open the Global profile section of the profile.

The mailbox server name is stored in the property tag


PR_INTERNET_CONTENT_ID PROP_TAG(PT_UNICODE, 0x662A)

The email ID is in general stored in these property tags

PROP_EXCHANGE_EMAILID PROP_TAG(PT_UNICODE, 0x663D) //email ID

PROP_EXCHANGE_EMAILID2 PROP_TAG(PT_UNICODE, 0x6641) //email ID


In some exchange 2000 account you won't find these properties. In that case you have to get the get the IMailUser interface of the user then query for PR_SMTP_ADDRESS property.

References

For further reading you may refer to Account Management API Reference