none
Redemption IMAPIFolder.CopyMessages(MESSAGE_MOVE): MAPI_E_NO_ACCESS

    Question

  • Hi all,

    I posted several weeks ago regarding a speed problem with the Outlook library accessing and working with MailItem's and was pointed towards Redemption.

    I semi-successfully implemented Redemption into my program, which very simply goes through the Inbox of the current user and moves each message over a day old into the folder for that person - creating a folder where one does not already exist.

    When I run the application with the Outlook library it works, but is slow.

    When I run it with Redemption, sometimes it works and sometimes I get:

    Redemption IMAPIFolder.CopyMessages(MESSAGE_MOVE): MAPI_E_NO_ACCESS
    ulVersion: 0
    Error: You do not have sufficient permission to perform this operation on this object.  See the folder contact or your system administrator.
    Component: Microsoft Exchange Server Information Store
    ulLowLevelError: 0
    ulContext: 1288

    It doesn't seem to happen every time and each time I run it it gets a bit further, but it will not do it all at once - not every time anyway.

    Can anyone suggest a reason for this?

    The Redemption setup is here:

    private void SortEmailUsingRedemptionLib(ref StreamWriter swLog)
    {
    	// connection object used for all transactions while we process this inbox
    	SqlConnection con = new SqlConnection(msSETTING_CONN_STR);
    	con.Open();
    
    	// setup the outlook environment and bring in the objects we'll be working with
    	Redemption.RDOSession rSession = new Redemption.RDOSession();
    	rSession.MAPIOBJECT = Application.Session.MAPIOBJECT;
    	Redemption.RDOFolder fInbox = rSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderInbox);
    	Redemption.RDOFolder fDeleted = rSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderDeletedItems);
    	Redemption.RDOFolder fProcess = null;
    
    	// used to save into the database against a user
    	msCurrentUser = rSession.CurrentUser.SMTPAddress;
    
    	// if msSETTING_USE_SUBFOLDER is set, use that folder INSIDE inbox.. if it can be found
    	if (msSETTING_USE_SUBFOLDER == string.Empty)
    		fProcess = fInbox;
    	else
    		fProcess = FindFolder(msSETTING_USE_SUBFOLDER, fInbox.Folders);
    
    	// create the progress bar form and show it
    	SetupProgressBar(fProcess.Items.Count + 1);
    
    	// process the folder
    	ProcessFolder(ref con, ref fProcess, ref swLog);
    
    	// clean up
    	con.Close();
    	fInbox = null;
    	fDeleted = null;
    	rSession = null;
    	mfProgressBar.Dispose();
    }

    And the code that does the work is as follows:

    private void ProcessFolder(ref SqlConnection con, ref Redemption.RDOFolder fProcess, ref StreamWriter swLog)
    {
    	int idx;
    	System.Collections.IEnumerator ienumMailItems;
    	Redemption.RDOMail mi;
    
    	// get all the items sat in outlook. not all will be mail items - some are calendar events etc.
    	ienumMailItems = fProcess.Items.GetEnumerator();
    
    	idx = 1; // used for progress bar
    
    	// interate through the list of items, find the ones that are mail items, and process per settings
    	while (ienumMailItems.MoveNext())
    	{
    		try
    		{
    			mi = (Redemption.RDOMail)ienumMailItems.Current;
    
    			// update the progress bar
    			mpbProgress.Value = idx;
    			mpbProgress.Refresh();
    
    			// this allows the form to catch up - specifically it is to process the cancel event from the button
    			System.Windows.Forms.Application.DoEvents();
    
    			ProcessFolderSort(ref con, ref fProcess, ref mi, ref swLog);
    
    			if (mi != null)
    			{
    				System.Runtime.InteropServices.Marshal.ReleaseComObject(mi);
    				GC.Collect();
    			}
    
    			idx++;
    
    			// handle the cancel button which sets a flag if pressed
    			if (mbCancelSort)
    				break;
    		}
    		catch (InvalidCastException)
    		{
    			// skip these over - we don't care - we're only looking for MailItem's in the folder
    			if (miSETTING_LOG_LEVEL >= 2)
    				LogText(ref swLog, "** INVALID CAST EXCEPTION **");
    		}
    
    	} // while (ienumMailItems.MoveNext())
    }
    
    private void ProcessFolderSort(ref SqlConnection con, ref Redemption.RDOFolder fInbox, ref Redemption.RDOMail mi, ref StreamWriter swLog)
    {
    	if (!mi.UnRead && (
    							 (mi.SentOn < DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0))
    							&& mi.FlagStatus == (int)EmailFlagStatus.olNoFlagIcon
    							&& mi.Importance != (int)EmailImportance.olImportanceHigh)
    						||
    							 (mi.SentOn < DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0))
    							&& mi.Importance == (int)EmailImportance.olImportanceHigh))
    							)
    	{
    		Redemption.RDOFolder mfldFolder = FindFolder(mi.SenderName, fInbox.Folders);
    
    		// if you can't find a folder by the same name as the sender, create one
    		if (mfldFolder == null)
    			mfldFolder = fInbox.Folders.Add(mi.SenderName, Outlook.OlDefaultFolders.olFolderInbox);
    
    		// move the message to the folder
    		mi.Move(mfldFolder); // <--- ERROR HAPPENS HERE
    
    		msAudit = string.Format("FOLDER SORT: moved message \"{0}\" from \"{1}\" their folder.", mi.Subject, mi.SenderName);
    		Audit(ref con, eAuditTypes.FolderSort, msAudit);
    
    		// do no more work on this
    		mi = null; 
    		bWorkDone = true;
    	}
    }
    
    private Redemption.RDOFolder FindFolder(string sSearch, Redemption.RDOFolders fsFolderList)
    {
    	foreach (Redemption.RDOFolder fFolder in fsFolderList)
    	{
    		if (fFolder.Name == sSearch)
    			return fFolder;
    	}
    	return null;
    }

    Thanks in advance for any help,

    Dom

    Thursday, September 23, 2010 8:29 AM

All replies

  • Instead of using an enumerator, try to use a "for" loop going from Count down to 1. This way you will avoid modifying the collection as you enumerate it.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Thursday, September 23, 2010 4:51 PM
  • Also, there is no reason to loop through the subfolders lookign for a particular fodler - RDOFolders.Item() takes either in integer index (1 through Count) or a string (th name of the subfolder ).


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Thursday, September 23, 2010 4:52 PM
  • Hiya,

    Thanks for the reply. I've changed the code to go through the collection as per the snippet below. Unfortunately the error is still occurring. 

    //while (ienumMailItems.MoveNext())
    for (int iLoop = fProcess.Items.Count - 1; iLoop >= 0; iLoop--)
    {
    	try
    	{
    		//mi = (Redemption.RDOMail)ienumMailItems.Current;
    		mi = (Redemption.RDOMail)fProcess.Items[iLoop];
    

    It processes two or three at a time but then fails again - same error - this is a very typical run:

    08:36:59:527
    08:36:59:527: ----- Load Settings Values -----
    08:36:59:527: Logging Level: 2
    Folder Sort: On
    Download Inbox: Off
    Investment Case: Off
    Hard Delete: Off
    Soft Delete After: 2 days
    Hard Delete After: 28 Days
    Research Email: shawd@ttint.com
    Database Enabled: On
    Data Source=.\SQL2005;Initial Catalog=investment_case;User Id=sa;Password=;
    08:36:59:527: --------------------------------
    08:36:59:558: ---------- START PROCESS INBOX ----------
    08:36:59:761: FOLDER SORT: moved message "FW: Investment Support Project" from "Gavin Cuthbert" their folder.
    08:37:00:073: FOLDER SORT: moved message "FW: Template Amendments for Research Project" from "Gavin Cuthbert" their folder.
    08:37:00:511: FOLDER SORT: moved message "DEV - SCHEDULER - SchedulerStatusPhone was executed at 26/08/2010 16:30:08" from "TT_SSRS" their folder.
    08:37:01:058: FOLDER SORT: moved message "Evening Checks for 26/08/2010" from "Jeff Jeffery" their folder.
    08:37:01:777: ********************************************************
    08:37:01:777: Error in IMAPIFolder.CopyMessages(MESSAGE_MOVE): MAPI_E_NO_ACCESS
    ulVersion: 0
    Error: You do not have sufficient permission to perform this operation on this object. See the folder contact or your system administrator.
    Component: Microsoft Exchange Server Information Store
    ulLowLevelError: 0
    ulContext: 1288
    08:37:01:777: ********************************************************
    
    08:37:01:777: ----------- END LOG -----------
    08:37:01:777: *

    Thanks as well for the tip on the folder - I'll get the changed.

    Have you seen this happen before? We are on  Server 2003 - Exchange 2007 (SP1), WinXP - Outlook 2003 (SP3). I saw a link saying this error was related to Exchange 2003 SP1 and before but we are 2007.

    Any ideas on things to try very gratefully received.

    Thanks,

    Dom

    Friday, September 24, 2010 7:45 AM
  • This is weird... But when you run the same code again, does that message get moved? Does it always error out on the same message? Can you log the mesage subject before it gets moved?

    RDOItems collection is 1 based, not 0, so your loop needs to be

    for (int iLoop = fProcess.Items.Count; iLoop >= 1; iLoop--)


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Friday, September 24, 2010 1:45 PM
  • Hiya Dmitry,

    I moved the log to the front to confirm that;

     

    15:18:03:910: ---------- START PROCESS INBOX ----------
    
    15:18:04:114: FOLDER SORT: moved message "Datawarehouse : Duplicate securities" from "TT_SSRS" their folder.
    15:18:04:895: FOLDER SORT: moved message "Datawarehouse : Index Weight Monitor" from "TT_SSRS" their folder.
    15:18:05:051: FOLDER SORT: moved message "DEV - SCHEDULER - SchedulerStatusPhone was executed at 31/08/2010 10:05:09" from "TT_SSRS" their folder.
    15:18:05:192: FOLDER SORT: moved message "LVTS Support" from "Gavin Cuthbert" their folder.
    15:18:05:504: FOLDER SORT: moved message "DEV - SCHEDULER - SchedulerStatusPhone was executed at 31/08/2010 12:05:04" from "TT_SSRS" their folder.
    15:18:09:645: ********************************************************
    15:18:09:645: Error in IMAPISession.OpenEntry: MAPI_E_CALL_FAILED

     

    and then another run...

     

    15:18:14:411: ---------- START PROCESS INBOX ----------
    
    15:18:14:426: FOLDER SORT: moved message "DEV - SCHEDULER - SchedulerStatusPhone was executed at 31/08/2010 12:05:04" from "TT_SSRS" their folder.
    15:18:14:598: FOLDER SORT: moved message "Emailing: LVNotify in the LVTS Middle-Tier.pdf" from "Andy Finch" their folder.
    ...
    15:18:19:567: ********************************************************
    15:18:19:567: Error in IMAPIFolder.CopyMessages(MESSAGE_MOVE): MAPI_E_NO_ACCESS

     

    So you can see that it is successfully moving the item that it previously failed on...

    Thanks for the Items collection tip - I'd actually just recently changed that when I tried to run it through without moving the objects! (which ran, and error'd at the end when the index couldn't be found for 0! :)

    Any thoughts? I can't help feeling like it is more likely to be Outlook that's the problem than Redemption... I'm surprised others haven't hit similar problems though which gives me some hope?

    Dom

    EDIT: note as well the error isn't necessarily always happening in the same place. in the first error log its happening when it is being opened, in the second where it is being moved...

    Friday, September 24, 2010 2:21 PM
  • Firstly, try to avoid using multiple dot notaation: cache the RDOItems collection before entering the loop:

    RDOItems items = fProcess.Items;

    for (int iLoop = items.Count - 1; iLoop >= 0; iLoop--)
    {
    try
    {
          mi = items.Item(iLoop);

    Secondly, can you try to copy the message, and the delete it?

    mi.CopyTo(mfldrFolder);

    mi.Delete;


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Friday, September 24, 2010 7:24 PM
  • Morning Dmitry, hope you had a good weekend!

    The timezones are working against us unfortunately but thank you very much for your reply.

    I tried both actions you proposed and I have seen an enormous difference in reliability. It now runs at least 4 in 5 tests completely to the end with no errors. The big difference seems to be due to the multiple dot notation - CopyTo/Delete and Move both generate the error with a similar frequency.

    Before it happened at least 19 in 20 runs and usually only processed 5-10 mail items before failing; now it is doing about 500 messages 4 out of 5 runs. Unfortunately my mail box is a lot smaller than the end users and this will ultimately not meet requirements :(

    It's very interesting that removing this multiple dot seems to have made such a difference and I am looking at my code for more instances where this approach could be taken. Could you explain why this is the case? I had rather foolishly assumed that when you did something like RDOItems items = fProcess.Items that it would just create a pointer to that collection, rather than copy it altogether!? (can you just create a pointer?).

    I don't really understand why it being cached should matter in terms of causing this error either.

    So - overall - excellent improvement in performance but occasional error. The error is here:

    09:10:49:031: ********************************************************
    09:10:49:031: Error in IMAPISession.OpenEntry: MAPI_E_CALL_FAILED
    ulVersion: 0
    Error: The client operation failed.
    Component: Microsoft Exchange Server Information Store
    ulLowLevelError: 0
    ulContext: 1281
    09:10:49:031: ********************************************************

    In the tests I've run this has been on the .Delete(1) line in the code below. When it is .Move(mfldFolder) instead, it happens just as regularly, with the error reported last time.

    Here is the code as it stands...

    private void SortEmailUsingRedemptionLib(ref StreamWriter swLog)
    {
    	// connection object used for all transactions while we process this inbox
    	SqlConnection con = new SqlConnection(msSETTING_CONN_STR);
    	con.Open();
    
    	// setup the outlook environment and bring in the objects we'll be working with
    	Redemption.RDOSession rSession = new Redemption.RDOSession();
    	rSession.MAPIOBJECT = Application.Session.MAPIOBJECT;
    	Redemption.RDOFolder fInbox = rSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderInbox);
    	Redemption.RDOFolder fDeleted = rSession.GetDefaultFolder(Redemption.rdoDefaultFolders.olFolderDeletedItems);
    	Redemption.RDOFolder fProcess = null;
    
    	// used to save into the database against a user
    	msCurrentUser = rSession.CurrentUser.SMTPAddress;
    
    	// if msSETTING_USE_SUBFOLDER is set, use that folder INSIDE inbox.. if it can be found
    	if (msSETTING_USE_SUBFOLDER == string.Empty)
    		fProcess = fInbox;
    	else
    		fProcess = fInbox.Folders[msSETTING_USE_SUBFOLDER];
    
    	if (fProcess == null)
    	{
    		LogText(ref swLog, string.Format("Folder \"{0}\" was not found. Correct settings.xml.", msSETTING_USE_SUBFOLDER));
    		return;
    	}
    
    	// create the progress bar form and show it
    	SetupProgressBar(fProcess.Items.Count + 1);
    
    	// process the folder
    	ProcessFolder(ref con, ref fProcess, ref swLog);
    
    	// clean up
    	con.Close();
    	fInbox = null;
    	fDeleted = null;
    	rSession = null;
    	mfProgressBar.Dispose();
    }
    

    Note that in this function, the other calls to SaveToDatabase and ProcessCase are not called (verified) because they are turned off in the settings file

    private void ProcessFolder(ref SqlConnection con, ref Redemption.RDOFolder fProcess, ref StreamWriter swLog)
    {
    	
    	if (miSETTING_LOG_LEVEL >= 1)
    	{
    		LogText(ref swLog, "---------- START PROCESS INBOX ----------");
    		LogText(ref swLog);
    	}
    
    	int idx;
    	Redemption.RDOMail mi;
    	Redemption.RDOItems iFolderItems = fProcess.Items;
    
    	idx = 1; // used for progress bar
    
    	// interate through the list of items, find the ones that are mail items, and process per settings
    	for (int iLoop = fProcess.Items.Count; iLoop > 0; iLoop--)
    	{
    		try
    		{
    			TraceWrite(ref swLog, "********************************* CAST RDOMAIL ***");
    			mi = (Redemption.RDOMail)iFolderItems[iLoop];
    
    			// update the progress bar
    			mpbProgress.Value = idx;
    			mpbProgress.Refresh();
    
    			// this allows the form to catch up - specifically it is to process the cancel event from the button
    			if (miSETTING_LOG_LEVEL != -1) System.Windows.Forms.Application.DoEvents();
    
    			TraceWrite(ref swLog, string.Format("********************************* START PROCESS *** ({0}: {1}) ***", mi.SentOn.ToString("yyyy-MM-dd HH:mm"), mi.Subject));
    
    			// this must go first or it wont get saved before it gets put into a folder
    			if (mbSETTING_DOWNLOAD_INBOX && mi != null)
    				SaveToDatabase(ref con, ref mi, ref swLog);
    
    			//mi is set to null if it is deleted or moved in these procs...
    			if (mbSETTING_INVESTMENT_CASE && mi != null)
    				ProcessCases(ref con, ref mi, ref swLog, "inbox");
    
    			if (mbSETTING_FOLDER_SORT && mi != null)
    				ProcessFolderSort(ref con, ref fProcess, ref mi, ref swLog);
    
    			TraceWrite(ref swLog, "********************************* RELEASE OBJECTS ***");
    
    			if (mi != null)
    			{
    				System.Runtime.InteropServices.Marshal.ReleaseComObject(mi);
    				GC.Collect();
    			}
    
    			TraceWrite(ref swLog, "********************************* END PROCESS ***");
    
    			idx++;
    
    			// handle the cancel button which sets a flag if pressed
    			if (mbCancelSort)
    				break;
    		}
    		catch (InvalidCastException)
    		{
    			// skip these over - we don't care - we're only looking for MailItem's in the folder
    			if (miSETTING_LOG_LEVEL >= 2)
    				LogText(ref swLog, "** INVALID CAST EXCEPTION **");
    		}
    
    		swLog.Flush();
    
    		if (mbCancelSort)
    		{
    			mbCancelSort = false;
    			break;
    		}
    
    	} // while (ienumMailItems.MoveNext())
    
    	//} // while (bWorkDone)      
    
    	if (miSETTING_LOG_LEVEL >= 1)
    	{
    		LogText(ref swLog, "----------- END PROCESS INBOX -----------");
    		LogText(ref swLog);
    	}
    }
    
    private void ProcessFolderSort(ref SqlConnection con, ref Redemption.RDOFolder fInbox, ref Redemption.RDOMail mi, ref StreamWriter swLog)
    {
    	if (!mi.UnRead && (
    							 (mi.SentOn < DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0))
    							&& mi.FlagStatus == (int)EmailFlagStatus.olNoFlagIcon
    							&& mi.Importance != (int)EmailImportance.olImportanceHigh)
    						||
    							 (mi.SentOn < DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0))
    							&& mi.Importance == (int)EmailImportance.olImportanceHigh))
    							)
    	{
    		Redemption.RDOFolder mfldFolder = fInbox.Folders[mi.SenderName]; //FindFolder(mi.SenderName, fInbox.Folders);
    
    		// if you can't find a folder by the same name as the sender, create one
    		if (mfldFolder == null)
    			mfldFolder = fInbox.Folders.Add(mi.SenderName, Outlook.OlDefaultFolders.olFolderInbox);
    
    		// move the message to the folder
    		mi.CopyTo(mfldFolder);
    		mi.Delete(1);
    
    		msAudit = string.Format("FOLDER SORT: moved message \"{0}\" from \"{1}\" their folder.", mi.Subject, mi.SenderName);
    		Audit(ref con, eAuditTypes.FolderSort, msAudit);
    
    		if (miSETTING_LOG_LEVEL >= 1)
    			LogText(ref swLog, msAudit);
    
    		// do no more work on this
    		mi = null; 
    	}
    }
    

    Thanks for your help so far and for any future advice..?

    Dominic

     

    Monday, September 27, 2010 8:55 AM