E-Mails mit SSL/TLS-Unterstützung mittels Powershell versenden


Die Powershell Funktion Send-MailMessage https://msdn.microsoft.com/de-de/powershell/reference/5.1/microsoft.powershell.utility/send-mailmessage hat so ihre Tücken und hilft leider nicht, wenn es Probleme beim E-Mailversand gibt. Teilweise reagiert sie auch nicht wie erwartet. In der heutigen Zeit, vor allem wegen der TLS-Pflicht reagiert sie oft zickig in Bezug auf den UseSSL Parameter. Aus diesem Grund hier eine einfache Methode E-Mails mittels TLS per Powershell versenden zu können.

Wer Probleme beim E-Mailversand hat, der schaue sich https://newyear2006.wordpress.com/2015/08/23/probleme-mit-transportverschlsselung-zertifikaten-oder-passwrtern-bei-smtp-servern-mittels-powershell-berprfen/ an, dort wird im Detail beschrieben, wie dieses Skript funktioniert.

Gleichzeitig benutzt diese Routine nicht mehr die Test-NetConnection Funktion, da diese komischerweise nicht mehr den Socket öffnet. Statt dessen wird der benötigte TCP-Socket zu beginn manuell geöffnet. Der Rest sind nur Anpassungen für diese Änderung.

Man muss nur die im ersten Block aufgeführten Parameter eintragen und schon kann die Spammerei losgehen…

Hier der ganze Code:

$smtp = "smtp.web.de"
$smtpPort = 587
$user = "meinewebdeadresse@web.de"
# $userPass kann auch leer gesetzt werden, dann wird nachgefragt
$userPass = "meinPasswort"
$Subject = "Test Betreff"
$Body = "Test Body"
$from = "meinewebdeadresse@web.de"
$to = "meinewebdeadresse@web.de"
$xmailer = "Powershell v1"

$c=New-Object System.Net.Sockets.TcpClient
$c.Connect($smtp, $smtpPort)

# Antwort vom SMTP-Server holen und ausgeben
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# Begrüßung durchführen
$buffer=[System.Text.Encoding]::ASCII.GetBytes("EHLO $Env:Computername`r`n")
$c.Client.Send($buffer)
Sleep -Seconds 1

# Antwort vom SMTP-Server holen
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# STARTTLS-Anfrage starten
$buffer=[System.Text.Encoding]::ASCII.GetBytes("STARTTLS`r`n")
$c.Client.Send($buffer)
Sleep -Seconds 1

# Bei dieser Antwort sollte 220 rauskommen,
# sonst gibt es ein Problem
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# für den weiteren Gang ist entscheidend, dass man einen Stream braucht
# vom Socket erhält man einen Stream, durch Weitergabe an NetworkStream-Klasse
$n=New-Object System.Net.Sockets.NetworkStream $c.Client

# über den Networkstream kann man nun SslStream aktivieren:
$ssl = New-Object System.Net.Security.SslStream $n, $true

# nun kann man den eigentlichen TLS Handshake starten, dazu muss nochmal der Host angegeben werden
$ssl.AuthenticateAsClient($smtp)

# für die weitere Kommunikation richtet man sich am besten zusätzliche Streams ein:
$reader = New-Object System.IO.StreamReader $ssl
$writer = New-Object System.IO.StreamWriter $ssl
$writer.AutoFlush = $true

# damit wird die weitere Kommunikation einfacher und das Spiel beginnt von vorne:
$writer.WriteLine("EHLO $Env:Computername")
while ($reader.Peek() -gt -1) {
    $reader.ReadLine()
}

$writer.WriteLine("AUTH LOGIN")
$reader.ReadLine()

[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("VXNlcm5hbWU6"))

If ($UserPass) {
  $Spass = ConvertTo-SecureString -String $UserPass -AsPlainText -Force
  $cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $SPass } else {
  $cred = Get-Credential –UserName $User –Message "Bitte Passwort eingeben"
}
$pass = $cred.GetNetworkCredential().Password
$userBytes = [System.Text.Encoding]::ASCII.GetBytes($user)
$userBase64 = [System.Convert]::ToBase64String($userBytes)
$writer.WriteLine($userBase64)
$reader.ReadLine()

$passBytes = [System.Text.Encoding]::ASCII.GetBytes($pass)
$passBase64 = [System.Convert]::ToBase64String($passBytes)
$writer.WriteLine($passBase64)
$reader.ReadLine()

$writer.WriteLine("MAIL FROM:$from")
$writer.WriteLine("RCPT TO:$to")
$writer.WriteLine("DATA")
#$writer.WriteLine("")
$writer.WriteLine("From: $from")
$writer.WriteLine("To: $to")
$writer.WriteLine("Date: {0}", (Get-Date).ToString("ddd, d M y H:m:s z"))
$writer.WriteLine("Subject: $Subject")
$writer.WriteLine("X-Mailer: $xmailer")
$writer.WriteLine("")
$writer.WriteLine("$Body")
$writer.WriteLine(".")
$writer.WriteLine("")
$writer.WriteLine("")
$reader.ReadLine()

# Streams und Sockets schließen
$ssl.Close()
$n.Close()
$c.Close()

10 Antworten to “E-Mails mit SSL/TLS-Unterstützung mittels Powershell versenden”

  1. Quirel Says:

    gutes Tool zum Mailserver und Webserver testen: https://de.ssl-tools.net/

  2. hannes Says:

    Vielen Dank !

    Deine Lösung funktioniert auf Anhieb perfekt !

    lg
    hannes

  3. Thomas Says:

    According to RFC 5321 the TO: and FROM: fields must not be spaced between key and value. Correct: TO:destinationadress@web.de, FROM:myaddress@web.de
    Regards, Thomas

  4. Jürgen Says:

    Guten Tag, ich habe den Code per Copy/Paste übernommen, meine persönlichen Daten eingetragen (FROM, TO…) und laufen lassen.

    Es läuft sauber durch, allerdings mailt es nicht – an der Testadresse kommt nichts an.

    Die letzte Meldung lautet „250 Requested mail Action okay, completed“

    Ich kenne mich mit PowerShell noch nicht so aus, trotzdem sehe ich nicht, wo der eigentliche Sendevorgang erfolgt. Und was bedeutet der Hinweis auf RFC 5321 (ja, ich habe es gegoogelt…)? Könnte es sein, dass der Wert 354 (Start Mail Input) fehlt – tut er bei mir jedenfalls?

    Ich danke für Hilfe.

    Jürgen

    • Quirel Says:

      RFC5321 ist nicht das Problem.

      Es ist eher so, dass der Versand der E-Mail nicht perfekt ist und nicht alle Rückgaben der Kommunikation ausgegeben, bzw. erst versetzt ausgegeben werden.

      Man sollte in obigem Script den Part mit dem Versand durch diese Zeilen ersetzen:

      $writer.WriteLine(„MAIL FROM:$from“)
      $reader.ReadLine()
      $writer.WriteLine(„RCPT TO:$to“)
      $reader.ReadLine()
      $writer.WriteLine(„DATA“)
      $reader.ReadLine()
      #$writer.WriteLine(„“)
      $writer.WriteLine(„From: $from“)
      $writer.WriteLine(„To: $to“)
      $writer.WriteLine(„Subject: $Subject“)
      $writer.WriteLine(„X-Mailer: $xmailer“)
      $writer.WriteLine(„“)
      $writer.WriteLine(„$Body“)
      $writer.WriteLine(„.“)
      $writer.WriteLine(„“)
      #$writer.WriteLine(„“)
      $reader.ReadLine()

      D.h. nach MAIL, RCPT und DATA werden explizit die Rückgaben des Servers ausgelesen und ausgegeben. So kann man schneller feststellen, ob es ein Problem mit den Adressen gibt. Die Ausgabe „250 Requested mail Action okay, completed“ kommt zum Beispiel von „MAIL FROM:“.

      Dadurch stößt man z. B. auf sowas:
      554-Transaction failed
      In diesem Fall sollte man nochmal
      $reader.ReadLine()
      ausführen und erhält unter Umständen
      554 For explanation visit https://postmaster.web.de/error-messages?ip=192.88.33.55&c=hi

      D.h. unter https://postmaster.web.de/error-messages findet man weitere Infos unter anderem, dass im Header bei WEB.DE keine Datumsfelder erlaubt sind!

      Also sollte man diese Zeile im Ursprungsscript auskommentieren:
      $writer.WriteLine(„Date: {0}“, (Get-Date).ToString(„ddd, d M y H:m:s z“))
      und schon geht die E-Mail bei web.de durch.

      Übrigens sollte man nach dem E-Mailversand noch ein

      $writer.WriteLine(„QUIT“)
      $reader.ReadLine()

      einfügen, um die Verbindung ordnungsgemäß zu beenden.

  5. newyear2006 Says:

    Komfortable Variante:

    https://evotec.xyz/mailozaurr-new-mail-toolkit-smtp-imap-pop3-with-support-for-oauth-2-0-and-graphapi-for-powershell/

  6. Chris Says:

    Hallo,
    vielen Dank für dein Script. Das ist echt einfacher als per MailKit und ich würde das gerne grundsätzlich einsetzen.
    Ich kann das sehr gut gebrauchen um Systemmeldungen abzuschicken als Ersatz für Send-MailMessage.

    Aber nun habe ich ein Problem. Ich muss die Mails zu einem Server schicken über den ich nur per VPN zugreifen kann. Dabei kommt folgender RemoteCertificateNameMismatch Fehler:


    Line 54 | $ssl.AuthenticateAsClient($smtp)
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Exception calling „AuthenticateAsClient“ with „1“ argument(s): „The remote certificate is
    | invalid according to the validation procedure: RemoteCertificateNameMismatch“

    ich kann an der Umgebung nix ändern, wäre es möglich einfach die Zertifikatsprüfung zu deaktivieren? Wie geht das?

    Und noch als vielen Dank dass du das Script teilst.

    • newyear2006 Says:

      Probier mal

      add-type @“
      using System.Net;
      using System.Security.Cryptography.X509Certificates;
      public class TrustAllCertsPolicy : ICertificatePolicy {
      public bool CheckValidationResult(
      ServicePoint srvPoint, X509Certificate certificate,
      WebRequest request, int certificateProblem) {
      return true;
      }
      }
      „@
      [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

      Bei Powershell SSL/TLS-Zertifikate-Prüfung einfach ignorieren

      Vielleicht hilft das bereits weiter.

  7. Chris Says:

    Hallo,
    bei mir klappt das Verbinden schon nicht, wohl weil das TCP-Connect irgendwie versucht per IPv6 zuzugreifen?

    Line |
    20 | $c.Connect($smtp, $smtpPort)
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Exception calling „Connect“ with „2“ argument(s): „Connection refused [::ffff:10.7.70.30]:587“

    wobei es egal ist ob ich die IP oder den DNS des Servers übergebe.
    Wie kann ich IPv4 erzwingen?

Hinterlasse einen Kommentar