none
Searching for Contacts through MAPI in c++ RRS feed

  • Question

  • Hi all,

    I am writing a .dll to integrate with a current system we have made, which we would like to provide some automated outlook integration, the first and main feature we need to provide is searching for contacts. 

    I'm using c++ and mapi headers : MAPIX.h and  MAPIUtil.h

    HRESULT hr = MAPIInitialize(0); 
    	if (FAILED(hr)){
    		return false; 
    	}
    	
    	LPMAPISESSION currentSession;
    
    	HRESULT result = MAPILogonEx(0, NULL, NULL, NULL, &currentSession);
    	if (result == S_OK){
    		LPADRBOOK addressBook;
    		HRESULT res = currentSession->OpenAddressBook(NULL, NULL, NULL, &addressBook);
    		if (FAILED(res)){
    			return false;
    		}
    
    		
    		ULONG obj_type;
    		//open the root container of the address book
    		LPMAPICONTAINER rootContainer = NULL;
    		hr = addressBook->OpenEntry(0, NULL, NULL, 0, &obj_type,
    			(LPUNKNOWN*)&rootContainer);
     

    Then I go on to iterate through the root container, grabbing hierarchy tables then if these are empty then I assume they have content so I grab the content table and iterate over the table to get each contact. If this contact matches my search criteria then I add it to the list to return.

    This method works, but it's reasonably slow and on huge address books would be very inefficient.

    There must be a better way! Can anyone help? I've tried a few different methods I've been able to fine online but either they didn't work or I couldn't get them to work nicely.

    My environment is win7, visual studio express 2013 and I'm focusing on Outlook 2010 (but the more I can get this to work with the better)

    Jay

    Wednesday, April 8, 2015 10:54 AM

Answers

  • The best you can for a number lookup is to retrieve all rows (HrQueryAllRwos would do) requesting PR_ENTRYID and PR_BUSINESS_TELEPHONE_NUMBER_A, then do matching explicitly in your code.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    • Marked as answer by jaybaldwin1 Thursday, August 20, 2015 9:54 AM
    Monday, April 13, 2015 1:46 PM

All replies

  • Hello Jay,

    You need to use the FindRow method of the IMAPITable interface to find the row in a table that matches specific search criteria and move the cursor to that row.

    Also you may find the Restrict method of the IMAPITable interface helpful. It allows to apply a filter to a table, reducing the row set to only those rows matching the specified criteria. If there is a previous restriction, it is discarded and the new one applied. Applying a restriction has no affect on the underlying data of a table; it simply alters the view by limiting the rows that can be retrieved to rows containing data that satisfy the restriction.

    Wednesday, April 8, 2015 11:33 AM
  • Why not simply resolve a name using IAddrBook::ResolveName?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Wednesday, April 8, 2015 2:42 PM
  • Hi Eugene,

    Thank you for your response.

    I have came across these but I can't seem to get them to work.

    Have you got any idea what is wrong with this? My call to findRow is returning 0x8004010f which apparently corresponds to MAPI_E_NOT_FOUND

    LPMAPITABLE contactTable;
    
    newTable->GetContentsTable(0, &contactTable);
    			
    SRestriction srName;
    SPropValue spv;
    ULONG ulCount = NULL;
    srName.rt = RES_CONTENT;
    
    srName.res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_IGNORECASE;
    
    srName.res.resContent.ulPropTag = PR_ANR;
    
    spv.ulPropTag = PR_DISPLAY_NAME_A;
    spv.Value.lpszA = "steve pen";
    spv.dwAlignPad = 0;
    
    srName.res.resContent.lpProp = &spv;
    
    HRESULT hrs = contactTable->FindRow(&srName, BOOKMARK_BEGINNING, NULL);

    I think restrict would probably be more appropriate to my needs but if I can get this working then I can probably use restrict. 

    Thanks again


    Thursday, April 9, 2015 9:16 AM
  • MAPI_E_NOT_FOUND means no rows were found that matched the restriction. Try to use the QueryColumns method to make sure that such column exists in the table.

    Do you get the same results with the Restrict method?

    Thursday, April 9, 2015 10:50 AM
  • Most address book providers (unlike store providers) do not support arbitrary search queries. The only restriction that reliably works is a restriction on PR_ANR  (RES_PROPERTY / RELOP_EQ / PR_ANR). That restriction is used by Outlook itself when resolving ambiguous name.

    In case of GAL, you can also open PR_SEARCH template as IMAPIContainer and use it to perform the search.

    Or, as I mentioned before, use IAddrBook::ResolveName.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 1:26 PM
  • >MAPI_E_NOT_FOUND means no rows were found that matched the restriction.

    > Try to use the QueryColumns method to make sure that such column exists in the table

    Eugene, what the heck does MAPI_E_NOT_FOUND from FindRow have to do with QueryColumns?


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!


    Thursday, April 9, 2015 1:27 PM
  • Most address book providers (unlike store providers) do not support arbitrary search queries. The only restriction that reliably works is a restriction on PR_ANR  (RES_PROPERTY / RELOP_EQ / PR_ANR). That restriction is used by Outlook itself when resolving ambiguous name.

    In case of GAL, you can also open PR_SEARCH template as IMAPIContainer and use it to perform the search.

    Or, as I mentioned before, use IAddrBook::ResolveName.

    Hi Dmitry,

    Thank you for your posts.

    Can I not use PR_ANR with a RES_CONTENT restriction? I am trying to implement predictive/suggestive search, so for exmaple I would be able to return the contact "John Smith" if the user typed in "john s" so from the docs it seemed that RES_CONTENT was the most suitable option.

    How would I go about using PR_SEARCH? 

    And with ResolveName, it would work for our current aim, but I then want to provide searches based on phone numbers, which seems unavailable from ResolveNames (please correct me if I am wrong though!)

    Thanks again

    Thursday, April 9, 2015 2:14 PM
  • I don't think address book provider will expect a RES_CONTENT  restriction. And you will not be able to use it in FindRow, only in Restrict. Don't be concerned by RES_PROPERTY: the address book provider is free to use whatever algorithm it finds appropriate, most likely a prefix search.

    PR_SEARCH won't help you either since it is only used to restrict the contents table, not scroll to a particular entry.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 2:26 PM
  • Thanks Dmitry,

    Restrict is returning MAPI_E_TOO_COMPLEX

    Here is what I have 

    SRestriction srName;
    SPropValue spv;
    ULONG ulCount = NULL;
    srName.rt = RES_PROPERTY;
    
    spv.ulPropTag = PR_ANR;
    spv.Value.lpszA = "steve pen";
    spv.dwAlignPad = 0;
    
    srName.res.resProperty.lpProp = &spv;
    srName.res.resProperty.relop = RELOP_RE;
    srName.res.resProperty.ulPropTag = PR_ANR;
    
    HRESULT hRes = contactTable->Restrict(&srName, NULL);
    Any Ideas? This should be working in my mind.

    Thursday, April 9, 2015 3:20 PM
  • I thought the previous sentence described the idea.
    Thursday, April 9, 2015 3:26 PM
  • That looks fine to me. Whre does contactTable come from?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 3:26 PM
  • Isn't relop supposed to be RELOP_EQ?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!


    Thursday, April 9, 2015 3:27 PM
  • What idea? FindRow and QueryCiolumns are completely unrelated.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 3:30 PM
  • That got me an S_OK response.

    Now how do I go about reading the restricted rows? 

    I've just tried HrQueryAllRows but 0 rows returned. 

    Thought it was a long shot that it would work. but maybe it is because it was restricted to 0 rows? 

    Can I use a fuzzylevel with resproperty? 

    Thanks again

    Thursday, April 9, 2015 4:01 PM
  • The QueryColumns method returns a list of columns for the table. I suggested to check whether such property exists in the table. Where I am wrong?
    Thursday, April 9, 2015 4:07 PM
  • If you want to use HrQueryAllRows , just pass your restriction to that function - it will call IMAPITable::Restrict / QueryRows internally.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 4:08 PM
  • QueryColumns will return the set of columns previously set by calling SetColumns. You can specify TBL_ALL_COLUMNS to retrieve all available columns, but the provider is under no obligation to return each and every property available.

    QueryColumns simply tells you what columns will be returned when you call QueryRows. It has absolutely nothing to do with FindRow or Restrict.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 4:12 PM
  • I thought only a small number of columns tables contain by default. To query additional columns we need to specify them expliucitly (to include in the result set).
    Thursday, April 9, 2015 4:15 PM
  • You, you must always call SetColumns to specify which columns you want to be returned from QueryRows.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Thursday, April 9, 2015 5:02 PM
  • Hi Dmitry,

    I still don't think this is working correctly,

    ULONG Count;
    contactTable->GetRowCount(0, &Count);
    //at this point Count is 26 (the total amount of rows in the table)
    LPSRowSet contactRows = NULL;
    SizedSPropTagArray(3, sptCols) = { 3, PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS };
    
    HRESULT res = HrQueryAllRows(contactTable, (SPropTagArray*)&sptCols, &srName, NULL, Count, &contactRows);
    			
    Count = contactRows->cRows;
    //now Count is 0				                                                      
    for (int rowNum = 0; rowNum < Count; rowNum++){

    srName is the restriction I created earlier.

    I can't see why it's empty, There is definitely a contact with the name I am searching for and I can see this row if I do not restrict the table... 


    Friday, April 10, 2015 10:36 AM
  • How is contactTable retrieved?Have you tried to use the Unicode version of the property (PR_ANR_W)?

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Friday, April 10, 2015 1:31 PM
  • Aha, PR_AN_A works, 

    Is there anyway to test which I should use? as I'm assuming different providers will require each?

    Thanks Again Dmitry!

    Friday, April 10, 2015 3:35 PM
  • PR_ANR_A will always be supported, so I usually check if the name to be resolved includes any characters outside of the US ASCII range and switch to PR_ANR_W if the detection finds such characters.

    What version of Outlook are you using?


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Friday, April 10, 2015 9:08 PM
  • Awesome, 

    I'm developing on Outlook 2010 but hopefully aiming to release on as many versions as possible.

    I have all of this at least showing promise of working now,

    Do you have any idea on number look up? I imagine I can't use a restriction for this in QueryAllRows,  as it the past I've need to open the entry to get the phone numbers.

    Monday, April 13, 2015 1:41 PM
  • The best you can for a number lookup is to retrieve all rows (HrQueryAllRwos would do) requesting PR_ENTRYID and PR_BUSINESS_TELEPHONE_NUMBER_A, then do matching explicitly in your code.

    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    • Marked as answer by jaybaldwin1 Thursday, August 20, 2015 9:54 AM
    Monday, April 13, 2015 1:46 PM
  • Hi again Dmitry,

    Sorry to give you another issue but I've got something very strange which I'm struggling to figure out.

    ULONG Count;
    contactTable->GetRowCount(0, &Count);
    LPSRowSet contactRows = NULL;
    SizedSPropTagArray(3, sptCols) = { 3, PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS };
    HRESULT res = HrQueryAllRows(contactTable, (SPropTagArray*)&sptCols, &srName, NULL, Count, &contactRows);
    			
    Count = contactRows->cRows;
    				                                                      
    for (int rowNum = 0; rowNum < Count; rowNum++){
    
    	if (contactRows->aRow[rowNum].lpProps[1].Value.lpszW){ 
    
    		ULONG ContactObjectType = 0;
    		LPMAILUSER FullContact = 0;
    		SBinary wstrID; 
    		std::stringstream dets;
    
    		LPSPropValue entryRow = PpropFindProp(contactRows->aRow[rowNum].lpProps, contactRows->aRow[rowNum].cValues, PR_ENTRYID);
    
    		_PV* CountainerEntryId = NULL;
    					CountainerEntryId = &entryRow->Value;
    		ULONG CounttainerId = ContainerEntryId->bin.cb;
    					
    		ULONG type = contactRows->aRow[rowNum].lpProps[1].ulPropTag;
    		std::wstring wstr = contactRows->aRow[rowNum].lpProps[1].Value.lpszW;
    		ULONG sectype = contactRows->aRow[rowNum].lpProps[0].ulPropTag;
    		wstrID = contactRows->aRow[rowNum].lpProps[0].Value.bin;
    		ULONG lasttype = contactRows->aRow[rowNum].lpProps[2].ulPropTag;
    		std::wstring wstrEMAIL = contactRows->aRow[rowNum].lpProps[2].Value.lpszW;

    This is the code I am using to get the entry id so I can grab the full contact details using OpenEntry.

    But The EntryId I'm getting from this is blank. 

    The value object for the row after I find the property, when viewed in the visual studio debugger shows a bin attribute contianing {cb=58 lpb=0x06f95838 "" }

    I'm able to access the name and email address fine.

    Friday, April 17, 2015 9:22 AM
  • New items don't have the EntryID property set, but I don't think that is the case.

    You may also find the Try Not To Query All Rows article helpful.

    Friday, April 17, 2015 10:38 AM
  • If wstrID.cb is 56, how is it empty? wstrID.lpb will point to a byte array that contains 56 bytes. Do you mean it is empty when you cast it to a string? That is to be expected since the first 4 bytes most likely are 0x0.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Friday, April 17, 2015 4:12 PM
  • You are correct once again. (I think)

    The ID is actually 119950648 according to the watch panel in VS

    But when I pass this into addressBook->OpenEntry I get back 0x80040201 

    Visual Studio says this means "An event was unable to invoke ant of the subscribers"

    While Microsofts List of extended MAPI numeric result codes says "MAPI_E_UNKNOWN_ENTRYID"

    Which makes a lot more sense but I still can't see why?

    Here is the code I'm using which gets me even more confused as I grab the ID from the contact table then open it... what can be wrong?

    LPSPropValue entryRow = PpropFindProp(contactRows->aRow[rowNum].lpProps, contactRows->aRow[rowNum].cValues, PR_ENTRYID);
    
    _PV* CountainerEntryId = NULL;
    CountainerEntryId = &entryRow->Value;
    ULONG id = CountainerEntryId->bin.cb;
    LPENTRYID  ContainerId = (LPENTRYID) ContainerEntryId->bin.lpb;
    					
    HRESULT didWeOpenEntry = addressBook->OpenEntry(id, ContainerId, NULL, NULL, &ContactObjectType, (LPUNKNOWN *)&FullContact);
    

    Thanks Again! 

    Wednesday, April 22, 2015 12:20 PM
  • The error is MAPI_E_UNKNOWN_ENTRYID

    Did you retrieve the entry id from a folder (IMAPIFolder) or from IABContainer?


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Wednesday, April 22, 2015 3:22 PM
  • I tried to recreate your problem but could not replicate the error.  If it helps, the following example code drills down to the Contacts and retrieves some MailUser properties from a specific contact.

    STDMETHODIMP_(void) CAddin::addrTest()
    {
    	LPADRBOOK	pAddrBook = NULL;
    	HRESULT	hr = E_FAIL;
    
    	hr = m_pMapiSession->OpenAddressBook(NULL, NULL, 0, &pAddrBook);
    
    	if (pAddrBook)
    	{
    		LPABCONT pRoot = NULL, pOLABook = NULL, pContacts = NULL;
    		LPMAPITABLE pHTable = NULL;
    		LPSRowSet	lpRows = NULL;
    		LPENTRYID	lpEID = NULL;
    		bool	bHoldsRecipients = false, bHoldsContainers = false;
    		ULONG	ulObj;
    		SizedSPropTagArray(4, Hprops) = { 4, PR_DISPLAY_NAME, PR_OBJECT_TYPE, PR_CONTAINER_FLAGS, PR_ENTRYID };
    
    		hr = pAddrBook->OpenEntry(0, NULL, &IID_IABContainer, 0, &ulObj, (LPUNKNOWN *) &pRoot);
    
    		hr = pRoot->GetHierarchyTable(0, &pHTable);
    
    		hr = HrQueryAllRows(pHTable, (LPSPropTagArray)&Hprops, NULL, NULL, 0, &lpRows);
    
    		if (lpRows)
    		{
    			for (ULONG i = 0; i < lpRows->cRows; i++)
    			{
    				LPWSTR lpwName = lpRows->aRow[i].lpProps[0].Value.lpszW;
    
    				LONG	obj = lpRows->aRow[i].lpProps[1].Value.l;
    
    				LONG	cFlags = lpRows->aRow[i].lpProps[2].Value.l;
    
    				bHoldsRecipients = (cFlags & AB_RECIPIENTS) != 0;
    
    				bHoldsContainers = (cFlags & AB_SUBCONTAINERS) != 0;
    
    				if (bHoldsContainers && _wcsicmp(lpwName, L"Outlook Address Book") == 0)
    				{
    					hr = pRoot->OpenEntry(lpRows->aRow[i].lpProps[3].Value.bin.cb,
    						(LPENTRYID) lpRows->aRow[i].lpProps[3].Value.bin.lpb, &IID_IABContainer, 0, &ulObj, (LPUNKNOWN *) &pOLABook);
    
    					if (pOLABook)
    					{
    						LPMAPITABLE pOLABHTable = NULL;
    
    						hr = pOLABook->GetHierarchyTable(0, &pOLABHTable);
    
    						if (pOLABHTable)
    						{
    							LPSRowSet	lpOLABRows = NULL;
    
    							hr = HrQueryAllRows(pOLABHTable, (LPSPropTagArray)&Hprops, NULL, NULL, 0, &lpOLABRows);
    
    							if (lpOLABRows)
    							{
    								for (ULONG i = 0; i < lpOLABRows->cRows; i++)
    								{
    									LPWSTR lpwName = lpOLABRows->aRow[i].lpProps[0].Value.lpszW;
    
    									LONG	obj = lpOLABRows->aRow[i].lpProps[1].Value.l;
    
    									LONG	cFlags = lpOLABRows->aRow[i].lpProps[2].Value.l;
    
    									bHoldsRecipients = (cFlags & AB_RECIPIENTS) != 0;
    
    									bHoldsContainers = (cFlags & AB_SUBCONTAINERS) != 0;
    
    									if (bHoldsRecipients && _wcsicmp(lpwName, L"Contacts") == 0)
    									{
    										hr = pOLABook->OpenEntry(lpOLABRows->aRow[i].lpProps[3].Value.bin.cb,
    											(LPENTRYID)lpOLABRows->aRow[i].lpProps[3].Value.bin.lpb, &IID_IABContainer, 0, &ulObj, (LPUNKNOWN *)&pContacts);
    
    										if (pContacts)
    										{
    											LPMAPITABLE  pContactContents = NULL;
    
    											hr = pContacts->GetContentsTable(0, &pContactContents);
    
    											if (pContactContents)
    											{
    												LPSRowSet	lpCRows = NULL;
    
    												SizedSPropTagArray(3, uProps) = { 3, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ENTRYID };
    
    												hr = HrQueryAllRows(pContactContents, (LPSPropTagArray)&uProps, NULL, NULL, 0, &lpCRows);
    
    												if (lpCRows)
    												{
    													for (ULONG i = 0; i < lpCRows->cRows; i++)
    													{
    														LPWSTR	lpwName = lpCRows->aRow[i].lpProps[0].Value.lpszW;
    
    														LPWSTR	lpwAdd = lpCRows->aRow[i].lpProps[1].Value.lpszW;
    
    														if (_wcsicmp(lpwAdd, L"TestUser@example.com") == 0)
    														{
    															LPMAILUSER	pContactsUser = NULL;
    
    															ULONG cb = lpCRows->aRow[i].lpProps[2].Value.bin.cb;
    
    															LPENTRYID	lpEID = (LPENTRYID)lpCRows->aRow[i].lpProps[2].Value.bin.lpb;
    
    															hr = pContacts->OpenEntry(cb, lpEID, &IID_IMailUser, 0, &ulObj, (LPUNKNOWN *)&pContactsUser);
    															
    															if (pContactsUser)
    															{
    																LPSPropValue	pProps = NULL;
    																ULONG	ulCount = 0;
    
    																hr = pContactsUser->GetProps((LPSPropTagArray)&uProps, MAPI_UNICODE, &ulCount, &pProps);
    
    																LPWSTR	lpwContactName = pProps[0].Value.lpszW;
    
    																LPWSTR	lpwContactEmail = pProps[1].Value.lpszW;
    
    																MAPIFreeBuffer(pProps);
    
    																hr = pContactsUser->Release();
    															}
    														}
    													}
    
    													FreeProws(lpCRows);
    												}
    
    												hr = pContactContents->Release();
    											}
    
    											hr = pContacts->Release();
    										}
    									}
    								}
    
    								FreeProws(lpOLABRows);
    							}
    
    							hr = pOLABHTable->Release();
    						}
    					
    						hr = pOLABook->Release();
    					}
    				}
    			}
    
    			FreeProws(lpRows);
    		}
    
    		hr = pHTable->Release();
    
    		hr = pRoot->Release();
    
    		hr = pAddrBook->Release();
    	}
    
    }


    • Edited by RLWA32 Thursday, April 23, 2015 11:51 AM
    Thursday, April 23, 2015 11:49 AM