private void GetMeetingHeaders(MeetingItem item, out string smallHead, string[] exclusions) { StringBuilder header = new StringBuilder(); if (!exclusions.Contains("title", StringComparer.OrdinalIgnoreCase)) { header.Append("<p>"); AppendTitle(item, header, true); } AppendPplAndTime(item, header, exclusions); var appointment = item.GetAssociatedAppointment(false); if (appointment != null) { string when = appointment.Start.Date.Equals(appointment.End.Date) ? string.Format("from {0:h:mm tt} to {1:h:mm tt}, {2:dddd, MMMM d yyyy}", appointment.Start, appointment.End, appointment.Start) : string.Format("from {0:h:mm tt dddd, MMMM d yyyy} to {1:h:mm tt dddd, MMMM d yyyy}", appointment.Start, appointment.End); header.AppendFormat("<br/>{0}", when); if (!string.IsNullOrWhiteSpace(appointment.Location)) header.AppendFormat("<br/>at {0}", appointment.Location); } AddAttachments(header, item.Attachments); if (!exclusions.Contains("title", StringComparer.OrdinalIgnoreCase)) header.Append("</p>"); smallHead = header.ToString(); }
/// <summary> /// Applies the appropiate rule to the meeting parameter /// </summary> /// <param name="meetingItem">The meeting that needs processing</param> /// <param name="rule">the associated decline rule for the folder of this meetingItem</param> private static void ProcessRule(MeetingItem meetingItem, DeclineRuleSetting rule) { // if it's a Cancelation, delete it from calendar if (meetingItem.Class == OlObjectClass.olMeetingCancellation) { if (meetingItem.GetAssociatedAppointment(false) != null) { meetingItem.GetAssociatedAppointment(false).Delete(); return; } meetingItem.Delete(); return; // if deleted by user/app, delete the whole message } // get associated appointment AppointmentItem appointment = meetingItem.GetAssociatedAppointment(false); string globalAppointmentID = appointment.GlobalAppointmentID; // optional, send notification back to sender appointment.ResponseRequested &= rule.SendResponse; // set decline to the meeting MeetingItem responseMeeting = appointment.Respond(rule.Response, true); // https://msdn.microsoft.com/en-us/VBA/Outlook-VBA/articles/appointmentitem-respond-method-outlook // says that Respond() will return a new meeting object for Tentative response // optional, add a meesage to the Body if (!String.IsNullOrEmpty(rule.Message)) { (responseMeeting ?? meetingItem).Body = rule.Message; } // send decline //if(rule.Response == OlMeetingResponse.olMeetingDeclined) (responseMeeting ?? meetingItem).Send(); // and delete the appointment if tentative if (rule.Response == OlMeetingResponse.olMeetingTentative) { appointment.Delete(); } // after Sending the response, sometimes the appointment doesn't get deleted from calendar, // but appointmnent could become and invalid object, so we need to search for it and delete it AppointmentItem newAppointment = (AppointmentItem)Globals.AddIn.Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderCalendar).Items .Find("@SQL=\"http://schemas.microsoft.com/mapi/id/{6ED8DA90-450B-101B-98DA-00AA003F1305}/00030102\" = '" + globalAppointmentID + "' "); if (newAppointment != null) { newAppointment.Delete(); } }
private void GetMeetingHeaders(MeetingItem item, out string smallHead, string[] exclusions) { StringBuilder header = new StringBuilder(); if (!exclusions.Contains("title", StringComparer.OrdinalIgnoreCase)) { header.Append("<p>"); AppendTitle(item, header, true); } AppendPplAndTime(item, header, exclusions); var appointment = item.GetAssociatedAppointment(false); if (appointment != null) { string when = appointment.Start.Date.Equals(appointment.End.Date) ? string.Format("from {0:h:mm tt} to {1:h:mm tt}, {2:dddd, MMMM d yyyy}", appointment.Start, appointment.End, appointment.Start) : string.Format("from {0:h:mm tt dddd, MMMM d yyyy} to {1:h:mm tt dddd, MMMM d yyyy}", appointment.Start, appointment.End); header.AppendFormat("<br/>{0}", when); if (!string.IsNullOrWhiteSpace(appointment.Location)) { header.AppendFormat("<br/>at {0}", appointment.Location); } } AddAttachments(header, item.Attachments); if (!exclusions.Contains("title", StringComparer.OrdinalIgnoreCase)) { header.Append("</p>"); } smallHead = header.ToString(); }
private SyncResult synchronize() { Console console = Forms.Main.Instance.Console; console.Update("Finding Calendar Entries", Console.Markup.mag_right, newLine: false); List <AppointmentItem> outlookEntries = null; List <Event> googleEntries = null; try { #region Read Outlook items console.Update("Scanning Outlook calendar..."); outlookEntries = OutlookOgcs.Calendar.Instance.GetCalendarEntriesInRange(false); console.Update(outlookEntries.Count + " Outlook calendar entries found.", Console.Markup.sectionEnd, newLine: false); if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion #region Read Google items console.Update("Scanning Google calendar..."); try { googleEntries = GoogleOgcs.Calendar.Instance.GetCalendarEntriesInRange(); } catch (AggregateException agex) { OGCSexception.AnalyseAggregate(agex); } catch (Google.Apis.Auth.OAuth2.Responses.TokenResponseException ex) { OGCSexception.AnalyseTokenResponse(ex, false); return(SyncResult.Fail); } catch (System.Net.Http.HttpRequestException ex) { OGCSexception.Analyse(ex); ex.Data.Add("OGCS", "ERROR: Unable to connect to the Google calendar. Please try again."); throw ex; } catch (System.Exception ex) { OGCSexception.Analyse(ex); ex.Data.Add("OGCS", "ERROR: Unable to connect to the Google calendar."); if (OGCSexception.GetErrorCode(ex) == "0x8013153B") //ex.Message == "A task was canceled." - likely timed out. { ex.Data["OGCS"] += " Please try again."; } throw ex; } Recurrence.Instance.SeparateGoogleExceptions(googleEntries); if (Recurrence.Instance.GoogleExceptions != null && Recurrence.Instance.GoogleExceptions.Count > 0) { console.Update(googleEntries.Count + " Google calendar entries found."); console.Update(Recurrence.Instance.GoogleExceptions.Count + " are exceptions to recurring events.", Console.Markup.sectionEnd, newLine: false); } else { console.Update(googleEntries.Count + " Google calendar entries found.", Console.Markup.sectionEnd, newLine: false); } if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion #region Normalise recurring items in sync window console.Update("Total inc. recurring items spanning sync date range..."); //Outlook returns recurring items that span the sync date range, Google doesn't //So check for master Outlook items occurring before sync date range, and retrieve Google equivalent for (int o = outlookEntries.Count - 1; o >= 0; o--) { log.Fine("Processing " + (o + 1) + "/" + outlookEntries.Count); AppointmentItem ai = null; try { if (outlookEntries[o] is AppointmentItem) { ai = outlookEntries[o]; } else if (outlookEntries[o] is MeetingItem) { log.Info("Calendar object appears to be a MeetingItem, so retrieving associated AppointmentItem."); MeetingItem mi = outlookEntries[o] as MeetingItem; outlookEntries[o] = mi.GetAssociatedAppointment(false); ai = outlookEntries[o]; } else { log.Warn("Unknown calendar object type - cannot sync it."); skipCorruptedItem(ref outlookEntries, outlookEntries[o], "Unknown object type."); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); continue; } } catch (System.Exception ex) { log.Warn("Encountered error casting calendar object to AppointmentItem - cannot sync it."); log.Debug(ex.Message); skipCorruptedItem(ref outlookEntries, outlookEntries[o], ex.Message); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); continue; } //Now let's check there's a start/end date - sometimes it can be missing, even though this shouldn't be possible!! String entryID; try { entryID = outlookEntries[o].EntryID; DateTime checkDates = ai.Start; checkDates = ai.End; } catch (System.Exception ex) { log.Warn("Calendar item does not have a proper date range - cannot sync it."); log.Debug(ex.Message); skipCorruptedItem(ref outlookEntries, outlookEntries[o], ex.Message); outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); continue; } if (ai.IsRecurring && ai.Start.Date < Settings.Instance.SyncStart && ai.End.Date < Settings.Instance.SyncStart) { //We won't bother getting Google master event if appointment is yearly reoccurring in a month outside of sync range //Otherwise, every sync, the master event will have to be retrieved, compared, concluded nothing's changed (probably) = waste of API calls RecurrencePattern oPattern = ai.GetRecurrencePattern(); try { if (oPattern.RecurrenceType.ToString().Contains("Year")) { log.Fine("It's an annual event."); Boolean monthInSyncRange = false; DateTime monthMarker = Settings.Instance.SyncStart; while (Convert.ToInt32(monthMarker.ToString("yyyyMM")) <= Convert.ToInt32(Settings.Instance.SyncEnd.ToString("yyyyMM")) && !monthInSyncRange) { if (monthMarker.Month == ai.Start.Month) { monthInSyncRange = true; } monthMarker = monthMarker.AddMonths(1); } log.Fine("Found it to be " + (monthInSyncRange ? "inside" : "outside") + " sync range."); if (!monthInSyncRange) { outlookEntries.Remove(ai); log.Fine("Removed."); continue; } } Event masterEv = Recurrence.Instance.GetGoogleMasterEvent(ai); if (masterEv != null && masterEv.Status != "cancelled") { Event cachedEv = googleEntries.Find(x => x.Id == masterEv.Id); if (cachedEv == null) { googleEntries.Add(masterEv); } else { if (masterEv.Updated > cachedEv.Updated) { log.Debug("Refreshing cache for this Event."); googleEntries.Remove(cachedEv); googleEntries.Add(masterEv); } } } } catch (System.Exception ex) { console.Update("Failed to retrieve master for Google recurring event outside of sync range.", Console.Markup.error); throw ex; } finally { oPattern = (RecurrencePattern)OutlookOgcs.Calendar.ReleaseObject(oPattern); } } //Completely dereference object and retrieve afresh (due to GetRecurrencePattern earlier) ai = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(ai); OutlookOgcs.Calendar.Instance.IOutlook.GetAppointmentByID(entryID, out ai); outlookEntries[o] = ai; } console.Update("Outlook " + outlookEntries.Count + ", Google " + googleEntries.Count); GoogleOgcs.Calendar.ExportToCSV("Outputting all Events.", "google_events.csv", googleEntries); OutlookOgcs.Calendar.ExportToCSV("Outputting all Appointments.", "outlook_appointments.csv", outlookEntries); if (CancellationPending) { return(SyncResult.UserCancelled); } #endregion Boolean success = true; String bubbleText = ""; if (Settings.Instance.SyncDirection != Direction.GoogleToOutlook) { success = outlookToGoogle(outlookEntries, googleEntries, ref bubbleText); if (CancellationPending) { return(SyncResult.UserCancelled); } } if (!success) { return(SyncResult.Fail); } if (Settings.Instance.SyncDirection != Direction.OutlookToGoogle) { if (bubbleText != "") { bubbleText += "\r\n"; } success = googleToOutlook(googleEntries, outlookEntries, ref bubbleText); if (CancellationPending) { return(SyncResult.UserCancelled); } } if (bubbleText != "") { Forms.Main.Instance.NotificationTray.ShowBubbleInfo(bubbleText); } return(SyncResult.OK); } finally { for (int o = outlookEntries.Count() - 1; o >= 0; o--) { outlookEntries[o] = (AppointmentItem)OutlookOgcs.Calendar.ReleaseObject(outlookEntries[o]); outlookEntries.RemoveAt(o); } } }
private void Items_ItemAdd(object item) { MeetingItem meetingItem = item as MeetingItem; if (meetingItem != null) { var apptItem = meetingItem.GetAssociatedAppointment(false); if (apptItem != null) { var organizerAddressEntry = apptItem.GetOrganizer(); var currentUserAddressEntry = apptItem.Session?.CurrentUser?.AddressEntry; CreateEmailItem( subjectEmail: $"Oofer debug: {meetingItem.Subject}", toEmail: currentUserAddressEntry.Address, bodyEmail: $"This meeting item was received:\n\n{meetingItem.Subject}"); var alias = string.Empty; if (organizerAddressEntry.Type == "EX") { var exchangeUser = organizerAddressEntry.GetExchangeUser(); alias = exchangeUser.Alias; } var organizerFirstName = organizerAddressEntry.Name.Split(' ').FirstOrDefault(); if (_matches.Any(x => apptItem.Subject.ToUpperInvariant().Contains(x)) && organizerAddressEntry != currentUserAddressEntry) { var needsAlias = !string.IsNullOrWhiteSpace(alias) && !apptItem.Subject.Contains(organizerFirstName) && !apptItem.Subject.Contains(alias); if (apptItem.ReminderSet || apptItem.BusyStatus != OlBusyStatus.olFree || apptItem.ResponseRequested == true || needsAlias) { var subjectText = apptItem.Subject; var busyStatusText = GetBusyStatusString(apptItem.BusyStatus); var reminderSetText = apptItem.ReminderSet ? "Reminder was set" : "Reminder was not set"; var responseRequestedText = apptItem.ResponseRequested ? "Response was requested" : "Response was not requested"; apptItem.BusyStatus = OlBusyStatus.olFree; apptItem.ReminderSet = false; apptItem.ResponseRequested = false; if (needsAlias) { apptItem.Subject = $"{alias}: {subjectText}"; } // We will not send the response, but want to accept the appointment on our side. See linked // docs to send the response, if desired. apptItem.Respond(OlMeetingResponse.olMeetingAccepted, true, Type.Missing); apptItem.UnRead = false; apptItem.Save(); meetingItem.UnRead = false; meetingItem.Save(); CreateEmailItem( subjectEmail: $"Cleaned OOF/WFH: {subjectText}", toEmail: currentUserAddressEntry.Address, bodyEmail: $"This appointment was cleaned:\n\n{apptItem.Subject}\n{busyStatusText}\n{reminderSetText}\n{responseRequestedText}"); } } } } }