Получение элементов календаря (Outlook API, WebDAV) со странным поведением

79

Мы пишем плагин для MS Outlook. Чтобы удовлетворить нашу бизнес-логику, он должен проверять все встречи между некоторыми датами. У нас возникло несколько проблем с получением всех элементов из календарей. Мы попробовали два варианта:

  1. Outlook API. Мы используем стандартную логику, которая описана в MSDN - элементы сортировки по [Start], набор IncludeRecurrencesдля Trueи запустить Find \ Ограничить запрос через элементы календаря , как здесь . Он отлично работает в нашей тестовой среде. Однако в среде наших клиентов: для повторяющихся встреч даты начала и окончания устанавливаются на соответствующие даты «основной встречи». Например, в календаре некоторых комнат у нас есть еженедельная встреча, созданная в январе, и если мы попытаемся найти все элементы в августе, мы получим среди прочих четыре элемента этой повторяющейся встречи, но их даты начала и окончания установлены на январь. . Но Outlook отображает правильные даты в том же календаре ...

  2. Очень плохо, но у нас все еще есть WebDAV! Мы пишем простое тестовое приложение и пытаемся запросить все элементы из календаря с помощью WebDAV. Конечно, мы не изобретали велосипед, а просто вставили код из документации . Предыдущая проблема решена, но возникает следующая: он не возвращает повторяющиеся элементы, которые были созданы более шести месяцев назад. Понятия не имею - нет параметров, ограничивающих «старые» предметы!

Что случилось? Мы упускаем что-то важное?

Технические детали: Exchange 2003, Outlook 2003-2010. Честно говоря, первая ошибка исчезает, если мы включаем Cached Exchange Mode, но мы не можем этого сделать.

var nameSpace = application.GetNamespace("MAPI");
var recepient = nameSpace.CreateRecipient(roomEMail);
recepient.Resolve();
var calendar = nameSpace.GetSharedDefaultFolder(recepient, OlDefaultFolders.olFolderCalendar);
var filter = string.Format("[Start]<'{1}' AND [End]>'{0}'",
  dateFrom.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture), dateTo.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture)
);
var allItems = calendar.Items;
allItems.Sort("[Start]");
allItems.IncludeRecurrences = true;
var _item = allItems.Find(filter);
while (_item != null) {
  AppointmentItem item = _item as AppointmentItem;
  if (item != null) {
    if (item.Subject != "some const")
      && (item.ResponseStatus != OlResponseStatus.olResponseDeclined)
      && (item.MeetingStatus != OlMeetingStatus.olMeetingReceivedAndCanceled 
      && item.MeetingStatus != OlMeetingStatus.olMeetingCanceled))
    {
      /* Here we copy item to our internal class.
       * We need: Subject, Start, End, Organizer, Recipients, MeetingStatus,
       * AllDayEvent, IsRecurring, RecurrentState, ResponseStatus,
       * GlobalAppointmentID */
    }
  }
  _item = allItems.FindNext();
}

ОБНОВЛЕНИЕ 1:

Дополнительное исследование с использованием OutlookSpy показывает, что проблема не в нашем коде - даты начала и окончания неверны внутри API, когда режим кэширования Exchange отключен. Но разработчики Outlook знали об этом и каким-то образом отображали правильные даты в календарях! Кто-нибудь знает как?

ОБНОВЛЕНИЕ 2:

Ответ от инженера по расширению поддержки Outlook:

На основании этого я могу подтвердить, что это проблема нашего продукта.

Болик
источник
3
1. Какой у вас код? 2. Не используйте WebDAV; он устарел.
Дмитрий Стреблеченко
Выглядит прекрасно ... Какой у вас код доступа к встрече? Вы когда-нибудь обращались к AppointmentItem.Parent (который дает вам главную встречу для экземпляра повторяющейся активности)?
Дмитрий Стреблеченко
Я обновил код выше. Нет, мы не используем AppointmentItem.Parent. В любом случае, прежде чем мы получим доступ к датам начала и окончания, мы обращаемся только к свойствам Subject, ResponseStatus и MeetingStatus для AppointmentItem.
Bolick
Во-первых, Outlook не использует OOM для отображения содержимого папки календаря, во-вторых, почему вы думаете, что даты начала и окончания неверны? Что именно не так?
Дмитрий Стреблеченко
Точно: OutlookSpy показывает нам несколько встреч с одним и тем же StartTime, в нашем случае = 11.01.2012, и это определенно основная встреча повторяющейся еженедельной активности (тот же органайзер, та же тема и т. Д.). Но в календаре мы видим правильную картину - по одному пункту в неделю. Я был бы очень признателен, если бы Вы могли объяснить, как работает Outlook, какие технологии он использует для отображения календаря, какие-либо идеи, почему мы получаем неправильный результат в OOM и как исправить эти ошибки?
Bolick

Ответы:

1

Возможная причина:

  • Сортировка после установки IncludeRecurrences.

Вот мой код модуля PowerShell, который извлекает элементы Outlook между двумя датами.

И небольшой апплет для проверки изменений и отправки электронного письма с обновлениями повестки дня, который пригодится, когда у вас нет мобильного доступа к Exchange.

Путь: Documents \ WindowsPowerShell \ Modules \ Outlook \ expcal.ps1

Function Get-OutlookCalendar
{
  <#
   .Synopsis
    This function returns appointment items from default Outlook profile
   .Description
    This function returns appointment items from the default Outlook profile. It uses the Outlook interop assembly to use the olFolderCalendar enumeration.
    It creates a custom object consisting of Subject, Start, Duration, Location
    for each appointment item.
   .Example
    Get-OutlookCalendar |
    where-object { $_.start -gt [datetime]"5/10/2011" -AND $_.start -lt `
    [datetime]"5/17/2011" } | sort-object Duration
    Displays subject, start, duration and location for all appointments that
    occur between 5/10/11 and 5/17/11 and sorts by duration of the appointment.
    The sort is the shortest appointment on top.
   .Notes
    NAME:  Get-OutlookCalendar
    AUTHOR: ed wilson, msft
    LASTEDIT: 05/10/2011 08:36:42
    KEYWORDS: Microsoft Outlook, Office
    HSG: HSG-05-24-2011
   .Link
     Http://www.ScriptingGuys.com/blog
 #Requires -Version 2.0
 #>

 echo Starting... Initialize variables

 Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
 $olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
 $olCalendarDetail = "Microsoft.Office.Interop.Outlook.OlCalendarDetail" -as [type]

 echo ... Getting ref to Outlook and Calendar ...

 $outlook = new-object -comobject outlook.application
 $namespace = $outlook.GetNameSpace("MAPI")
 $folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)

 echo ... Calculating dates ...

 $now = Get-Date -Hour 0 -Minute 00 -Second 00

 echo From $a To $b

 echo ... Getting appointments ...

 $Appointments = $folder.Items
 $Appointments.IncludeRecurrences = $true
 $Appointments.Sort("[Start]")

 echo ... Setting file names ...

 $oldfile = "$env:USERPROFILE\outlook-calendar.bak"
 echo oldfile: $oldfile
 $newfile = "$env:USERPROFILE\outlook-calendar.txt"
 echo newfile: $newfile
 $calfile = "$env:USERPROFILE\outlook-calendar.ics"
 echo calfile: $calfile

 echo ... Exporting calendar to $calfile ...

 $calendarSharing = $folder.GetCalendarExporter()
 $calendarSharing.CalendarDetail = $olCalendarDetail::olFullDetails
 $calendarSharing.IncludeWholeCalendar = $false
 $calendarSharing.IncludeAttachments = $false
 $calendarSharing.IncludePrivateDetails = $true
 $calendarSharing.RestrictToWorkingHours = $false
 $calendarSharing.StartDate = $now.AddDays(-30)
 $calendarSharing.EndDate = $now.AddDays(30)
 echo $calendarSharing
 $calendarSharing.SaveAsICal($calfile)

 echo ... Backing up $newfile into $oldfile ...

 if (!(Test-Path $newfile)) {
  echo "" |Out-File $newfile
 }

 # Backup old export into $oldfile
 if (Test-Path $oldfile) {
  echo "Deleting old backup file $oldfile"
  del $oldfile 
 }
 echo " ... moving $newfile into $oldfile ... "
 move $newfile $oldfile

 echo "... Generating text report to file $newfile ..."

 $Appointments | Where-object { $_.start -gt $now -AND $_.start -lt $now.AddDays(+7) } | 
  Select-Object -Property Subject, Start, Duration, Location, IsRecurring, RecurrenceState  |
  Sort-object Start |
  Out-File $newfile -Width 100

 echo "... Comparing with previous export for changes ..."

 $oldsize = (Get-Item $oldfile).length
 $newsize = (Get-Item $newfile).length

 if ($oldsize -ne $newsize ) {
  echo "!!! Detected calendar change. Sending email..."
  $mail = $outlook.CreateItem(0)

  #2 = high importance email header
  $mail.importance = 2

  $mail.subject = $env:computername + “ Outlook Calendar“

  $mail.Attachments.Add($newfile)
  $mail.Attachments.Add($calfile)
  $text = Get-Content $newfile | Out-String
  $mail.body = “See attached file...“ + $text

  #for multiple email, use semi-colon ; to separate
  $mail.To = “your-email@your-mail-domain.com“

  $mail.Send()

 }
 else {
  echo "No changes detected in Calendar!"
 }


} #end function Get-OutlookCalendar

Function Get-OutlookCalendarTest
{
 echo starting...
 Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
 $olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
 $outlook = new-object -comobject outlook.application
 $namespace = $outlook.GetNameSpace("MAPI")
 $folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)

 $a = Get-Date -Hour 0 -Minute 00 -Second 00
 $b = (Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(7)
 echo From $a To $b

 $Appointments = $folder.Items
 $Appointments.IncludeRecurrences = $true
 $Appointments.Sort("[Start]")

 $Appointments | Where-object { $_.start -gt $a -AND $_.start -lt $b } | Select-Object -Property IsRecurring, RecurrenceState, Subject, Start, Location

} #end function Get-OutlookCalendarTest

Это код для вызова функции PowerShell в модуле:

Путь: Documents \ WindowsPowerShell \ mono.ps1

Import-Module -Name Outlook\expcal.psm1 -Force

$i=0

#infinite loop for calling connect function   
while(1)
{
   $i = $i +1
   Write-Output "Running task Get-OutlookCalendar ($i)"
   Get-OutlookCalendar

   start-sleep -seconds 300

}

Чтобы запустить сценарий PowerShell, используйте powershell.exe. Чтобы запустить это при запуске, ярлык в "% APPDATA% \ Microsoft \ Windows \ Start Menu \ Programs \ Startup \":

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "C:\Users\%USERNAME%\Documents\WindowsPowerShell\mono.ps1"
Хосе Мануэль Гомес Альварес
источник