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.
Am 1. Okt 2022 schaltet Microsoft in
Exchange Online "BasicAuth" ab. Es gibt keinen
dokumentierten Weg ein OAUTH-Token für Extended Mapi zu
bekommen.
Microsoft doesn't expose permissions to generate OAuth
tokens for Extended MAPI access to mailboxes
https://docs.microsoft.com/en-us/outlook/troubleshoot/authentication/expose-permissions-issue-with-mapi-oauth-tokens
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.
- Outlook Sicherheitswarnung
Die auf Outlook Sicherheitswarnung beschriebenen Probleme kann man mit Redemption umgehen - Eingeschränkter CDO Support
Laut Microsoft funktioniert CDO unter.NET und PowerShell nicht zuverlässig, da sich das Speichermanagement angeblich nicht verträgt. Redemption hingegen scheint dies gelöst zu haben, so dass man hier auch aus .NET heraus mit dem COM-Objekt einfach arbeiten kann. - PowerShell Unterstützung
Meine ersten Gehversuche mit der PowerShell und CDO waren sehr ernüchternd, da die Microsoft CDO keine Typlibrary mitbringt, so dass Tab-Completion der PowerShell nicht funktioniert. Ich konnte mich auch nicht ordentlich an andere Exchange Postfächer anmelden, da die "CDOSessoin.LOGON"-Methode anscheinend aus der PowerShell keine Parameter unterstützt. Mit Redemption gibt es diese Einschränkung nicht, Es gibt sogar weitere Methoden um direkt Exchange Postfächer oder PST-Dateien einzubinden - MSG/EML-Export/Import
Zudem kann man mit Redemption die Mails sehr einfach als Datei exportieren aber auch importieren. Eine Ideale Basis für eigene Migrationslösungen. - Erweiterte MAPI-Funktion
MAPI erlaubt z.B. eine "Synchronisation" von Inhalten, indem man sich den letzten Stand merkt und später einfach diese Info an Exchange sendet, welcher dann die seither geänderten Objekte zurück gibt. - Redemption ohne CDO/Outlook
Der Entwickler hat sogar den Spagat geschafft, die COM-Klassen anzubieten, selbst wenn auf dem System kein Outlook oder CDO installiert ist. Nur die rudimentäre MAPI-Funktion muss vorhanden sein. - Profilbearbeitung
Zudem erlaubt Redemption über die PROFMAN-Library sogar eine Konfiguration der MAPI-Profileinstellungen per Skript. Das was bislang den C++ Entwicklern vorbehalten.
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) |
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: User2 <mailto:user2@msxfaq.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
- Homepage von Redemption
http://www.dimastr.com/redemption/
http://www.dimastr.com/redemption/rdo/default.htm - PowerShell Beispiele
- PowerShell4Admin
- MAPI/CDO
- Outlook VBA
- Microsoft doesn't expose permissions to generate OAuth tokens
for Extended MAPI access to mailboxes
https://docs.microsoft.com/en-us/outlook/troubleshoot/authentication/expose-permissions-issue-with-mapi-oauth-tokens - Attempting to use Redemption and PowerShell to import .msg files
from a folder
http://www.PowerShellcommunity.org/Forums/tabid/54/aff/3/aft/4885/afv/topic/Default.aspx
$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()
- Exporting Mailboxes Larger Than 2 GB On An Exchange Server
http://theessentialexchange.com/blogs/michael/archive/2009/10/16/exporting-mailboxes-larger-than-2-gb-on-an-exchange-server.aspx - 813349 Support policy für Microsoft Exchange APIs with the .NET
Framework applications
Info, dass CDO und MAPI nur noch in C++-Anwendungen unterstützt werden. - Thread.ApartmentState and PowerShell Execution Thread
http://blogs.msdn.com/PowerShell/archive/2007/03/23/thread-apartmentstate-and-PowerShell-execution-thread.aspx - MAPI33
.Net wrapper um native Microsoft Extended MAPI
http://www.mapi33.adexsolutions.com/ - mossSOFT OLConnector
http://www.mosstools.de/index.php?option=com_content&view=article&id=64&Itemid=64