Send-MailMessage: E-Mails versenden mit PowerShell


    Tags: ,

    Ohne Auswahl eines Zeichensatzes werden Umlaute und Sonderzeichen verstümmeltSeit der Version 2.0 besitzt PowerShell ein eigenes Cmdlet zum Versand von E-Mails. Es ist relativ einfach, ihm die Elemente einer Nachricht als Parameter zu übergeben und sich interaktiv am SMTP-Server zu authentifizieren. Trickreicher wird die Sache jedoch, wenn man Mails ohne Intervention eines Benutzers aus einem Script versenden möchte.

    Seit der Einführung des Cmdlets Send-MailMessage enthält PowerShell ein spracheigenes Mittel zum Versenden von E-Mails, so dass dafür keine .NET-Klassen explizit angesprochen werden müssen, wie dies in PowerShell 1.0 noch nötig war. Der alte Kommandoprozessor cmd.exe bietet für diese Aufgabe überhaupt keine eigenen Möglichkeiten, dort muss man auf externe Programme wie blat.exe zurückgreifen.

    Parameter für alle Elemente einer Nachricht

    Der Aufruf von Send-MailMessage erfolgt mit all jenen Parametern, die man von einem Mail-Client erwartet:

    Send-MailMessage [-To] <string[]> [-Subject] <string> -From <string> [[-Body] <string>] [[-SmtpServer] <string>] [-Attachments <string[]>] [-Bcc <string[]>] [-BodyAsHtml] [-Cc <string[]>] [-Credential <PSCredential>] [-DeliveryNotificationOption {None | OnSuccess | OnFailure | Delay | Never}] [-Encoding <Encoding>] [-Priority {Normal | Low | High}] [-UseSsl] <CommonParameters>]

    Meistens wird man jedoch zum einen nicht alle Angaben benötigen, zum anderen sie nicht samt und sonders als Parameter eingeben. Je nach Anwendungsfall kann der Inhalt der Mail beispielsweise aus einer Log-Datei stammen, die man mit Hilfe anderer PowerShell-Cmdlets analysiert, oder man bestimmt die Anhänge, indem man den Inhalt eines Verzeichnisses ausliest.

    Minimal-Aufruf von Send-MailMessage

    Ein einfacher Aufruf, der alle nötigen Angaben als Parameter übermittelt, könnte so aussehen:

    Send-MailMessage -to "billg@contoso.com" -from "PowerShell <ps@fabrikam.de>" -Subject "Test" -body "Test für Send-MailMessage"

    Damit dieser Befehl erfolgreich sein kann, muss man zuvor den SMTP-Server als Vorgabewert in der Variable $PSEmailServer gespeichert haben, also nach dem Muster

    $PSEmailServer = "smtp.fabrikam.de"

    Andernfalls ist es notwendig, dass man den Ausgangs-Server über den Parameter -SmtpServer bestimmt. Wenn man öfter Mails auf diesem Weg versendet, dann empfiehlt es sich, $PSEmailServer schon im PowerShell-Profil mit dem Standard-SMTP-Server vorzubelegen, so dass man sich die manuelle Eingabe sparen kann.

    Interaktive Anmeldung am SMTP-Server

    Das Cmdlet würde im obigen Beispiel versuchen, sich gegenüber dem SMTP-Server mit den Windows-Anmeldedaten des aktuellen Benutzers zu authentifizieren. Wenn das jedoch nicht möglich ist, dann muss man zusätzlich ein PSCredential-Objekt für die Anmeldung mitliefern (es sei denn, der SMTP-Server verlangt keine Authentifizierung).

    Reicht eine interaktive Anmeldung am SMTP-Server, dann ist der Mail-Versand mit PowerShell recht einfach.

    Die einfachste Variante dafür ist, den Benutzernamen zusammen mit dem Parameter -Credential zu übergeben. PowerShell öffnet dann einen Anmeldedialog, in den man sein Passwort eintippen kann:

    Send-MailMessage -to "billg@contoso.com" -from "PowerShell <ps@fabrikam.de>" -Subject "Test" -body "Test für Send-MailMessage" -Credential "MailUser"

    Nach erfolgreicher Authentifizierung geht die Nachricht hinaus und die Aufgabe ist erledigt.

    SMTP-Authentifizierung ohne Benutzereingriff

    Diese interaktive Authentifizierung ist indes nicht ideal, wenn ein Script unter bestimmten Bedingungen selbständig Mails verschicken soll. Für diesen Fall muss man einen Weg finden, um die Anmeldung zu automatisieren.

    Die in anderen Umgebungen praktizierte unsichere Methode, Passwörter an Send-MailMessage im Klartext zu übergeben, lässt PowerShell nicht zu. Andererseits ist es aber auch nicht möglich, PSCredential-Objekte komplett in einer Datei zu speichern, um sie von dort bei Bedarf einzulesen.

    Aus diesem Grund muss man das erforderliche PSCredential-Objekt vor dem Versand einer Mail erzeugen, indem man dafür einen Benutzernamen angibt und das Passwort aus einer Datei einliest, in der man es zuvor verschlüsselt gespeichert hat. Letzteres lässt sich auf folgende Weise bewerkstelligen:

    (Get-Credential).password | ConvertFrom-SecureString > MailPW.txt

    Das Cmdlet Get-Credential fordert den Benutzer dazu auf, seinen Namen und sein Passwort einzugeben. Es erzeugt daraus ein PSCredential-Objekt, aus dem der obige Befehl dann wieder das Kennwort als SecureString extrahiert und dieses verschlüsselt in der Datei MailPW.txt abspeichert.

    PSCredential-Objekt vor dem Mail-Versand erzeugen

    Vor dem Aufruf von Send-MailMessage erzeugt man das erforderliche PSCredential-Objekt, indem man für den betreffenden User das Passwort aus dieser Datei ausliest und wieder zurück in einen SecureString verwandelt:

    $pw = Get-Content .\MailPW.txt | ConvertTo-SecureString

    $cred = New-Object System.Management.Automation.PSCredential "MailUser", $pw

    Die Variable $cred kann man anschließend Send-MailMessage über den Parameter -Credential mitgeben, um den Benutzer zu authentifizieren:

    Send-MailMessage -Credential $cred -to "billg@contoso.com" -from "PowerShell <ps@fabrikam.de>" -Subject "Test" -body "Test für Send-MailMessage"

    Auch für diese Prozedur könnte man überlegen, ob man das PSCredential-Objekt bereits beim Start von PowerShell über das Profil anlegt.

    Umlaute und Sonderzeichen bewahren

    Eine dritte Hürde schließlich stellt der verwendete Zeichensatz dar. Lässt man den Parameter -Encoding weg, dann lautet die Voreinstellung auf ASCII. Enthält der Betreff oder die Nachricht Umlaute oder andere Sonderzeichen, dann kommen diese verstümmelt beim Empfänger an.

    Daher wird man in der Regel UTF8 verwenden, um den Inhalt unbeschadet zu übertragen. Dies teilt man dem Cmdlet mit, indem man den Parameter

    -encoding ([System.Text.Encoding]::UTF8)

    hinzufügt.

    Anhänge über eine Pipe einfügen

    Möchte man Anhänge nicht als seine Liste von Dateinamen für den Parameter -Attachments eintippen, sondern beispielsweise aus dem Inhalt eines Verzeichnisses auslesen, dann kann man sie über eine Pipe an Send-MailMessage schicken:

    Get-ChildItem f*.jpg | Send-MailMessage -Credential $cred -to "billg@contoso.com" -from "PowerShell <ps@fabrikam.de>" -Subject "Test" -body "Test für Send-MailMessage" -encoding ([System.Text.Encoding]::UTF8)

    Send-MailMessage bietet noch einige weitere Optionen, wobei die hier gezeigten Beispiele die meisten Fälle abdecken sollten. Zu erwähnen wären vor allem noch -BodyAsHtml und -UseSSL, wenn die man eine HTML-Mail verschickt bzw. den SMTP-Server über eine SSL-Verbindung ansteuert.

    10 Kommentare

    Bild von Stefan Rehwald
    10. Juni 2013 - 20:11

    Hallo,

    ich beschäftige mich gerade intensiv mit Get-Credential und alles was dazu gehört.
    Nun stellt sich mir eine große Frage, sicher liegt das Passwort in der Text-Datei?

    Das Passwort wurde mittels
    (Get-Credential).password | ConvertFrom-SecureString > .\MailPW.txt
    verschlüsselt.
    Allerdings lässt sich dieses spielend wieder entschlüsseln mit folgendem Code:
    #Einlesen des verschüsselten Passwortes
    $pw = Get-Content D:\MailPW.txt | ConvertTo-SecureString
    #PSCredential-Objekt erzeugen
    $ShowPWD = New-Object System.Management.Automation.PsCredential("MailUser",$pw)
    # Passwort entschlüsselt anzeigen lassen
    $ShowPWD.GetNetworkCredential().password

    Gibt es eine sicherere Methode um Passwörter zu verschlüsseln und diese dann im Script zu verwenden?

    Viele Grüße und danke für den Artikel
    Stefan Rehwald

    Bild von Wolfgang Sommergut
    10. Juni 2013 - 21:18

    Ich denke, die Antwort gibt der letzte Absatz in diesem Beitrag über das Management von Credentials in PowerShell.

    Bild von Stefan Rehwald
    10. Juni 2013 - 21:37

    Danke, das ist eine wertvolle Information. Vielen vielen Dank. Make my day :)

    Bild von Gustav Sommer
    30. März 2014 - 8:46

    Lässt man die Sicherheit außen vor:
    Das Passwort vom MailUser z.B. p@ssword123! könnte man in PowerShell 3.0 hart codieren:
    > $pw ConvertTo-SecureString -String p@ssword123! -AsPlainText -Force
    > $cred = New-Object System.Management.Automation.PSCredential "MailUser", $pw

    Bild von Mike
    Mike sagt:
    13. März 2016 - 23:10

    Hi Herr Sommer,

    ich drehe mich schon tagelang im Kreis und versuche vergeblich in meiner Batch die $cred Variable zu füllen und an send-mailmessage zu übergeben.
    Bin dabei auf den Eintrag hier gestossen.
    Die Sicherheit stelle ich jetzt mal hinten an wäre froh die Mail würde erstmal automatisch rausgehen ohnen manuelle Eingabe von User und PW.
    Das funktioniert soweit aber bei der Automatik scheitere ich.

    So sieht mein Versuch aus mit dem Versuch $cred zu erzeugen.

    @echo off
    set SMTP=smtp.live.com
    set SUBJECT=Erfolgsmeldung DASI
    set BODY=Datensicherung war erfolgreich.
    set FROM=xxx@hotmail.de
    set TO=xxx@yahoo.de
    set port=25
    echo Die EMail wird verschickt
    powershell $pw ConvertTo-SecureString -String "$Passwort$" -AsPlainText -Force
    powershell $cred = New-Object System.Management.Automation.PSCredential "xxx@hotmail.de", $pw
    powershell -ExecutionPolicy Unrestricted -c 'Send-MailMessage -To %TO% -Subject %SUBJECT% -Body %BODY% -SmtpServer %SMTP% -From %FROM% -UseSSL -Credential $cred'
    pause

    Können Sie mir dabei helfen, der nächste Schritt wäre dann natürlich die sicherere Variante :-))))

    Danke im voraus

    Gruss Mike

    Bild von Harald Dietl
    Harald Dietl sagt:
    15. April 2014 - 10:18

    Wie kann ich die Attachments filtern? Ich würde gerne mir über ein automatisiertes Skript Log-Dateien senden lassen. Wie kann ich angeben, das immer nur das aktuellste Log versendet wird?

    Danke schon mal im Voraus für die Hilfe und auch für den Artikel.

    Bild von Daniel Häring
    Daniel Häring sagt:
    9. April 2016 - 17:27

    Hallo,

    vielen Dank für den Artikel er hat mir sehr geholfen.
    ich schreibe gerade ein Script um automatisch Log-Dateien zu verschicken.

    Allerdings bekomme ich immer einen Time-Out Error wenn ich die Email mit den Anhängen verschicke. Es handelt sich dabei um gezippte Log-Dateien mit 8MB Größe. Gibt es eine Möglichkeit den Time-out zu verlängern?

    Danke schonmal

    Bild von Cox
    Cox sagt:
    12. Mai 2016 - 10:32

    Hallo,
    ich würde das Script gern in der Variante verwenden, dass die E-Mail-Adresse, an die die Nachricht geht, jeweils aus einem extensionAttribute ausgelesen wird. Ich benötige das, weil dort eine Alternativ-Adresse hinterlegt ist. Geht das?

    Viele Grüße
    Cox

    Bild von Wolfgang Sommergut
    12. Mai 2016 - 14:43

    Das sollte sich mit Get-ADUser -Filter {Bedingung} -Properties extensionAttribute(x) machen lassen.