'System.Runtime.InteropServices.COMException: 'This function cannot be performed because the message has changed

I've been trying to build a VSTO Outlook Addin.

The Addin dose the following:

1- saves the email attachments to a certain path

2- writes the path of the saved attachments in the Email body

3- delete the attachments from the email

4- saves the email

My problem is whenever I try to save an email after editing it with the links, I get the following error:

System.Runtime.InteropServices.COMException: 'This function cannot be performed because the message has changed.

the error occurs only when trying to save the email using: getItem.Save();

I am not holding any references to the mailitem, and also releasing all the object using Marshal.ReleaseComObject(item);

the save function only occurs one time in the whole code.

Note: the exception isn't always thrown, sometimes the save works and sometimes not

How do I solve this problem ?

the code:

class SaveInvoice {

    dynamic activeWindow = Globals.ThisAddIn.Application.ActiveWindow();

    private MailItem getCurrentEmailObject() {
       
        try {
            if (activeWindow is Explorer) {
                dynamic i = activeWindow.currentFolder;
                if (activeWindow.Selection.Count > 0) {
                    object selObject = activeWindow.Selection[1];
                    if (selObject is MailItem) {
                        MailItem mailItem = (selObject as MailItem);
                        return mailItem;
                    }
                }
            }
            else {
                return activeWindow.currentitem;
            }
        } catch (System.Exception) {
            return null;
        }
        return null;
    }
    private void addLinkToEmail(string savedpath) {

        if (Globals.ThisAddIn.Application.ActiveWindow() is Explorer) {
            MailItem selObject = Globals.ThisAddIn.Application.ActiveExplorer().Selection[1];
            selObject.HTMLBody = savedpath + "<br>" + Environment.NewLine + selObject.HTMLBody;
            Marshal.ReleaseComObject(selObject);
            
        }
        else {
            MailItem selObject = Globals.ThisAddIn.Application.ActiveInspector().CurrentItem;
            selObject.HTMLBody = savedpath + "<br>" + Environment.NewLine + selObject.HTMLBody;
            Marshal.ReleaseComObject(selObject);
        }
    }
    private void saveEmail(string guid, string folderStoreID) {

        Application outlookApplication = new Application();
        NameSpace outlookNamespace = outlookApplication.GetNamespace("MAPI");      
        MailItem getItem = (MailItem)outlookNamespace.GetItemFromID(guid, folderStoreID);

        getItem.Save();   //the Expection is Thrown here
      
        Marshal.ReleaseComObject(getItem);
    }

    private string SaveFileTo(string initStorage, string fileName) {
        SaveFileDialog fd = new SaveFileDialog();
        fd.AddExtension = true;
        fd.ValidateNames = true;
        fd.FileName = fileName;
        fd.InitialDirectory = initStorage;
        fd.Filter = "PDF files|*.pdf";
        if (fd.ShowDialog() == DialogResult.OK)
            return fd.FileName;
        return "";
    }

    public void saveInvoice() {

        MailItem mailObject = getCurrentEmailObject();
        if (mailObject != null) {

            string CustomerName = "CutomerNameTest"
        
                foreach (Attachment attachment in mailObject.Attachments) {

                    string saveToPath = "stringPath";           

                    if (attachment.FileName.Contains(".pdf")) {

                        attachment.SaveAsFile(saveToPath);
                        attachment.Delete();
                        addLinkToEmail(saveToPath);
                    }
                }
                string guid = mailObject.EntryID;
                var folderStoreID = mailObject.Parent.StoreID;
                Marshal.ReleaseComObject(mailObject);

                try {
                    saveEmail(guid, folderStoreID);
                } catch (COMException e) {

                   MessageBox.Show(e.ToString());
                }
                activeWindow = null;
        }
    }
    private string getSenderEmailAddress(MailItem mail) {
        AddressEntry sender = mail.Sender;
        string SenderEmailAddress = "";

        if (sender.AddressEntryUserType == OlAddressEntryUserType.olExchangeUserAddressEntry
            || sender.AddressEntryUserType == OlAddressEntryUserType.olExchangeRemoteUserAddressEntry) {
            ExchangeUser exchUser = sender.GetExchangeUser();
            if (exchUser != null) {
                SenderEmailAddress = exchUser.PrimarySmtpAddress;
            }
        }
        else {
            SenderEmailAddress = mail.SenderEmailAddress;
        }
        return SenderEmailAddress;
    }
}

the invoice class is being called at the button click in the ribbon:

private void button1_Click(object sender, RibbonControlEventArgs e)
    {
        SaveInvoice currentData = new SaveInvoice();
        currentData.saveInvoice();
        //MessageBox.Show(currentData.getCurrentEmailData());
    }

Thanks in advance

EDIT 1:

i changed the code to:

class SaveInvoice {

    dynamic activeWindow = Globals.ThisAddIn.Application.ActiveWindow();

    private MailItem getCurrentEmailObject() {
       
        try {
            if (activeWindow is Explorer) {
                dynamic i = activeWindow.currentFolder;
                if (activeWindow.Selection.Count > 0) {
                    object selObject = activeWindow.Selection[1];
                    if (selObject is MailItem) {
                        MailItem mailItem = (selObject as MailItem);
                        return mailItem;
                    }
                }
            }
            else {
                return activeWindow.currentitem;
            }
        } catch (System.Exception) {
            return null;
        }
        return null;
    }
    private void addLinkToEmail(string savedpath, MailItem mailItem) {
        mailItem.HTMLBody = savedpath + "<br>" + Environment.NewLine + mailItem.HTMLBody;
    }
  
    public void saveInvoice() {

        MailItem mailObject = getCurrentEmailObject();
        if (mailObject != null) {

                foreach (Attachment attachment in mailObject.Attachments) {

                    string saveToPath = "savePath";
                    attachment.SaveAsFile(saveToPath);
                    attachment.Delete();
                    addLinkToEmail(saveToPath, mailObject);
                    
                }
                mailObject.Save();
                Marshal.ReleaseComObject(mailObject);
                activeWindow = null;
        }
    }
    private string getSenderEmailAddress(MailItem mail) {
        AddressEntry sender = mail.Sender;
        string SenderEmailAddress = "";

        if (sender.AddressEntryUserType == OlAddressEntryUserType.olExchangeUserAddressEntry
            || sender.AddressEntryUserType == OlAddressEntryUserType.olExchangeRemoteUserAddressEntry) {
            ExchangeUser exchUser = sender.GetExchangeUser();
            if (exchUser != null) {
                SenderEmailAddress = exchUser.PrimarySmtpAddress;
            }
        }
        else {
            SenderEmailAddress = mail.SenderEmailAddress;
        }
        return SenderEmailAddress;
    }
}

still getting the error sometimes, i am trying to use the same mailobject throughout the whole code, but the problem is occuring

EDIT 3:

i deleted all the other functions and left only one to save:

public void saveInvoice(){
MailItem mailObject = Globals.ThisAddIn.Application.ActiveWindow().Selection[1];
try {
    if (mailObject != null) {

        foreach (Attachment attachment in mailObject.Attachments) {
            string saveToPath = "saveToPath";
            attachment.SaveAsFile(saveToPath);
            attachment.Delete();
            mailObject.Save();
        }
    }

    Marshal.ReleaseComObject(mailObject);
    Marshal.ReleaseComObject(Globals.ThisAddIn.Application.ActiveWindow().Selection[1]);
    Marshal.ReleaseComObject(Globals.ThisAddIn.Application.ActiveWindow());
    Marshal.ReleaseComObject(Globals.ThisAddIn.Application);
}

catch (System.Exception){
    throw;
}

}

its called dirctly after the click event from Ribbon1.cs

     private void button1_Click(object sender, RibbonControlEventArgs e)
    {
       saveInvoice();
    }

the System.Runtime.InteropServices.COMException is still sometimes occurring when trying to save the mailitem (mailObject.Save()), I released ALL the underlying COM objects that i know off



Solution 1:[1]

In VSTO add-ins you should use a safe Application instance provided by the ThisAddin class instead of creating a new instance in the code.

 private void saveEmail(string guid, string folderStoreID) {

        Application outlookApplication = new Application();

The Program VSTO Add-ins states the following:

You can start writing your VSTO Add-in code in the ThisAddIn class. Visual Studio automatically generates this class in the ThisAddIn.vb (in Visual Basic) or ThisAddIn.cs (in C#) code file in your VSTO Add-in project. The Visual Studio Tools for Office runtime automatically instantiates this class for you when the Microsoft Office application loads your VSTO Add-in.

To access the object model of the host application, use the Application field of the ThisAddIn class. This field returns an object that represents the current instance of the host application.

Your code may look like that instead:

 private void saveEmail(string guid, string folderStoreID) {

     Application outlookApplication = Globals.ThisAddin.Application;

You need to review the code base and refactor functions to re-use the retrieved Outlook item instead of getting it in each function separately. For example, almost in all functions I see the following:

 if (Globals.ThisAddIn.Application.ActiveWindow() is Explorer) {
            MailItem selObject = Globals.ThisAddIn.Application.ActiveExplorer().Selection[1];

But at the same time there is a function which returns the currently shown item:

private MailItem getCurrentEmailObject() {
       
        try {
            if (activeWindow is Explorer) {
                dynamic i = activeWindow.currentFolder;
                if (activeWindow.Selection.Count > 0) {
                    object selObject = activeWindow.Selection[1];
                    if (selObject is MailItem) {
                        MailItem mailItem = (selObject as MailItem);
                        return mailItem;
                    }
                }
            }
            else {
                return activeWindow.currentitem;
            }
        } catch (System.Exception) {
            return null;
        }
        return null;
    }

So, you need to extract it once and re-use through the code until you are done with all you modifications/work.

Also I'd recommend releasing underlying COM objects instantly. For example:

MailItem selObject = Globals.ThisAddIn.Application.ActiveExplorer().Selection[1];

A single line of code contains multiple property and method calls that return COM objects. There objects are left in memory when you are done. The ActiveExplorer method returns an instance of the Explorer class. Then you call the Selection property which returns the Selection object which also was left in memory.

Also I noticed that you use the foreach loop in the code:

foreach (Attachment attachment in mailObject.Attachments) {

Note, in this case the attachment object is not release with each iteration. I'd suggest using the for instead where you could retrieve the attachment object explicitly and then release it with each iteration.

Note, there is no need to call the Save method each time an attachment is removed. So, place the following call outside of the loop:

mailObject.Save();

Finally, the HTMLBody property returns a string which represents the message body in the HTML format. So, when you set any value to the property you need to be sure that you set a valid HTML string, for example:

private void addLinkToEmail(string savedpath, MailItem mailItem) {
        mailItem.HTMLBody = savedpath + "<br>" + Environment.NewLine + mailItem.HTMLBody;
    }

The code inserts a path string before the HTML string, that means before the <html> or <body> tags. Instead, you need to find the opening <body> tag and insert your string there to preserve any formatting.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1