VB.NET - Mit Lotus Notes E-Mails versenden

Vor etwas mehr als zwei Jahre (wie die Zeit vergeht :-() habe ich hier einen Artikel namens "VB .NET - Mit Lotus Notes E-Mails und Dateianhängen arbeiten" veröffentlicht, der beschreibt, wie man mit Lotus Notes E-Mails aus der Inbox arbeiten kann.

Heute musste ich nun etwas ähnliches bewerkstellen, und zwar E-Mails über unseren Domino Server versenden. Im Prinzip benötigt man dazu keine COM-Verweis auf das Domino Mail Objekt, da man (sofern SMTP auf dem Domino Server freigegegeben ist) das ganze auch schön mit .NET Bordmitteln, sprich Net.Mail.MailMessage usw. lösen kann.

Doch wir wollten auch Faxe über Lotus Notes versenden, und dazu benötigt der Domino Server eine Authentifizierung, was man theoretisch über die .NET Credentials lösen könnte. Leider bekam ich beim Faxversenden immer wieder den Fehler, dass die Benutzerrechte nicht korrekt sein.

Laut Rechereche im Internet hat das auch noch keiner hinbekommen, also blieb mir nichts anderes übrig, als wieder auf das Domino COM-Objekt zu verweisen und damit zu arbeiten.

Herausgekommen dabei sind einige Klassen, mit denen man relativ einfach E-Mails und Faxe über den Server verschicken kann.

Zuerst einmal die Helfer-Klassen:

Hier die Anhangs-Klasse für Dateianhänge:

Public Class LotusNotesMailAttachment
 
  Public FullFilename As String
  Public DisplayName As String
 
  Public Sub New()
  End Sub
 
  Public Sub New(ByVal aFullFilename As String)
    Me.FullFilename = aFullFilename
  End Sub
 
  Public Sub New(ByVal aFullFilename As String, ByVal aDisplayName As String)
    Me.FullFilename = aFullFilename
    Me.Displayname = aDisplayName
  End Sub
 
End Class

Die Liste der Anhänge:

Public Class LotusNotesMailAttachmentList
 
  Inherits List(Of LotusNotesMailAttachment)
 
End Class

Die Klase LotusNotesMailData enthält einfach nur Absender, Empfänger, Betreff usw.
Diese Daten werden später an die Factory zum Versenden weitergereicht.

Public Class LotusNotesMailData
 
  Public Subject As String
 
  Private _To As List(Of String)
  Public Property [To]() As List(Of String)
    Get
      If _To Is Nothing Then
        _To = New List(Of String)
      End If
      Return _To
    End Get
    Set(ByVal value As List(Of String))
      _To = value
    End Set
  End Property
 
  Public From As String
 
  Private _CC As List(Of String)
  Public Property CC() As List(Of String)
    Get
      If _CC Is Nothing Then
        _CC = New List(Of String)
      End If
      Return _CC
    End Get
    Set(ByVal value As List(Of String))
      _CC = value
    End Set
  End Property
  Public Body As String
 
  Private _Attachments As LotusNotesMailAttachmentList
  Public Property Attachments() As LotusNotesMailAttachmentList
    Get
      If _Attachments Is Nothing Then
        _Attachments = New LotusNotesMailAttachmentList
      End If
      Return _Attachments
    End Get
    Set(ByVal value As LotusNotesMailAttachmentList)
      _Attachments = value
    End Set
  End Property
 
End Class

Die folgende Klasse "MailAccount" beinhaltet einfach die "Login-Daten" für den Domino-Server und Infos über das Postfach, das zum Senden verwendet werden soll.

Klasse Mailaccount:

Public Class MailAccount
 
  ''' <summary>
  ''' Hostname of the Domino Server
  ''' </summary>
  ''' <remarks></remarks>
  Public Host As String
 
  ''' <summary>
  ''' Filename with Path of the Notes Mail File
  ''' </summary>
  ''' <remarks>e.g. 'mail\user.nsf'</remarks>
  Public DatabaseFilename As String
 
  Public Password As String
 
  Public Sub New()
  End Sub
 
  Public Sub New(ByVal aHost As String, ByVal aDatabaseFilename As String, ByVal aPassword As String)
    Me.Host = aHost
    Me.DatabaseFilename = aDatabaseFilename
    Me.Password = aPassword
  End Sub
 
End Class

Hier nun meine Notes Factory, die das eigentliche Versenden der E-Mails und Faxe bewerkstelligt. Faxe werden dabei wie E-Mails gehandhabt. Bei unserer Fax-Software für Domino (Ferrari-Fax (übrigens sehr zu empfehlen)) wird statt der E-Mail Adresse einfach die Faxnummer mit dem Zusatz "@Fax" angegeben. Die Faxsoftware wandelt daraufhin alle Anhänge in TIFF um und sendet diese als Fax an den/die Empfänger.

Man erhält zudem das versendete Fax in Form einer E-Mail im "Gesendet" Ordner von Lotus Notes.

Public Class LotusNotesMailFactory
 
  Public Shared Function SendMail(ByVal aMailAccount As MailAccount, ByVal aMaildata As LotusNotesMailData) As Boolean
 
    ' Die Session starten
    Dim Session As New Domino.NotesSession
    Session.Initialize(aMailAccount.Password)
 
    ' Datenbank öffnen
    Dim MailDB As Domino.NotesDatabase
    Dim MailServer As String = Session.GetEnvironmentString("MailServer", True)
    MailDB = Session.GetDatabase(MailServer, aMailAccount.DatabaseFilename)
 
    If Not MailDB.IsOpen Then
      MailDB.Open()
    End If
 
    Dim MailDoc As Domino.NotesDocument = MailDB.CreateDocument
    Dim Body As Domino.NotesRichTextItem = MailDoc.CreateRichTextItem("Body")
 
    MailDoc.ReplaceItemValue("Form", "Memo")
 
    For Each toadr As String In aMaildata.To
      MailDoc.ReplaceItemValue("SendTo", toadr)
      'MailDoc.AppendItemValue("SendTo", aMaildata.To)
    Next
 
    MailDoc.ReplaceItemValue("Subject", aMaildata.Subject)
    'MailDoc.AppendItemValue("Subject", aMaildata.Subject)

    ' CC (Carbon Copy)
    For Each ccadr As String In aMaildata.CC
      MailDoc.AppendItemValue("CopyTo", ccadr)
    Next
 
    ' Create and set the Body content
    Body.AppendText(String.Empty)
    Body.AppendText(aMaildata.Body)
 
    ' Attachment
    For Each lnma As LotusNotesMailAttachment In aMaildata.Attachments
      Body.AddNewLine(2)
      Body.EmbedObject(1454, String.Empty, lnma.FullFilename, lnma.DisplayName)
    Next
 
    ' Example to save the message (optional)
    MailDoc.SaveMessageOnSend = True
 
    ' Send the document
    ' Gets the mail to appear in the Sent items folder
    MailDoc.ReplaceItemValue("PostedDate", Now)
    MailDoc.Send(False)
 
    ' Clean Up
    MailDB = Nothing
    MailDoc = Nothing
    Body = Nothing
    Session = Nothing
 
    Return True
 
  End Function
 
End Class

Wer Verbesserungen hat, oder gar weiß, wie man über die .NET Bordmittel das ganze bewerkstellingen kann, ist herzlich eingeladen, zu kommentieren.

Sollte ich dazukommen, werde ich das ganze noch etwas besser dokumentieren, aufräumen und verbessern.

VCard / VCF Dateien in VB.NET erstellen

Hier eine kleine Klasse, die VCard-Dateien erstellen kann.
Wie man die Klasse aufruft, zeige ich am Ende dieses Eintrags.

Klasse VCard.vb:

Public Class VCard
 
  Private _VCardData As List(Of String)
  Public Property VCardData() As List(Of String)
    Get
      If _VCardData Is Nothing Then
        _VCardData = New List(Of String)
      End If
      Return _VCardData
    End Get
    Set(ByVal value As List(Of String))
      _VCardData = value
    End Set
  End Property
 
  Public InternalName As String
 
  Public VORNAME As String
  Public NACHNAME As String
  Public STRASSE As String
  Public ORT As String
  Public PLZ As String
  Public LAND As String
  Public TELEFONNUMMER As String
  Public TELEFAXNUMMER As String
  Public MOBILNUMMER As String
  Public MAILADRESSE As String
 
  Public Sub New(ByVal aInternalName As String)
 
    Me.InternalName = aInternalName
 
    Me.VCardData.Add("BEGIN:VCARD")
    Me.VCardData.Add("VERSION:2.1")
    Me.VCardData.Add("FN:%VORNAME% %NACHNAME%")
    Me.VCardData.Add("N:%NACHNAME%;%VORNAME%")
    Me.VCardData.Add("ADR;HOME:;;%STRASSE%;%ORT%;;%PLZ%;%LAND%")
    Me.VCardData.Add("TEL;HOME;VOICE:%TELEFONNUMMER%")
    Me.VCardData.Add("TEL;CELL:%MOBILNUMMER%")
    Me.VCardData.Add("TEL;HOME;FAX:%TELEFAXNUMMER%")
    Me.VCardData.Add("EMAIL;WORK;PREF;INTERNET:%MAILADRESSE%")
    Me.VCardData.Add("END:VCARD")
 
  End Sub
 
  ''' <summary>
  ''' Anhand der Felder werden sämtliche Informationen in VCardData durch die jeweiligen Feldwerte ersetzt
  ''' </summary>
  ''' <remarks></remarks>
  Public Sub Fill()
 
    Dim fis As System.Reflection.FieldInfo() = Me.GetType().GetFields()
 
    Dim strReplace As String = String.Empty
    Dim strSearch As String = String.Empty
 
    ' Durch jede Zeile
    For i As Integer = 0 To Me.VCardData.Count - 1
 
      ' Zeile merken
      Dim line As String = Me.VCardData(i)
      If Not line.Contains("%") Then GoTo NextLine
 
      ' Durch alle Felder
      For Each fi As System.Reflection.FieldInfo In fis
 
        strSearch = "%" & fi.Name.ToUpper & "%"
        strReplace = fi.GetValue(Me)
 
        ' Debug.Print("Replacing: " & line & " with: " & strSearch & " - " & strReplace)

        line = Replace(line, strSearch, strReplace)
 
      Next
 
      Me.VCardData(i) = line
 
NextLine:
    Next
 
  End Sub
 
  Public Sub ExportToPath(ByVal aDirectory As String)
 
    ' Mich mit Daten füllen
    Me.Fill()
 
    ' Text für VCard Datei generieren
    Dim strVCardData As String = String.Empty
    For Each line As String In Me.VCardData
      If strVCardData = String.Empty Then
        strVCardData = line
      Else
        strVCardData += vbNewLine & line
      End If
 
    Next
 
    ' Datei schreiben
    My.Computer.FileSystem.WriteAllText(aDirectory & "\" & Me.InternalName & ".vcf", strVCardData, False, System.Text.Encoding.ASCII)
 
  End Sub
 
End Class

Klasse VCardList.vb:

Public Class VCardList
 
  Inherits List(Of VCard)
 
End Class

Aufruf:

Public Class SCVCardCreator
 
  Public Sub New()
  End Sub
 
  Private Function GetList() As VCardList
 
    Dim Result As New VCardList
 
    Using con As New SqlClient.SqlConnection(My.Settings.SQLConnectionString)
 
      Try
        con.Open()
      Catch ex As Exception
        Return Nothing
      End Try
 
      Dim strSQL As String = "Select * From Datenquelle"
 
      Using cmd As New SqlClient.SqlCommand(strSQL, con)
 
        Using dr As SqlClient.SqlDataReader = cmd.ExecuteReader
 
          Do While dr.Read
 
            Try
 
              Dim vcard As New VCard(dr.Item("MAB_NACHNAME"))
              With vcard
 
                Try
                  .VORNAME = dr.Item("MAB_VORNAME")
                Catch ex As Exception
                End Try
 
                Try
                  .NACHNAME = dr.Item("MAB_NACHNAME")
                Catch ex As Exception
                End Try
 
             End With
 
              Result.Add(vcard)
 
            Catch ex As Exception
 
            End Try
 
          Loop
 
        End Using
 
      End Using
 
      con.Close()
    End Using
 
    Return Result
 
  End Function
 
  Public Sub ExportToPath(ByVal aDirectory As String)
 
    Dim lst As VCardList = Me.GetList
 
    For Each vcard As VCard In lst
      vcard.ExportToPath(aDirectory)
    Next
 
  End Sub
 
End Class

Countdown Version 1.1.3826

Auf eine Anfrage hin habe ich Countdown um eine neue Zeiteingabeart erweitert. Bisher konnte man im sogenannten Countdown-Modus ein Enddatum wie beispielsweise die heutige Nacht 24:00 Uhr einstellen.

Nun ist es auch möglich, die gewünschte Endzeit direkt in Tagen, Stunden, Minuten oder Sekunden einzugeben (ich nenne das mal Hochzählen oder CountUp :-).

Die neue Version von Countdown findet sich auf der Programmseite.

MySQL unter VB .NET

Um MySQL unter VB .NET zu verwenden genügt es eigentlich, sich den Quellcode des MySQL Connectors von MySQL.de herunterzuladen, das ganze zu kompilieren und anschließend die kompilierte DLL ins gewünschte Projekt als Referenz einzubinden.

Anschließend kann man wie unter ADO.NET mit Connections, Commands und DataReadern arbeiten:

(angenommen, wir hätten eine Tabelle namens Users, mit einer Spalte Username, die einige Benutzernamen enthält)

Module modMain
 
  Sub Main()
 
    Using con As New MySql.Data.MySqlClient.MySqlConnection("Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword")
 
      Try
        con.Open()
      Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.Critical)
      End Try
 
      Using cmd As New MySql.Data.MySqlClient.MySqlCommand("SELECT * FROM Users", con)
 
        Using dr As MySql.Data.MySqlClient.MySqlDataReader = cmd.ExecuteReader
 
          Do While dr.Read
            MsgBox(dr.Item("Username"))
          Loop
 
        End Using
 
      End Using
 
    End Using
 
  End Sub
 
End Module

Mit NLog unter .NET protokollieren

Wer kennt das nicht? Mal eben ein kleines Programm geschrieben, an die Anwender verteilt und auf irgendeinem Rechner läuft das Programm nicht wie es soll.

Nun ist guter Rat teuer, denn welcher Programmierer gibt sich schon die Mühe in kleinere Tools eine ausgefeilte Protokoll-Funktion zu implementieren um dem Fehler gegebenenfalls schnell auf die Schliche zu kommen?

Mit NLog ist das anders. NLog ist ein Framework fürs Protokollieren unter .NET. Ist der Verweis zu NLog im Projekt erst einmal eingebunden, kann man mit einer Zeile wie

Me.Logger.Warn(ex.Message)

eine Exception inklusive aufrufender Methode und kompletter Exception-Fehlerausgabe protokollieren.

Auf der NLog Webseite ist zwar relativ einfach beschrieben, wie man das Framework in das eigene Projekt einbaut, was ich hier im folgenden allerdings beschreibe, ist noch etwas kürzer, da ausschließlich für VB .NET.

Verweis einbinden

Bevor man das NLog Framework verwenden kann, muss man als Verweis die NLog.dll zum Projekt hinzufügen. Hierzu lädt man auf der Codeplex Projekteseite von NLog am einfachsten die vorkompilierte DLL herunter (man kann sich natürlich auch den Quellcode schnappen und selbst kompilieren :-).

Konfigurationsdatei erstellen

Es gibt zwar viele Varianten, wie NLog nach einer Konfigurationsdatei sucht, aber die einfachste ist, eine Datei namens "nlog.config" im Programmverzeichnis zu erstellen.

Als Inhalt dieser Datei empfehle ich für Anfänger folgendes:

<geshi lang="xml"><nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
  <targets> 
    <target name="file" xsi:type="File" 
            layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring} ${newline}" 
            fileName="${basedir}/${windows-identity:domain=false}.log"/> 
  </targets> 
  <rules> 
    <logger name="*" minlevel="Debug" writeTo="file"/> 
  </rules> 
</nlog>

In der Konfigurationsdatei wird mit dem Tag "Target" bestimmt, wohin der Protokolleintrag wandern soll.

Das Target vom Typ "file" beispielsweise schreibt alle Einträge in eine Datei. Außer der Möglichkeit Protokolleinträge in Dateien zu schreiben, bietet NLog dem Benutzer auch eine Mail, ein Textcontrol, ein Netzwerk, das Windows Ereignisprotokoll einen Webservice und eine per ADO.NET ansprechbare Datenbank als Ziel an. Darüberhinaus ist es auch noch möglich, sich eigene Targets, wie beispielsweise einen FTP-Server, zu schreiben. Und natürlich kann man auch mehrere Targets gleichzeitig befüllen.

Im obigen Beispiel wir als Ziel eine Datei verwendet, deren Dateiname aus dem Windows-Benutzername besteht. Die Datei wird dabei automatisch in Unterordnern gelagert, deren Name aus dem aktuellen Datum generiert wird.

Der Tag "Layout" beschreibt dabei, wie die Protokolleinträge formatiert bzw. ergänzt werden.

Der Platzhalter "${longdate}" wird durch das Datum, der Platzhalter "${logger}" durch den Namen der Logging-Klasse, "${callsite}" durch die aufrufende Methode und schließlich "${message}" als die eigentliche Protokollmeldung ersetzt.

Der Zusatz "${exception:Format=tostring}" regelt, dass Exceptions inklusive der Detailmeldungen erfasst werden. Der Platzhalter "${newline}" schließlich, fügt einen Zeilenumbruch am Ende der Meldung ein.

Verwendung

Im Programm selbst, greift man auf NLog beispielsweise so zu:

Public Logger as NLog.Logger = NLog.LogManager.GetCurrentClassLogger
...
Me.Logger.Info("Dies ist ein Test")
Me.Logger.Warn(ex.Message)

Ergänzung vom 21.01.2011:

Hier noch ein Beispiel für eine ziemlich umfassende Protokollausgabe, inklusiv Ausgabe auf die Konsolenanwendung:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
	<targets>
		<target name="file" xsi:type="File" layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring}" fileName="${basedir}/${windows-identity:domain=false}.log"/>
		<target name="console" xsi:type="Console" layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring}"/>
	</targets>
 
	<rules>
		<logger name="*" minlevel="Debug" writeTo="file"/>
		<logger name="*" minlevel="Info" writeTo="console"/>	
	</rules>
 
</nlog>

Ergänzung vom 17.02.2011:

Noch ein Beispiel mit einem Formular-Target:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
	<targets>
		<target xsi:type="FormControl" name="FormControlTarget" layout="${longdate} ${logger} ${message} ${exception:Format=tostring}" append="Boolean" controlName="txtLog" formName="frmMain" />
	</targets>
 
	<rules>
		<logger name="*" minlevel="Debug" writeTo="FormControlTarget"/>	
	</rules>
 
</nlog>

VB .NET: MSI Pakete automatisch installieren

Dieses kleine Modul installiert alle MSI-Pakete, die im aktuellen Programmverzeichnis gefunden werden im "Quiet"-Modus, d. h. ohne irgendwelche Nachfragen.

Module modMain
 
  Sub Main()
    Console.WriteLine("Installiere MSI Pakete...")
    Try
      RunAll()
    Catch ex As Exception
      Console.WriteLine("Fehler: " & ex.Message)
      Console.WriteLine("Bitte drücken Sie eine beliebige Taste zum Beenden.")
      Console.ReadLine()
    End Try
    Console.WriteLine("Installation abgeschlossen.")
  End Sub
 
  ''' <summary>
  ''' Nach MSI-Paketen im aktuellen Verzeichnis (dieser EXE) suchen und alle MSI-Pakete
  ''' nacheinander ausführen.
  ''' </summary>
  ''' <remarks></remarks>
  Public Sub RunAll()
 
    Dim lstFiles As Collections.ObjectModel.ReadOnlyCollection(Of String) = _
    My.Computer.FileSystem.GetFiles(Environment.CurrentDirectory, FileIO.SearchOption.SearchTopLevelOnly, "*.MSI")
 
    For Each fn As String In lstFiles
      ExecuteAndWait(fn, "/quiet")
    Next
 
  End Sub
 
  ''' <summary>
  ''' Einen Prozess starten und auf dessen Ende warten
  ''' </summary>
  ''' <param name="ProcessPath">Name der Datei die ausgeführt werden soll</param>
  ''' <param name="Arguments">Optionale Parameter</param>
  ''' <remarks></remarks>
  Public Sub ExecuteAndWait(ByVal ProcessPath As String, Optional ByVal Arguments As String = "")
 
    Dim proc As System.Diagnostics.Process
 
    Try
      proc = New System.Diagnostics.Process()
      proc.StartInfo.FileName = ProcessPath
      proc.StartInfo.Arguments = Arguments
      proc.StartInfo.WindowStyle = ProcessWindowStyle.Normal
      proc.Start()
      proc.WaitForExit()
      proc.Close()
    Catch ex As Exception
      MsgBox(String.Format("Prozess {0} konnte nicht gestartet werden. Fehler: {1}.", ProcessPath, ex.Message))
    End Try
 
  End Sub
 
End Module

HowTo: VB .NET Barcodes und Reports

Wir haben in den letzten Tagen nach einer Möglichkeit gesucht, in Crystal Reports Barcodes anzuzeigen, und zwar so, dass man sie nachher mit einem Barcode Scanner auch wieder einlesen kann.

Um dies zu tun habe ich nach einer kostenlosen Open-Source Variante gesucht.

Nach unzähligen Versuchen fand ich dann endlich das Barcode Rendering Framework ZEN auf Codeplex.com.

Das Framework kann über Verwendung der drei DLLs als Verweis recht simpel eingebunden werden. Es beherrscht folgende Varianten:

  • Code 11 (mit oder ohne Prüfsumme)
  • Code 25 standard/interleaved (mit oder ohne Prüfsumme)
  • Code 39 (mit oder ohne Prüfsumme)
  • Code 93 (nur mit Prüfsumme)
  • Code 128 (nur mit Prüfsumme)
  • Code EAN 13 (nur mit Prüfsumme)
  • Code EAN 8 (nur mit Prüfsumme)
  • Code PDF417 2D (mit Prüfsumme)

Im Prinzip wird im Code unten folgendes gemacht:

  • Barcode Bild erstellen und als temporäre Datei abspeichern
  • Das abgespeicherte Bild in ein Dataset laden, damit wir es im Report anzeigen können. Das Dataset hat eine Tabelle Namens dtImage mit eben diesen Spalten.

Das Dataset, das der Report verwendet, besitzt nur zwei Spalten. Eine namens ImageFilename (vom Typ String) welche den Dateinamen des temporären Barcode Bildes enthält, und eine namens Image (vom Typ base64Binary bzw. System.Byte()), die das Bild selbst enthält.

Public Sub ShowRepDemo()
 
    ' Barcode-Bild erstellen und als temporäre Datei abspeichern
    Dim tmpfile As String
    tmpfile = My.Computer.FileSystem.GetTempFileName & ".bmp"
    Zen.Barcode.BarcodeDrawFactory.Code128WithChecksum.Draw("Dies ist ein Barcode 128 Test", 30).Save(tmpfile)
 
    Dim ds As New Dataset
 
    ' Barcode Bild in Datatable schreiben
    ds.dtImage.AdddtImageRow(tmpfile, Nothing)
 
    ' Image binär in Datatable schreiben
    For index As Integer = 0 To ds.Tables(0).Rows.Count - 1
      If Not String.IsNullOrEmpty(ds.Tables(0).Rows(index).Item("ImagePath").ToString) Then
        LoadImage(ds.dtImage.Rows(index), "Image", ds.dtImage.Rows(index).Item("ImagePath").ToString)
      End If
    Next
 
    ' Report öffnen
    Dim rptdoc As New repDemo
    rptdoc.SetDataSource(ds.dtImage)
 
    Dim rv As New CrystalDecisions.Windows.Forms.CrystalReportViewer
    rv.Dock = DockStyle.Fill
    Me.Controls.Add(rv)
    rv.ReportSource = rptdoc
 
  End Sub
 
  ' Bild in DataTable schreiben
  Private Sub LoadImage(ByVal objDataRow As DataRow, ByVal strImageField As String, ByVal FilePath As String)
    Try
      Dim fs As System.IO.FileStream = New System.IO.FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)
      Dim Image() As Byte = New Byte(fs.Length) {}
      fs.Read(Image, 0, CType(fs.Length, Integer))
      fs.Close()
      objDataRow(strImageField) = Image
    Catch ex As Exception
      MsgBox(ex.Message)
    End Try
 
end Sub