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


2 comments:

Anonymous said...

Usually I try Redemption at a fairly high level (C#) but I'm not very familiar with Outlook account management.
Is is possible to also export (and import) the Outlook 2003 passwords, so that a similar method could be used to more easily set up / transfer Outlook settings to a new computer?

xignite said...

I was searching for this .....thanks to post this nice article



GetFundMinimumInvestments