The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)

I've discussed about the “wrong” approach in a previous post of mine, where I also talked about why using PIMPR_SMARTPROP to retrieve the info about the last way a Windows Mobile-device user communicated with a given contact. Recently 2 MSDN Forums users asked for help about this (“How to get the information of a selected phonecall number?” and “How to read LastNumber in contact”) and therefore I wanted to invest some time for the Community, hoping this may help others as well! smile_shades (and also to play with POOM, since managed APIs wrapped so many properties making POOM _quite_ obsolete…)

Before spreading the code, let me state a thing: the SmartProp property is not set for a contact until the first time the user
explicitly changes the default contact method, or it is otherwise explicitly set by an application
. I noticed this when coding this sample, and later I understood that this is the expected behavior.

Ehy, remember that this is not production code: this is for testing\didactic purposes only… indeed the code is simply meant to dump out to a text-file only the First Name, Last Name and the string representing the last way user communicated with the selected contact. The code doesn’t even use the IPOutlookItemCollection::Find to get a specific contact, as this was not the goal here. Yet, I think it was worth sharing as it is, so that who wants can customize it… smile_regular enjoy!!

 

NOTES:

  1. PIMPR_SMARTPROP is a property of the IItem, not of the IContact: that’s why for example the old POOM NETCF sample code didn’t contain it. So what we need is to retrieve the collection of Contacts and handle them as IItem.
  2. Once you have all the contacts as a collection (IPOutlookItemCollection), you can’t directly retrieve each item – even if IItem implements IDispatch, the right way is the one for example mentioned by my colleague Xiaoyun Li here, i.e. invoke IPOutlookItemCollection::Item by passing a IContact as IDispatch, then call IContact::get_Oid to retrieve the unique OID and finally use this with IPOutlookApp2::GetItemFromOidEx (see function DumpOutToText below).
  3. At this point you have an IItem object and can query its properties in the usual old way, considering that PIMPR_SMARTPROP will return the property id (e.g. PIMPR_MOBILE_TELEPHONE_NUMBER, PIMPR_SMS, etc); all the propIDs that can be returned as smart prop are listed in the documentation here.
  4. For each returned property, remember to check if it was really found ( .wFlags != CEDB_PROPNOTFOUND) and if it’s of the expected data type ( .propid == CEVT_LPWSTR).

 

If you know of any smarter way I’ll be more than welcome on continuing this saga about PIMPR_SMARTPROP… smile_teeth

 

 #include "stdafx.h"
#include <pimstore.h>

// **************************************************************************
// Globals
IPOutlookApp2 * g_polApp = NULL;
IUnknown * g_pUnknown = NULL;
LPCTSTR g_pszFilename = TEXT("contacts.txt");

// **************************************************************************
//Functions
HRESULT InitPoom(void);
HRESULT GetPoomFolder(int nFolder, IFolder ** ppFolder);
HRESULT FillFileWithContacts(void);
HRESULT DumpOutToText(IPOutlookItemCollection * pItemCol);
HRESULT WriteItemSmartProp(IItem *pItem);
HRESULT Log(LPTSTR szLog);
HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename);

// **************************************************************************
//MAIN
int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr = E_FAIL;

    //Initialize POOM
    hr = InitPoom();
    CHR(hr);

    //Fill file with Contacts
    hr = FillFileWithContacts();
    CHR(hr);
    
    //Success
    MessageBox(NULL, TEXT("Done"), TEXT("Test"), MB_OK);

Exit:
    return 0;
}


// ************************************************************************** 
//InitPoom
HRESULT InitPoom(void) 
{
    HRESULT hr = E_FAIL;

    hr = CoInitializeEx(NULL, 0);
    CHR(hr);

    hr = CoCreateInstance(CLSID_Application, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&g_pUnknown);
    CHR(hr);

    hr = g_pUnknown->QueryInterface(IID_IPOutlookApp, (void**)&g_polApp); 
    CHR(hr);

    hr = g_polApp->Logon(NULL);
    CHR(hr);

    //success
    hr = S_OK;

Exit:
    RELEASE_OBJ(g_polApp);
    return hr;
}


// ************************************************************************** 
//FillFileWithContacts
HRESULT FillFileWithContacts(void)
{
    HRESULT hr = E_FAIL;
    IFolder * pCurrFldr = NULL;
    IPOutlookItemCollection * pItemCol = NULL;
    
    // Get the Contacts folder and its items
    hr = GetPoomFolder(olFolderContacts, &pCurrFldr);
    CHR(hr);
        
    if (SUCCEEDED(pCurrFldr->get_Items(&pItemCol)))
    {
        //Dump out First Name, Last Name, SMARTPROP for each item
        hr = DumpOutToText(pItemCol);
        CHR(hr);
    }

    //success 
    hr = S_OK;

Exit:
    RELEASE_OBJ(pItemCol);
    RELEASE_OBJ(pCurrFldr);
    return hr;
}


// **************************************************************************
//GetPoomFolder
HRESULT GetPoomFolder(int nFolder, IFolder ** ppFolder)
{
    HRESULT hr = E_FAIL;
    if (SUCCEEDED(g_polApp->GetDefaultFolder(nFolder, ppFolder)))
    {
        hr = S_OK;
    }

    return hr;
}


// ************************************************************************** 
//DumpOutToText
HRESULT DumpOutToText(IPOutlookItemCollection * pItemCol)
{
    HRESULT hr = E_FAIL;
    IContact * pContact = NULL;
    IItem * pItem = NULL;
    CEOID oid = 0;   
    int cItems = 0;

    //Count contacts
    pItemCol->get_Count(&cItems);    
    for (int i = 1; i <= cItems; i++)
    {
        //get the item as a IContact
        if (SUCCEEDED(pItemCol->Item (i, reinterpret_cast<IDispatch**>(&pContact))))
        {
            //convert the IContact into a IItem...
            if (SUCCEEDED(pContact->get_Oid((long*)&oid)))
            {
                //... by using the Oid
                if (SUCCEEDED(g_polApp->GetItemFromOidEx(oid, 0, &pItem)))
                {
                    //Write item's properties to file
                    hr = WriteItemSmartProp(pItem);
                    
                    RELEASE_OBJ(pItem);
                    CHR(hr);
                }
            }
        }
    }

    //success
    hr = S_OK;

Exit:
    return hr;
}


// **************************************************************************
//GetItemSmartProp
HRESULT WriteItemSmartProp(IItem *pItem)
{
    HRESULT hr = E_FAIL;
    int cProps = 20;
    CEPROPID rgPropId[20] = {0};
    CEPROPVAL *prgPropvalUser = NULL;
    ULONG cbBuffer = 0;

    // FROM https://msdn.microsoft.com/en-us/library/bb415504.aspx
    // The Smart Property (PIMPR_SMARTPROP) is the Contact property that contains the property ID of the default communication mode.
    rgPropId[0] = PIMPR_FIRST_NAME; //type: CEVT_LPWSTR
    rgPropId[1] = PIMPR_LAST_NAME;  //type: CEVT_LPWSTR
    rgPropId[2] = PIMPR_SMARTPROP;  //type: CEVT_UI4

    //all of the following are of type: CEVT_LPWSTR
    rgPropId[3] = PIMPR_IM2_ADDRESS;                    
    rgPropId[4] = PIMPR_ASSISTANT_TELEPHONE_NUMBER;        
    rgPropId[5] = PIMPR_BUSINESS_TELEPHONE_NUMBER;        
    rgPropId[6] = PIMPR_BUSINESS2_TELEPHONE_NUMBER;        
    rgPropId[7] = PIMPR_CAR_TELEPHONE_NUMBER;            
    rgPropId[8] = PIMPR_COMPANY_TELEPHONE_NUMBER;        
    rgPropId[9] = PIMPR_EMAIL1_ADDRESS;                    
    rgPropId[10] = PIMPR_EMAIL2_ADDRESS;                    
    rgPropId[11] = PIMPR_EMAIL3_ADDRESS;                
    rgPropId[12] = PIMPR_HOME_TELEPHONE_NUMBER;            
    rgPropId[13] = PIMPR_HOME2_TELEPHONE_NUMBER;        
    rgPropId[14] = PIMPR_IM1_ADDRESS;                                        
    rgPropId[15] = PIMPR_IM3_ADDRESS;                    
    rgPropId[16] = PIMPR_MOBILE_TELEPHONE_NUMBER;        
    rgPropId[17] = PIMPR_PAGER_NUMBER;                    
    rgPropId[18] = PIMPR_RADIO_TELEPHONE_NUMBER;        
    rgPropId[19] = PIMPR_SMS;                            


    //FROM: https://msdn.microsoft.com/en-us/library/ms859378.aspx
    // Allocate memory, then get item properties
    cbBuffer = 0;
    hr = pItem->GetProps(rgPropId, 0, cProps, &prgPropvalUser, &cbBuffer, NULL);
    if(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr)
    {
        prgPropvalUser = (CEPROPVAL *) LocalAlloc(0, cbBuffer); 
    }

    // cbBuffer is set to the number of bytes required to hold the data.
    hr = pItem->GetProps(rgPropId, 0, cProps, (CEPROPVAL **)&prgPropvalUser, &cbBuffer, NULL);

    //better error-checking to do here...
    if(FAILED(hr) || 0 == cbBuffer)
    {
        goto Exit;
    }

    if(prgPropvalUser[0].wFlags!=CEDB_PROPNOTFOUND)   
    {   
        if(LOWORD(prgPropvalUser[0].propid) == CEVT_LPWSTR)
        {
            Log(prgPropvalUser[0].val.lpwstr);
            Log(TEXT("\t"));
        }
    }

    if(prgPropvalUser[1].wFlags!=CEDB_PROPNOTFOUND)   
    {   
        if(LOWORD(prgPropvalUser[1].propid) == CEVT_LPWSTR)  
        {
            Log(prgPropvalUser[1].val.lpwstr);
            Log(TEXT("\t"));
        }
    }

    if(prgPropvalUser[2].wFlags!=CEDB_PROPNOTFOUND)   
    {   
        if(LOWORD(prgPropvalUser[2].propid) == CEVT_UI4)   
        {
            switch (prgPropvalUser[2].val.ulVal)
            {
                case PIMPR_IM2_ADDRESS:
                    if(prgPropvalUser[3].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[3].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[3].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_ASSISTANT_TELEPHONE_NUMBER:
                    if(prgPropvalUser[4].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[4].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[4].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_BUSINESS_TELEPHONE_NUMBER:    
                    if(prgPropvalUser[5].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[5].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[5].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_BUSINESS2_TELEPHONE_NUMBER:    
                    if(prgPropvalUser[6].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[6].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[6].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_CAR_TELEPHONE_NUMBER:        
                    if(prgPropvalUser[7].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[7].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[7].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_COMPANY_TELEPHONE_NUMBER:
                    if(prgPropvalUser[8].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[8].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[8].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_EMAIL1_ADDRESS:                
                    if(prgPropvalUser[9].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[9].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[9].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_EMAIL2_ADDRESS:                
                    if(prgPropvalUser[10].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[10].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[10].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_EMAIL3_ADDRESS:                
                    if(prgPropvalUser[11].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[11].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[11].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_HOME_TELEPHONE_NUMBER:        
                    if(prgPropvalUser[12].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[12].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[12].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_HOME2_TELEPHONE_NUMBER:        
                    if(prgPropvalUser[13].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[13].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[13].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_IM1_ADDRESS:                    
                    if(prgPropvalUser[14].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[14].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[14].val.lpwstr);                            
                        }
                    }
                    break;
                case PIMPR_IM3_ADDRESS:                    
                    if(prgPropvalUser[15].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[15].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[15].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_MOBILE_TELEPHONE_NUMBER:        
                    if(prgPropvalUser[16].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[16].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[16].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_PAGER_NUMBER:                
                    if(prgPropvalUser[17].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[17].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[17].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_RADIO_TELEPHONE_NUMBER:        
                    if(prgPropvalUser[18].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[18].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[18].val.lpwstr);
                        }
                    }
                    break;
                case PIMPR_SMS:                            
                    if(prgPropvalUser[19].wFlags!=CEDB_PROPNOTFOUND)   
                    {   
                        if(LOWORD(prgPropvalUser[19].propid) == CEVT_LPWSTR)   
                        {
                            Log(prgPropvalUser[19].val.lpwstr);
                        }
                    }
                    break;
            } //switch
        }            
    }
    Log(TEXT("\r\n"));

    //success
    hr = S_OK;

Exit:
    LocalFree(prgPropvalUser); 
    return hr;
}
  
  
 // ************************************************************************** 
// Log 
HRESULT Log(LPTSTR szLog)
{
    HRESULT hr = E_FAIL;
    hr = LogToFile(szLog, g_pszFilename);
    CHR(hr);

Exit:
    return hr;
}


// ************************************************************************** 
// LogToFile 
// Writes szLog into the file named pszFilename
HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename)
{
    HRESULT hr = E_FAIL;
    
    //Open the handle to the file (and create it if it doesn't exist
    HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hFile)
        goto Exit;

    //Set the pointer at the end so that we can append szLog
    DWORD dwFilePointer = SetFilePointer(hFile, 0, NULL, FILE_END);
    if (0xFFFFFFFF == dwFilePointer)
        goto Exit;

    //Write to the file
    DWORD dwBytesWritten = 0;
    BOOL bWriteFileRet = WriteFile(hFile, szLog, wcslen(szLog) * 2, &dwBytesWritten, NULL);
    if (!bWriteFileRet)
        goto Exit;

    //Flush the buffer
    BOOL bFlushFileBuffersRet = FlushFileBuffers(hFile);
    if (!bFlushFileBuffersRet)
        goto Exit;

    //Success
    hr = S_OK;

Exit:
    if (NULL != hFile)
    CloseHandle(hFile);

    return hr;
}
  

Cheers,
~raffaele