Redemption, CDO und Powershell

Eigentlich würde ich diese Seite unter Produkte rund um Exchange ansiedeln, da Redemption kommerziell ist, aber die Information für Entwickler wichtig ist, habe ich diese Seite doch bei Programmieren mit Exchange abgelegt.

Es gibt auf der MSXFAQ sehr viele Beschreibungen und Codebeispiele zum Einsatz von Outlook VBA und MAPI/CDO, so dass ich lange auf Redemption verzichtet habe. Aber die Microsoft Schnittstellen kranken doch an einigen Stellen, die den Wunsch nach Drittprodukten aufkommen lassen, z.B.

All das kann Redemption lösen und ist sogar mit einer einzigen DLL die auf das Zielsystem kopiert und registriert werden muss, einfach zu installieren. Das Lizenzmodell ist auch sehr interessant. Sie können eine Entwicklungsversion kostenfrei herunter laden, welche aber mit einer Lizenzmeldung darauf hinweist und damit eine vollständige Automatisierung nicht erlaubt. Die Vollversion ist mit 150 Euro für den Entwickler und einer kostenfreie Weitergabe der DLL an davon abgeleitet kommerzielle Produkte ein echtes Schnäppchen.

Redemption und STA/MTA

Bei der Entwicklung von Programmen muss man genau beachten, ob man "Single threaded" oder "Multi Threaded" programmiert. Und genau das kann zu Problemen führen. Bei meinen Tests, einen öffentlichen Ordner zu öffnen, habe ich folgendes Verhalten beobachtet:

Programmiersprache Status
VBScript Funktioniert
Powershell Fehler
C# Console Application (VS2008 Express) Fehler (default)
Erfolgreich (mit STATHREAD)
C# Windows Forms Application (VS2008 Express) Erfolgreich

Das war natürlich erst einmal etwas verwirrend und zusammen mit Dmitry Streblechenko und der Powershell Newsgroup haben wir dann doch die Ursache gefunden. War war nicht das Fehlen der Windows Message Pump bei Console Anwendungen (Keine Formulare) sondern die Funktion, das Powershell als auch Console Anwendungen anscheinend "Muti Threading" nutzen. Die Console Anwendnug konnte man mit einem "[STATHREAD]" zwingen, einen Single Thread zu nutzen und damit die Funktion zu erhalten. Die Betriebsart STA kennt aber Powershell 1.0 noch nicht. (siehe auch http://blogs.msdn.com/powershell/archive/2007/03/23/thread-apartmentstate-and-powershell-execution-thread.aspx). Erst Powershell 2 kann man mit der Option "-STA" in einen Single Thread Mode zwingen.

Redemption und Powershell

Achtung mit Powershell 1
Diese Beispiele funktionieren nur unter Einschränkungen zuverlässig, da Powershell 1 per Default MultiThreaded arbeitet.

Je mehr Lösungen ich mit Powershell entwickle, desto schmerzlicher war die Einschränkungen, dass ich damit kein CDO nutzen konnte. Also habe ich mich umgeschaut und bin dann doch bei Redemption gelandet, da ich zum einen per Powershell nicht wirklich einfach die Exchange Webservices nutzen wollte (die mich dann eh auf E2007 beschränkt hätten) und ich das CDO Objektmodell doch schon recht gut kenne. Und zusammen mit der Powershell kann man all das schön "interaktiv" machen.

Hier sieht man schon mal den Aufbaue einer RDO-Session mit einer expliziten Anmeldung am Exchange Server, ohne vorher erst ein Profil anlegen zu müssen:

PS> $redemptionsession = New-Object -com redemption.rdosession
PS> $redemptionsession.LogonExchangeMailbox("fcarius","nawsv002")
PS> $redemptionsession


ProfileName                  : {D83E61D0-0764-4852-A8D1-EE03492CCF61}
LoggedOn                     : True
MAPIOBJECT                   : System.__ComObject
Stores                       : {Postfach - Carius, Frank}
AddressBook                  : System.__ComObject
CurrentUser                  : System.__ComObject
Accounts                     : {}
AuthKey                      :
TimeZones                    : {(GMT-12:00) Internationale Datumsgrenze (Westen), (GMT-11:00) Midway-Inseln, Samoa, (GM
                               T-10:00) Hawaii, (GMT-09:00) Alaska...}
Profiles                     : {CDO_Admin@msxfaq.local_00002BD8_00000001, CDO_Administrator@msxfaq.local_00002B7C_00000
                               001, CDO_Administrator@msxfaq.local_00003D3C_00000001, CDO_Administrator@msxfaq.local_00
                               003E10_00000001...}
ExchangeConnectionMode       : 800
ExchangeMailboxServerName    : NAWSV002
ExchangeMailboxServerVersion : 8.1.291.1
OutlookVersion               : 12.0.6316.5000
CurrentWindowsUser           : System.__ComObject

Die Ausgabe zeigt mir, dass ich angemeldet bin (LoggedOn : True) und ich auch ein Postfach habe. Ich kann mir nun auch direkt einen Ordner binden. Dazu verwende ich eine der folgenden Konstanten:

$olAppointmentItem = 1;
$olFolderDeletedItems = 3;
$olFolderOutbox = 4;
$olFolderSentMail = 5;
$olFolderInbox = 6;
$olFolderCalendar = 9;
$olFolderContacts = 10;
$olFolderJournal = 11;
$olFolderNotes = 12;
$olFolderTasks = 13;
$olFolderDrafts = 16;
$olPublicFoldersAllPublicFolders = 18;
$olFolderConflicts = 19;
$olFolderSyncIssues = 20;
$olFolderLocalFailures = 21;
$olFolderServerFailures = 22;
$olFolderJunk = 23;

Und hole mir z.B. den Posteingang in eine neue Variable:

PS > $folder = $redemptionsession.Stores.GetDefaultFolder($olFolderInbox)
PS > $folder


MAPIOBJECT             : System.__ComObject
Session                : System.__ComObject
DefaultMessageClass    : IPM.Note
Description            :
EntryID                : 000000007C317BB2CD33D011AFC9008029638ABA0100F1A817713F07D011AF9E008029638ABA00000001713F0000
Name                   : Posteingang
Parent                 : System.__ComObject
StoreID                : 0000000038A1BB1005E5101AA1BB08002B2A56C20000454D534D44422E444C4C00000000000000001B55FA20AA6611
                         CD9BC800AA002FC45A0C0000004E41575356303032002F6F3D4E657420617420576F726B20476D62482F6F753D5061
                         646572626F726E2F636E3D526563697069656E74732F636E3D6663617269757300
UnReadItemCount        : 58
Items                  : {Carius, Frank, Carius, Frank, , Carius, Frank...}
Folders                : {_MSXFAQ ToDo, _Spam, _test, Aktuelle Vorgänge...}
HiddenItems            : {, , , ...}
Store                  : System.__ComObject
AddressBookName        : Posteingang
ShowAsOutlookAB        : False
DefaultItemType        : 0
WebViewAllowNavigation : True
WebViewOn              : False
WebViewURL             :
DeletedItems           : {Carius, Frank, Carius, Frank, Carius, Frank, Carius, Frank...}
FolderKind             : 1
ACL                    : {Standard, Anonym}
FolderPath             : \\Postfach - Carius, Frank\Posteingang
FolderFields           : {}
IsInPFFavorites        : False
DeletedFolders         : {}
ExchangeSynchonizer    : System.__ComObject
ShowItemCount          : 1
ExchangeSynchronizer   : System.__ComObject

Man sieht direkt, dass ich zu dem Zeitpunkt 58 "ungelesene" Elemente hatte und welche Unterordner es gibt. Ich kann natürlich nicht nur lesen, sondern auch bestimmte Properties schreiben. Wenn ich nun die Mails in dem Ordner ansprechen will, dann kann ich mir über "Items" einfach die Auflistung holen, welche ich entweder als Collection durchlaufen kann, oder mit GetFirst/GetNext abarbeiten kann. Hier mal die erste Mail. (Ausgabe gekürzt)

PS> $mail = $folder.Items.GetFirst()
PS> $mail

MAPIOBJECT                        : System.__ComObject
Session                           : System.__ComObject
EntryID                           : 000000007C317BB2CD33D011AFC9008029638ABA0700F1A817713F07D011AF9E008029638ABA0000000
                                    1713F0000EAB40024B97AD74587304851424699D60013B095B2D90000
Subject                           : Zugestellt: Accepted: Team Meeting
AlternateRecipientAllowed         : False
AutoForwarded                     : False
BCC                               :
BillingInformation                :
Body                              : Ihre Nachricht wurde den folgenden Empfängern zugestellt:
                                    Ulbrich, Uwe <mailto:Uwe.Ulbrich@netatwork.de>
                                    Betreff: Accepted: Team Meeting
                                    ________________________________
                                    Mit Microsoft Exchange Server 2007 gesendet
BodyFormat                        : 2
CreationTime                      : 01.10.2008 12:17:47
HTMLBody                          : <html>
                                    <Head></head><body>
                                    <p><b><font color="#000066" size="3" face="Arial">Ihre Nachricht wurde den folgende
                                    n Empfängern zugestellt:</font></b></p>
...
                                    </body>
                                    </html>
Importance                        : 1
MessageClass                      : REPORT.IPM.Schedule.Meeting.Resp.Pos.DR
ReceivedTime                      : 01.10.2008 12:17:47
Size                              : 998
Submitted                         : False
To                                : Ulbrich, Uwe
UnRead                            : True
RTFBody                           : {\rtf1\ansi\ansicpg1252\fromhtml1 \fbidis \deff0{\fonttbl
                                    {\f0\fswiss Arial;}
                                    {\f1\fmodern Courier New;}
                                    {\f2\fnil\fcharset2 Symbol;}</html>}}
Attachments                       : {}
Recipients                        : {Ulbrich, Uwe}
HidePaperClip                     : False
Sender                            : System.__ComObject
SentOnBehalfOf                    : System.__ComObject
Store                             : System.__ComObject
Parent                            : System.__ComObject
Actions                           : {Reply, Reply to All, Forward, Reply to Folder}
Conflicts                         : {}
UserProperties                    : {}
Modified                          : False
ReportText                        : Your message
                                          To:    Ulbrich, Uwe
                                          Subject:    Accepted: Team Meeting
                                          Sent:    01.10.2008 12:17
                                    was delivered to the following recipient(s):
                                          Ulbrich, Uwe on 01.10.2008 12:17

Noch mal als Zusammenfassung. Es hat mich also gerade mal 5 Zeilen gekostet, um ohne Outlook in einen Exchange Store zuzugreifen.

PS> $redemptionsession = New-Object -com redemption.rdosession
PS> $redemptionsession.LogonExchangeMailbox("fcarius","nawsv002")
PS> $folder = $redemptionsession.Stores.GetDefaultFolder($olFolderInbox)
PS> $mail = $folder.Items.GetFirst()

Sie können sich sicher vorstellen, dass es nicht sehr viel schwerer ist, um diese Zeilen eine Schleife zu bauen, um mehrere Elemente, Ordner oder sogar alle Postfächer zu durchlaufen.

Weitere Links

$outlook = New-Object -COM Outlook.Application 
$redemtionOutlook = New-Object -COM Redemption.RDOSession 
$redemtionOutlook.Logon() 
$redemtionOutlook.MAPIOBJECT = $outlook.Session.MAPIOBJECT 
$msg = $redemtionOutlook.GetDefaultFolder(6).Items.Add(0) 
$msg.Import("c:\testmail.msg", 3) 
$msg.Save() 
$redemtionOutlook.Logoff()
Tags:Redemption CDO MAPI VBA