Outlook und PowerShell

Jahrelang habe ich mit Outlook mit VBScript (Siehe Outlook VBScript) angesteuert, bis Microsoft den Zugriff aufgrund des Missbrauchspotential durch Viren eingeschränkt hat. Dann habe ich per VBScript mit MAPI/CDO hantiert. Seit Exchange 2007 und neuer ist natürlich EWS die bevorzugte Schnittstellen, auch wenn die Exchange REST-API eine neue Möglichkeit aufzeigt.

Dennoch ist Outlook immer noch ein sehr nützliches Werkzeug, um mal schnell an die Elemente eines Postfachs zu kommen und zu verarbeiten.

Einsatzbereich

PowerShell spielt eine immer größere Rolle in meiner täglichen Tätigkeit und da ist es nur logisch, wenn ich immer weniger VBScript einsetze. Nur für Outlook war VBScript neben Outlook VBA lange Zeit meine primäre Skriptsprache. Die Programmierung von Outlook unterliegt aber einigen Einschränkungen:

  • Outlook muss installiert und idealerweise gestartet werden
  • Ein Outlook Profil muss bereits konfiguriert sein
    Ich habe kaum Wege gefunden, per PowerShell die Profileinstellungen zu ändern.
  • Keine Impersonation
    Man arbeitet immer mit dem aktiv angemeldeten Benutzer
  • Kein wahlfreier Postfachzugriff
    Ich konnte bislang nur auf die Ordner zugreifen, die Outlook auch eingebunden hatte

Dafür ist die Programmierung im Vergleich zu EWS eher "einfach" und Sie können parallel per Outlook sehen, was das Skript sieht. Zudem ist auf einem PC, auf dem Outlook vorhanden ist, CDO sowieso nicht mehr möglich.

Collaboration Data Objects (CDO) 1.2.1 is not supported with Outlook 2010 and later versions
https://support.microsoft.com/en-us/help/2028411/collaboration-data-objects-cdo-1.2.1-is-not-supported-with-outlook-2010-and-later-versions
CDO 1.2.1 is not supported for use with Outlook 2010 or later versions, and we do not recommend its use with Outlook 2010 and later versions.

CDO könnte man zwar noch auf einem Exchange Server installieren aber es ist eh kein 64bit Code mehr. Letztlich sollte man richtige Projekte auf EWS aufsetzen. Aber um mal eben schnell etwas zu fixen, was der Anwender (oder ein Anmeldeskript) auch ohne weitere Installationen einer EWS Managed API o.ä. starten kann, ist das Outlook Modell immer noch passend.

Outlook per COM-Objekt

Outlook hat immer noch COM-Objekte, und die kann natürlich auch die PowerShell instanzieren. Allerdings kommt hier wieder verschiedene Warnungen, z.B. wenn sie automatisch auf den Kontaktordner lesend zugreifen wollen. Dieser kurze Abschnitt instanziert Outlook als COM-Objekt und listet den Betreff aller Mails im Posteingang auf.

# Outlook per PowerShell Sample
#

write-host "get-pfcontent: Loading Outlook Object Model"
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null

write-host "Erstellen der Auflistung fuer Default Folder"
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]

Write-host "Instanziere Outlook"
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")

Write-host "Binde Posteingang"
$inbox = $namespace.getDefaultFolder($olFolders::olFolderInBox)

$inbox.Items | foreach { $_.subject }

Writel-host "Binde Public Folder"
$pffolderroot = $namespace.getDefaultFolder($olFolders::olPublicFoldersAllPublicFolders)

Ein Zugriff auf die gleichen Funktionen per CDO ist übrigens nicht möglich, da hier das Multithreading von PowerShell mit CDO nicht kompatibel ist.

PowerShell User Guide page 33,
"Not all COM objects are supported. Objects that are based on Exchange Collaboration Data Objects (CDO) are not supported in this release."
813349 Support policy für Microsoft Exchange APIs with the .NET Framework applications

Vorsicht bei Loops

Es ist eigentlich naheliegend, die Inhalte eines Ordners einfach mit einer Schleife oder gar Pipeline abzuarbeiten. Das ist aber keine gute Idee, da .NET den Speicher nicht zuverlässig wieder freigeben kann, wen die Laufvariable so benutzt wird.

Besser ist es daher mit einer "Zählervariable" zu arbeiten

foreach ($count in 1..($folder.items.count)) {
   $item = $folder.items.item($count)
   write-host "  Item: ($count) = $($item.Subject)"
   write-host $item.ReceivedTime
   write-host $item.SenderName
   write-host $item.Size
}

Über den Counter ist natürlich auch einfach ein Fortschrittsanzeige möglich.

Große Ordner

Speziell bei Ordnern mit sehr vielen Inhalten ist aber auch das sequentielle Durchlaufen mühsam und hat immer das Problem, das speziell Änderungen auch die Reihenfolge ändern. Hier ist es dann sinnvoller sich von Element zu Element zu hangeln.

Ordner gezielt anspringen

Mit ist es noch nicht gelungen, einen Ordner anhand des Pfad direkt und gezielt anzuspringen. Man muss sich schon selbst durch die Ordnerstruktur hangeln und schauen, ob der Name des aktuellen Ordners oder der Pfad mit dem gesuchten Ordner übereinstimmt.

function process-folder($folder)  {
   write-host "get-pfcontent: Folder: $($folder.folderpath)" -nonewline
   [string]$folderpath = $folder.folderpath
   if (( $recursive -and ($folder.FullFolderPath.startswith($startfolder))) -or (($folder.FullFolderPath -eq $startfolder))){ 
      write-host "get-pfcontent:   Start Items"
      if ($folder.items.count -ge 1) {
         foreach ($count in 1..($folder.items.count)) {
            $item = $folder.items.item($count)
            write-host "  Item: ($count) = $($item.Subject)"
         }
      }
   else {
       write-host "  Skip Folder"
   }
   write-host "  Subfolders"
   if ($folder.folders.count -ge 1){
      foreach ($count in 1..($folder.folders.count)) {
         Write-host "  Next Subfolder $($subfolder.folderpath)"
         process-folder $folder.folders.item($count)
      }
   }
}

# start recursive
write-host "Start Folder Processing"
process-folder $pffolderroot

Weitere Links