Pobierz elementy kalendarza (Outlook API, WebDAV) wykazujące dziwne zachowanie

79

Piszemy wtyczkę do MS Outlooka. Aby spełnić naszą logikę biznesową, powinien sprawdzać wszystkie spotkania między niektórymi datami. Mamy kilka problemów z pobieraniem wszystkich pozycji z kalendarzy. Wypróbowaliśmy dwie opcje:

  1. Outlook API. Używamy standardową logikę, która jest opisana w MSDN - pozycje Sortuj według [Start], zestaw IncludeRecurrencesdo Truei uruchom Find \ Ograniczanie zapytania nad kalendarzem przedmioty jak tutaj . Działa dobrze w naszym środowisku testowym. Jednak w środowisku naszego klienta: w przypadku spotkań cyklicznych daty rozpoczęcia i zakończenia są ustawione na odpowiadające im daty „spotkania głównego”. Na przykład w kalendarzu jakiegoś pokoju mamy spotkanie tygodniowe, które zostało utworzone w styczniu i jeśli spróbujemy znaleźć wszystkie pozycje w sierpniu to otrzymamy m.in. cztery pozycje tego spotkania cyklicznego, ale ich data rozpoczęcia i zakończenia to styczeń . Ale Outlook wyświetla prawidłowe daty w tym samym kalendarzu ...

  2. Bardzo źle, ale wciąż mamy WebDAV! Piszemy prostą aplikację testową i próbujemy przeszukiwać wszystkie pozycje z kalendarza za pomocą WebDAV. Oczywiście nie wymyśliliśmy na nowo koła i po prostu wkleiliśmy kod z dokumentacji . Poprzedni problem został rozwiązany, ale pojawia się następny: Nie zwraca powtarzających się elementów, które zostały utworzone ponad około sześć miesięcy temu. Nie mam pojęcia - nie ma parametrów ograniczających „stare” przedmioty!

Co jest nie tak? Czy brakuje nam czegoś ważnego?

Szczegóły techniczne: Exchange 2003, Outlook 2003-2010. Szczerze mówiąc, pierwszy błąd znika, jeśli włączymy tryb buforowanej wymiany, ale nie możemy tego zrobić.

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();
}

AKTUALIZACJA 1:

Dodatkowe badania przy użyciu OutlookSpy pokazują, że problem nie występuje w naszym kodzie - daty rozpoczęcia i zakończenia są nieprawidłowe w interfejsie API, gdy tryb buforowanej wymiany jest wyłączony. Ale programiści Outlooka byli tego świadomi i w jakiś sposób wyświetlają prawidłowe daty w kalendarzach! Czy ktoś wie jak?

AKTUALIZACJA 2:

Odpowiedź od inżyniera ds. Eskalacji pomocy technicznej programu Outlook:

Na tej podstawie mogę potwierdzić, że jest to problem w naszym produkcie.

Bolick
źródło
3
1. Jaki jest twój kod? 2. Nie używaj protokołu WebDAV; jest przestarzały.
Dmitry Streblechenko
Wygląda idealnie ... Jaki jest Twój kod dostępu do spotkania? Czy kiedykolwiek uzyskujesz dostęp do AppointmentItem.Parent (który poda Ci spotkanie główne dla wystąpienia powtarzającej się czynności)?
Dmitry Streblechenko
Zaktualizowałem powyższy kod. Nie, nie używamy AppointmentItem.Parent. W każdym razie, zanim uzyskamy dostęp do dat rozpoczęcia i zakończenia, uzyskujemy dostęp tylko do właściwości Subject, ResponseStatus i MeetingStatus obiektu AppointmentItem.
Bolick
Po pierwsze, Outlook nie używa OOM do wyświetlania zawartości folderu kalendarza, po drugie, dlaczego uważasz, że daty rozpoczęcia / zakończenia są nieprawidłowe? Co dokładnie jest nie tak?
Dmitry Streblechenko
Dokładnie: OutlookSpy pokazuje nam kilka spotkań z tym samym StartTime, w naszym przypadku = 11/01/2012, i jest to z pewnością główne spotkanie cyklicznych tygodniowych czynności (ten sam organizator, ten sam temat itp.). Ale w kalendarzu widać poprawne zdjęcie - jedna pozycja na tydzień. Byłbym bardzo wdzięczny, gdybyś mógł wyjaśnić, jak działa Outlook, jakiej technologii używa do wyświetlania kalendarza, jakieś pomysły, dlaczego otrzymujemy błędny wynik w OOM i jak naprawić te błędy?
Bolick

Odpowiedzi:

1

Możliwa przyczyna:

  • Sortuj po ustawieniu IncludeRecurrences.

Oto mój kod modułu PowerShell, który pobiera elementy programu Outlook między dwiema datami.

I mały aplet do sprawdzania zmian i wysyłania wiadomości e-mail z aktualizacjami programu, co jest przydatne, gdy nie masz mobilnego dostępu do Exchange.

Ścieżka: 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 = “[email protected]“

  $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

Oto kod wywołujący funkcję PowerShell w module:

Ścieżka: 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

}

Aby uruchomić skrypt programu PowerShell, użyj programu powershell.exe. Aby uruchomić to podczas uruchamiania, użyj skrótu w „% APPDATA% \ Microsoft \ Windows \ Menu Start \ Programy \ Startup \”:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "C:\Users\%USERNAME%\Documents\WindowsPowerShell\mono.ps1"
Jose Manuel Gomez Alvarez
źródło