'How can I identifiy a specific appointment item in outlook

We're facing a problem where we need to identify a specific appointment in outlook, even if it is a recurring appointment. But since the GlobalAppointmentID is just unique for the whole recurring series, we don't know how.

For what I know, there is just no specific unique ID for a single appointment when it is a recurring one. So we thought we gerenate our own ID by adding the the Start-Time in Ticks to the GlobalAppointmentID, but we need / want to save this new ID in the appointment object (because the StartTime might change).

My question: Is there a way to identify a specific appointment and if not, how can I save a value in a specific appointment, which is ALWAYS accessible and up-to-date for every recipients?

//Edit: To be a bit more specific: We have an outlook plugin that allows to upload files to our server, which must be mapped to the specific appointment (and to all recipients). For normal, single time meetings we just just the GlobalAppointmentID. If it is a recurring appointment it still needs to be mapped to one specific "meeting". Most of the time this meeting / appointment does not contain any exceptions in time or whatever. So I need to get this specific ID in C#.



Solution 1:[1]

This is old, but it still shows up in searches, so I will attempt to improve the answer, having just been through this in our company.

As Dmitry wrote, occurrences of a recurring appointment do not exist unless and until they become exceptions by the user changing something on the occurrence. This also means that they have no independent ID.

But every exception has an original date for when it would have occurred, had it not been changed. And for every non-exceptional occurrence that date is simply the start date of the occurrence.

Representation

You can use a combination of global appointment id and original date to obtain an ID that uniquely identifies an appointment even if it is an occurrence:

    public struct AppointmentId : IEquatable<AppointmentId>
    {
        private readonly string GlobalAppointmentId;
        private readonly LocalDate? OriginalDate;

        public AppointmentId(string globalAppointmentId)
        {
            this.GlobalAppointmentId = globalAppointmentId;
            this.OriginalDate = null;
        }

        public AppointmentId(string globalAppointmentId, LocalDate originalDate)
        {
            this.GlobalAppointmentId = globalAppointmentId;
            this.OriginalDate = originalDate;
        }

        public bool Equals(AppointmentId other)
        {
            return this.GlobalAppointmentId.Equals(other.GlobalAppointmentId, StringComparison.Ordinal) &&
                this.OriginalDate == other.OriginalDate;
        }

        public override bool Equals(object obj) =>
            obj is AppointmentId other && this.Equals(other);

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 62207;
                hash += 3 * this.GlobalAppointmentId.GetHashCode();
                hash += (5 * this.OriginalDate?.GetHashCode() ?? 0);
                return hash;
            }
        }

        public override string ToString()
        {
            if (this.GlobalAppointmentId == null) return "<None>";
            if (this.OriginalDate == null)
            {
                return $"{this.GlobalAppointmentId}";
            }
            return $"{OriginalDate.Value)} / {GlobalAppointmentId}";
        }

        public static bool operator ==(AppointmentId x, AppointmentId y) => x.Equals(y);
        public static bool operator !=(AppointmentId x, AppointmentId y) => !(x == y);
    }

Here I used LocalDate from NodaTime to represent the original date without a time component.

Accessing

The global appointment id can easily be accessed. As mentioned, original date for an occurrence that is not an exception is simply the date part of the appointment start.

Exceptions are more troublesome, when you only have the AppointmentItem and not the Exception object from RecurrencePattern, since the original date is not directly available. But you can get it via PropertyAccessor:

        public static LocalDate GetOriginalDate(this AppointmentItem appointment)
        {
            // The original date is stored in a property called ExceptionReplaceTime but this
            // property is not exposed in the OOM so we need to use PropertyAccessor
            // See: https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/pidlidexceptionreplacetime-canonical-property
            using (var props = appointment.PropertyAccessor.AsOwnedResource())
            {
                var p = props.Resource.GetProperty("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/82280040");
                DateTime exceptionReplaceTime = (DateTime)p;
                return props.Resource.UTCToLocalTime(exceptionReplaceTime).ToLocalDate();
            }
        }

Solution 2:[2]

Keep in mind that instances of recurring appointments do not physically exist (think of an appointment with no end date). If you have an exception (stored as an attachment on the master appointment), it has 2 ids - the master appointment id and the instance id, which is derived form the master id and embeds the exception date.

Look at an appointment with OutlookSpy (I am its author - click IMessage button) and see http://msdn.microsoft.com/en-us/library/ee157690(v=exchg.80).aspx for the appointment id format.

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 Søren Boisen
Solution 2 Dmitry Streblechenko