0
votes

I know how to retrieve events in the current user's outlook calendar, the following code works to remove items matching a certain pattern for instance:

private void RemoveAppointments()
        {
            Outlook.Application outlook = new Outlook.Application();
            Outlook.MAPIFolder calendarFolder = outlook.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
            Outlook.Items outlookCalendarItems = calendarFolder.Items;
            for (int i = outlookCalendarItems.Count; i > 0; i--)
            {
                if (outlookCalendarItems[i].Subject.Contains("On Call: Regions:") && outlookCalendarItems[i].Start.Year == _year)
                {
                    outlookCalendarItems[i].Delete();
                }
            }
        }

However, I now need to be able to read calendar events from all users within an Outlook Team (assume sharing permissions have been set correctly). Ideally I need to be able to iterate per user but if I just get a collection of all events and can then query that by user that would suffice too.

Any ideas where I can start?


Note: this is how the team is represented withing the Calendar pane of Outlook. Sensitive details redacted.

Calendar Section Bar

2

2 Answers

1
votes

Instead of using Namespace.GetDefaultFolder, use Namespace.GetSharedDefaultFolder, passing a Recipient object returned from Namespace.CreateRecipient.

Also keep in mind that looping through all items in a folder is a horrible idea, especially if you open an online folder not cached in a local OST file. Use Items.Find/FindNext or Items.Restrict instead.

1
votes

Thanks to @Dmitry for his answer which helped me solve my issue. However, to maximise the utility of this question for future readers I thought I'd expand on it.


Assume that:

using Microsoft.Office.Interop.Outlook;

and that the COM assembly Microsoft Outlook 16.0 Object Library is referenced.


The first step is to create an Outlook.Application object that serves as an interface to Outlook's functions (you can think of it as an internal instance of the full Outlook program):

Application app = new Application();

From this I then pull all of the users from the distribution list in the Global Address List that is associated with the team. This is done by creating a Recipient object from the Session property of the Application instance.

Recipient distList = app.Session.CreateRecipient(yourDistList);

From here we can pull all of the real names and usernames vie the AdressEntry.Members property of our Recipient. To pull these both into an anonymous tuple of (string,string) we can use this LINQ query, if you don't like that you can just iterate like normal:

List<(string,string)> usersData = distList.AddressEntry.Members.Cast<AddressEntry>().Select(entry => (entry.Name,entry.Address)).ToList();

Now, given a specific username, as long as the calendar has been shared with the curernt user you can access it using the GetSharedDefaultFolder() method of the Session:

MAPIFolder sharedCalendar = _app.Session.GetSharedDefaultFolder(teamMember, OlDefaultFolders.olFolderCalendar);

At this point I found it useful to do some filtering to try and avoid the most common COMExceptions however, there are plenty I can't seem to determine the cause of so I just catch (COMException) and bin them off. Not good practice I know but it didn't seem to interfere with me accessing calendars that I had permissions for. Some (very) basic filtering:

if (sharedCalendar.DefaultMessageClass != "IPM.Appointment" || teamMember.DisplayType != 0)
{
    return null; //Calendar not shared.
}

Now we have to build a Filter string utilising the Microsoft Outlook format, this can be done using the following statement (where from and to are both DateTime objects):

string sFilter = $"[End] > '{from:g}' AND [Start] < '{to:g}' AND [Recurring] = 'No'";

We filter out recurring events as otherwise the Start and End dates can be wildly outside the range with no internal occurrences. For my purposes, I didn't need recurring events anyway, however, if you do you will have to deal with this separately.

Finally we can now collect the events we need utilising the Items.Restrict() method of MAPIFolder:

Items results = sharedCalendar.Items.Restrict(sFilter);

This returns an Items interface to all of the items falling within our filter.

Finally, we can iterate over each item (I iterate in reverse order because I copied code from an old application of mine that deleted events but it shouldn't matter in this context). You may have to cast the object to AppointmentItem depending on whether or not this can be inferred by the compiler.

List<AppData> appointments = new List<AppData>();
for (int i = results.Count; i > 0; i--)
{
    appointments.Add(new AppData(results[i], username));
}

I store each event as an AppData struct keeping only the data I need:

public struct AppData
{
    public string Subject { get; }
    public DateTime From { get; }      
    public DateTime To { get; }       
    public string Location { get; }      
    public string Categories { get; }      
    public string Username { get; }
    public AppData(AppointmentItem appItem, string username)
    {
        Subject = appItem.Subject;
        From = appItem.Start;
        To = appItem.End;
        Location = appItem.Location;
        Categories = appItem.Categories;
        Username = username;
    }
}

All of this results in a class that looks as such:

public class OutlookCommunicator : IDisposable
{
    private readonly Application _app;

    public OutlookCommunicator()
    {
        _app = new Application();
    }

    /// <summary>
    /// Username of the distribution list according to the GAL.
    /// </summary>
    private const string DistList = "redacted";

    /// <summary>
    /// Fetches a list of all usernames and names within the DistList.
    /// </summary>
    /// <returns>List&lt;string&gt; containing all usernames.</returns>
    public List<(string,string)> GetUsers()
    {
            Recipient warEngineering = _app.Session.CreateRecipient(DistList);
            List<(string,string)> usernames = warEngineering.AddressEntry.Members.Cast<AddressEntry>().Select(entry => (entry.Name,entry.Address)).ToList();
            return usernames;

    }



    /// <summary>
    /// Fetches all calendar events for a user falling within the provided range.
    /// </summary>
    /// <param name="from">Start search date.</param>
    /// <param name="to">End search dat.</param>
    /// <param name="username">User's calendar to search.</param>
    /// <returns></returns>
    public List<AppData> GetEventsInRange(DateTime from, DateTime to, string username)
    {
        List<AppData> appointments = new List<AppData>();
        try
        {

            Recipient teamMember = _app.Session.CreateRecipient(username);
            MAPIFolder sharedCalendar = _app.Session.GetSharedDefaultFolder(teamMember, OlDefaultFolders.olFolderCalendar);
            if (sharedCalendar.DefaultMessageClass != "IPM.Appointment" || teamMember.DisplayType != 0)
            {
                return null; //Calendar not shared.
            }

            string sFilter = $"[End] > '{from:g}' AND [Start] < '{to:g}' AND [Recurring] = 'No'";
            Items results = sharedCalendar.Items.Restrict(sFilter);
            for (int i = results.Count; i > 0; i--)
            {
                appointments.Add(new AppData(results[i], username));
            }

            return appointments;
        }
        catch (COMException)
        {
            return null;
        }
    }

    public void Dispose()
    {
        _app?.Quit();
    }