What's the best way to loop a restricted Outlook.Items collection?

Add-in Express™ Support Service
That's what is more important than anything else

What's the best way to loop a restricted Outlook.Items collection?
I dont't want a for loop doing 2147483647 iterations 
Subscribe
Inspirometer Developer




Posts: 12
Joined: 2017-10-13
One of the most important parts of my Outlook add-in involves looping through a list of calendar items and extracting certain information from them. Obviously in order to do this we need to loop through the Outlook.Items object.

Currently the code looks something like this:
foreach (var item in calendarItems)
{
    //Do some stuff
    ...
    
    if (item != null)
        Marshal.ReleaseComObject(item);
}

Since writing that I have been dissuaded from using a foreach loop with COM objects so I rewrote it to be more like this:
for (int i = 1; i <= calendarItems.Count; i++)
{
    var item = calendarItems[i];
    
    //Do some stuff
    ...
    
    if (item != null)
        Marshal.ReleaseComObject(item);
}

The problem with this is that the calendarItems.Count has a value of 2147483647 due to the setting of "IncludeRecurrences = true" on the collection earlier. Looping 2 billion times as you may have guessed takes a very, very long time so that's the for loop out the window too.

Finally I tried a while loop that looked something like this:
var item = calendarItems.GetNext();
while (item != null)
{
    //Do some stuff
    ...
    
    Marshal.ReleaseComObject(item);
    item = calendarItems.GetNext();
}

This seemed like a very clever solution and it seemed to work fine until I ran it on another machine and it spit out the following exception:

---
(Inner Exception)
Date and Time: 03/11/2017 11:57:38
Machine Name: PC
IP Address: fe80::adb7:43a1:7411:3203%8
Current User: Anon

Application Domain: <file location>
Assembly Codebase: <file location>
Assembly Full Name: InspirometerMeetingsToolADX, Version=2.0.0.1, Culture=neutral, PublicKeyToken=7740415350b3e9c2
Assembly Version: 2.0.0.1

Exception Source: InspirometerMeetingsToolADX
Exception Type: System.Runtime.InteropServices.InvalidComObjectException
Exception Message: COM object that has been separated from its underlying RCW cannot be used.
Exception Target Site: EncodeCalendar

---- Stack Trace ----
InspirometerMeetingsToolADX.Workers.CalendarWorker.EncodeCalendar(calendarItems As Items)
CalendarWorker.cs: line 0092, col 17, IL 0072 (0x48)
InspirometerMeetingsToolADX.AddinModule.AddinModule_OnSendMessage(sender As Object, e As ADXSendMessageEventArgs)
AddinModule.cs: line 0341, col 21, IL 0058 (0x3A)
AddinExpress.MSO.ADXAddinModule.DoOnSendMessage(sender As Object, msg As Message)
InspirometerMeetingsToolADX.DLL: N 0035 (0x23) IL



(Outer Exception)
Date and Time: 03/11/2017 11:57:38
Machine Name: PC
IP Address: fe80::adb7:43a1:7411:3203%8
Current User: Anon

Application Domain: <file location>
Assembly Codebase: <file location>
Assembly Full Name: AddinExpress.MSO.2005, Version=8.7.4430.0, Culture=neutral, PublicKeyToken=4416dd98f0861965
Assembly Version: 8.7.4430.0

Exception Source:
Exception Type: AddinExpress.MSO.ADXExternalException
Exception Message: An error has occurred in the code of the add-in.

---- Stack Trace ----

So clearly none of these solutions is working quite how I'd like it to. Out of the solutions I've presented the original foreach loop worked the best but I'm still worried about bad usage of COM objects and incorrect marshalling. Any pointers?
Posted 03 Nov, 2017 08:38:09 Top
Andrei Smolin


Add-in Express team


Posts: 14136
Joined: 2006-05-11
Hello,

Inspirometer Developer writes:
InspirometerMeetingsToolADX.Workers.CalendarWorker.EncodeCalendar(calendarItems As Items) CalendarWorker.cs: line 0092, col 17, IL 0072 (0x48)


The code fragment you provide (the "while" version) can't be responsible for this exception.

Regards from Belarus (GMT+3),

Andrei Smolin
Add-in Express Team Leader
Posted 03 Nov, 2017 09:28:30 Top
Inspirometer Developer




Posts: 12
Joined: 2017-10-13
It's not? Okay I'll go back to that then. Any idea what might be causing the error then? Line 92 is only a check that it's an AppointmentItem.

var item = calendarItems.GetNext();
while (item != null)
{
    if (item is Outlook.AppointmentItem) // <- line 92
    {
        //Do some stuff
        ...
    }
    
    Marshal.ReleaseComObject(item);
    item = calendarItems.GetNext();
}
Posted 03 Nov, 2017 09:50:47 Top
Andrei Smolin


Add-in Express team


Posts: 14136
Joined: 2006-05-11
Try setting item to null:

Marshal.ReleaseComObject(item); item = null;

Are you sure your code doesn't release item within the if block? Maybe you cast item to AppointmentItem, save the result to a variable, and release that variable after use? Aha, I see a finally block in your original code; it releases appointment - releasing this variable also releases item as they point to the same COM object.

Also, maybe, you have some extra code between declaring item and line 92?

Regards from Belarus (GMT+3),

Andrei Smolin
Add-in Express Team Leader
Posted 03 Nov, 2017 10:05:10 Top
Inspirometer Developer




Posts: 12
Joined: 2017-10-13
Stress levels are reaching an all time high here at Inspirometer. Since we last spoke I fixed the issues you noted in your last post on this topic and for a while things seemed good. It wasn't until a couple of days ago I realised that in using the while loop the code wasn't collecting any data on at least the first entry in the list (maybe the last as well but that was less clear). I amended the loop to compensate for this by changing
var item = calendarItems.GetNext();
to
var item = calendarItems.GetFirst();

Running this code on my machine, everything seemed good! It ran and it got all the items I was expecting, hooray! I published the code to get it tested by my colleagues and this is where things start to go bad again. For some reason running GetFirst() and then attempting to get any properties of the object returned was causing Outlook to hang and crash (bizarre, I know).

For now I've gone back to using a foreach loop as it both runs without crashing and collects all the data. I only changed from this loop method at your suggestion so I'm wondering if you can definitively tell me WHAT the best way to loop an Outlook.Items collection is and WHY that is the case.

Thanks xx
Posted 20 Nov, 2017 10:06:41 Top
Andrei Smolin


Add-in Express team


Posts: 14136
Joined: 2006-05-11
The best way is a way that releases every COM object created in your code.

The issue may not relate to your code at all.

Inspirometer Developer writes:
For some reason running GetFirst() and then attempting to get any properties of the object returned was causing Outlook to hang and crash (bizarre, I know).


Do you attribute this issue to any specific item, item type, folder, message store, or message store type? Say, to IMAP Folders?

The crash may be caused by an item which is broken in some way; we saw such issues. If you use PST, consider repairing it using ScanPst.exe; on my machine it is located in C:\Program Files (x86)\Microsoft Office\root\Office16\. If this is an Exchange mailbox, consider deleting the .OST file as Microsoft recommends at https://support.microsoft.com/en-us/help/983036/the-scanost-exe-tool-is-not-available-starting-with-outlook-2010.

Does turning off *all* other COM addins fixes the issue?

Regards from Belarus (GMT+3),

Andrei Smolin
Add-in Express Team Leader
Posted 21 Nov, 2017 08:47:20 Top