Search code examples
c#outlookoffice-interop

Unable to delete over 400 contacts from Outlook using Office Interop


I have this code to delete all contacts from Microsoft Outlook that have a specific user property:

public void PurgePTSContacts(ref long rlCount)
{
    int iLastPercent = 0;
    rlCount = 0;

    try
    {

        // Get the MAPI namespace object (not sure exactly what this is)
        Outlook.NameSpace MAPI = _OutlookApp.GetNamespace("MAPI");
        if (MAPI != null)
        {
            // Now get the default Outlook Contacts folder
            Outlook.MAPIFolder oFolder = MAPI.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
            if (oFolder != null)
            {
                // Ensure it is a calendar folder.  This test is not strictly required.
                if (oFolder.DefaultItemType == Outlook.OlItemType.olContactItem)
                {
                    // Get the collect of items from the calendar folder
                    Outlook.Items oFolderItems = oFolder.Items;
                    if (oFolderItems != null)
                    {
                        int iNumItems = oFolderItems.Count;

                        // Progress to do

                        int iItem = 0;
                        foreach (object oItem in oFolderItems)
                        {
                            iItem++;

                            if(oItem is Outlook.ContactItem )
                            {
                                Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);

                                int iPercent = ((iNumItems - iItem) + 1) * 100 / iNumItems;
                                if (iPercent >= iLastPercent + 5 || iPercent == 100)
                                {
                                    iLastPercent = iPercent;
                                    // Show progress
                                }

                                Outlook.UserProperties oContactDetails = oContact.UserProperties;
                                if (oContactDetails != null)
                                {
                                    if (oContactDetails.Find("xxxxx") != null)
                                    {
                                        oContact.Delete();
                                        rlCount++;
                                    }
                                }

                                oContact = null;
                            }
                        }
                    }
                }
            }
        }
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
    }
}

Now, what i don't understand is that when I import the contacts, there are 428. But when I delete the contacts, it only does just over 200. Then I have to repeatedly call my PurgePTSContacts method and it purges some more.

With each subsequent call less and less are purged until it is 0.

Why can't I purge all 400x in one function call?


Solution

  • Do not use foreach when you are modifying the collection - use a down loop:

    for (int i = FolderItems.Count; i >= 1; i--)
    {
      object oItem = FolderItems[i];
      Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);
      if (oContact  != null)
      {
        ...
        Marshal.ReleaseComObject(oContact);
      }
      Marshal.ReleaseComObject(oItem);
    }
                        
    

    UPDATE. Per OP request, an example that shows how to delete all items in a folder using Redemption (I am its author):

    RDOSession session = new RDOSession();
    session.MAPIOBJECT = MAPI.MAPIOBJECT; //an instance of the Namespace object from your snippet above
    RDOFolder rFolder = session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts); //or you can use RDOSession.GetFolderFromID if you already have an Outlook.MAPIFolder
    rFoldder.EmptyFolder ();
    

    If you want to only delete contacts (but not distribution lists), you can retreive their entry ids first using RDOFolder.Items.MAPITable.ExecSQL("SELECT EntryID from Folder where MessageClass LIKE 'IPM.Contact%' ") (ExecSQL returns an instance of the ADODB.Recordset COM object), use it to to build an array of entry ids, and pass it to RDOFolder.Items.RemoveMultiple().

    To create an instance of the RDOSession object, you can also use the RedemptionLoader class if you don't want to register redemption.dll in the registry using regsvr32.exe (you can just place redemption.dll alongside your executable).