Wie schwer es sein kann, eine E-Mail (automatisiert) zu lesen

Der Dotnet-Doktor  –  0 Kommentare

Wieder einmal ein kleiner Erfahrungsbericht aus unserem Projektalltag...

Eine Kunde von uns empfängt regelmäßig Daten von externen Partnern über E-Mail-Anhänge. Dies E-Mails sollen automatisch ausgepackt und in eine Microsoft SQL Server 2005-Datenbank importiert werden. Grundsätzlich ist das Abholen von Nachrichten von einem Exchange Server und das Auspacken von Dateianhängen in wenigen Codezeilen auf Basis der Collaboration Data Objects (CDO) als .NET-basierte Konsolenanwendung realisierbar (siehe Beispiel). CDO ist zwar eine COM-Komponente (cdo.dll), kann aber über einen Runtime Callable Wrapper (RCW) von .NET aus genutzt werden. Eine Herausforderung stellt jedoch die Verwendung innerhalb von SSIS dar.

SQL Server Integration Services (SSIS) ist ein ETL-Serverprodukt (Export, Transform, Load), das mit Microsoft SQL Server 2005 mitgeliefert wird. Microsoft liefert hier – genauso wie bei Windows Workflow Foundation (WF) – keinen vorgefertigten Baustein (Task/Item/Activity) zum Abholen von E-Mails mit.

Grundsätzlich ist es möglich, den oben referenzierten Programmcode der Konsolenanwendung auch in SSIS zu verwenden, da SSIS eine sogenannte "Script Tasks" besitzt, in der man Visual Basic .NET-Code (nicht aber C#-Code oder andere .NET-Sprachen) mit Hilfe von SQL Server Business Intelligence Development Studio, einer reduzierten Variante von Visual Studio 2005, hinterlegen kann.

Im Detail gibt es aber mehrere Herausforderungen:

  • SSIS kann nur Assemblies verwenden, die im Global Assembly Cache (GAC) liegen. Der RCW für CDO braucht also zunächst einmal eine digitale Signatur ("Strong Name"), um überhaupt in den GAC aufgenommen werden zu können. Da Visual Studio dies nicht erledigt, braucht man einen Kommandozeilenbefehl:
    TlbImp.exe cdo.dll /primary /keyfile:itvisions_demo_key.snk /out:Interop.mapi.dll
    Danach kann man mit gacutil.exe die erzeugte RCW-Assembly (interop.mapi.dll) in den GAC aufnehmen.
  • Der Assembly-Referenz-Dialog in SQL Server Business Intelligence Development Studio findet aber die Assembly nicht im GAC. Es ist daher notwendig, zusätzlich eine Kopie der Assembly im Verzeichnis %windir%\Microsoft.net\framework\v2.0.50727 abzulegen.
  • Nach erfolgreicher Referenzierung der CDO-Komponente aus der Script Task und dem Transfer des Visual Basic .NET-Codes wird sich die Script Task dennoch beharrlich weigern, die COM-Komponente zu instanziieren, obwohl die Konsolenanwendung auf dem gleichen Rechner problemlos funktioniert. Dies liegt an den unterschiedlichen Threading-Modellen von COM und SSIS/.NET. ASP.NET besitzt dafür die Einstellung aspcompat="true". Ein Pendant dafür in SSIS gibt es nicht. Es bleibt nur die Möglichkeit, in der SSIS Script Task den Zugriffscode auf die CDO-Komponente in einem eigenen Thread zu starten, der explizit als STA-Thread gestartet wurde:
    Dim t As New Thread(New ThreadStart(AddressOf EMailAuswertenFunktion))
    t.ApartmentState = ApartmentState.STA
    t.Start()
    t.Join()
  • Danach funktioniert die Lösung beim lokalen Start im SQL Server Business Intelligence Development Studio und bei der Ausführung im SQL Server Management Studio, sofern der angemeldete Benutzer ein passendes MAPI-Profil für den Zugang zum Exchange Server besitzt.
  • Die nächste Herausforderung besteht aber darin, das SSIS-Paket im SQL Server Agent als regelmäßige Aufgabe auszuführen. Jobs im SQL Server laufen normalerweise unter dem Dienstkonto des SQL Server Agents-Systemdienstes. Dies ist zwar in der Standardeinstellung das Windows-System-Konto, aber für dieses gibt es kein MAPI-Profil. Also muss zunächst ein anderes Windows-Benutzerkonto (das Zugang zum Exchange Server über ein MAPI-Profil hat) für die Verwendung als Agent-Job-Konto eingerichtet werden. Dieser Vorgang ist in folgendem Beitrag beschrieben.
  • Ein MAPI-Profil richtet man übrigens über Microsoft Outlook oder die Mail-Anwendung in der Systemsteuerung ein, wenn Outlook installiert ist. Wenn kein Outlook installiert ist, muss man CDO speparat installieren. Wenn die Logon()-Methode des Session-Objekts in CDO das angegebene Profil nicht findet, erscheint übrigens ein Dialog, der zur Auswahl eines vorhandenen Profils oder zum Erstellen eines neuen Profils auffordert. Daher sollte man die Lösung unbedingt zunächst testen, wenn der betreffende Benutzer auf dem Server angemeldet ist.
  • Wichtig ist auch noch, dass die generierte RCW-Assembly, die cdo.dll und alle von CDO verwendeten DLLs hinsichtlich der Versionsnummer zueinander passen. Als SSIS sich weiterhin weigerte, die CDO-Komponente zu verwenden ("ActiveX Component Cannot Create Object"), hat unser Rat geholfen, auf dem Server Outlook zu deinstallieren und es mit der eigenständigen CDO-Installation zu versuchen.

Auf Anfrage sende ich übrigens gerne das SSIS-Paket, mit dem wir die Lösung getestet haben, zu. Bitte nehmen Sie Kontakt über mein Kontaktformular auf.

Übrigens: Microsoft erklärt in einem Knowledge Base-Artikel, dass CDO in .NET gar nicht unterstützt werde. Das heißt aber eben nicht, dass es gar nicht geht, sondern nur, dass man sich mit den Herausforderungen nicht an Microsoft wenden kann. Um so mehr ein Grund, diese Erfahrungen hier zu dokumentieren. Mich wundert, dass es dazu im Web bisher keine Informationen gab. In einem SSIS-Vorgang eine E-Mail auszuwerten ist doch gar kein so absurdes Szenario!