【VBS・VBA共通】PowerShellを利用しSTARTTLS(587番ポート)でメール送信

Tips
スポンサーリンク

今回の記事では、VBScriptやVBAからPowerShellを呼び出し、STARTTLS(587番ポート)を使用してメールを送信する方法とサンプルコードを紹介します。

併せて、VBScriptやVBAからメールを送る際の一般的な実装方法の紹介や、「STARTTLS」の簡単な解説、「Send-MailMessage」コマンドレットの説明も紹介していきます。

 

VBScriptやVBAからメールを送信する一般的な方法

VBScriptは古いスクリプト言語であり、VBScriptの動作環境であるWSH(Windows Script Host)自体の機能としては、メールを送信する仕組みが用意されていません。
また、言語仕様が類似しているVBAも同様です。

VBScriptやVBAからメールを送信する一般的な方法としては以下があります。

  • CDO.Messageオブジェクトを使用して送信する。
  • BASP21などの外部ライブラリを使用して送信する。
  • Outlookなどのメーラーを操作して送信する。

最も一般的なのは、CDO(Collaboration Data Objects)を利用したメール送信方式です。
このライブラリは古くからあり、Windows環境であれば、サーバー用OS、PC用OSに関わらず、インストールをせずに使用可能です。

当ブログでもVBScriptでメール送信方法としてCDOを使用した実装方法の記事を公開しています。

良ければご一読ください。

非常に手軽にメール送信処理が実装できることから、VBScriptやVBAからメールを送信する場合の最も一般的な実装方式です。

ただし、CDOのメール送信機能には大きな問題があります。
それは以下です。

「STARTTLS」に対応していない。

CDOは古いライブラリであり、開発や更新は大昔に終了しています。
smtpで使用するポート番号では、25、465(SMTP over SSL/TLS)、587(STARTTLS)などがありますが、CDOでは25と465までしか対応していません。
※具体的には587番ポートを指定してメールを送ることはできますが、STARTTLSに対応していません。

最近のSMTPサーバー側では、セキュリティ上の制限から、25番ポートを受け付けてくれないケースも多く、587番ポートを使わせることが一般的です。

よって、CDOでは対応できないケースが非常に増えてきています。

後は、「BASP21」などの外部ライブラリを利用してメールを送信する方法もあります。
例に挙げた「BASP21」は非常に有名なフリーのライブラリですが、このライブラリを実行環境にインストールすることで、VBScriptからメール送信処理を行えるようになります。

ただし、ライブラリのインストールが必要になることから、業務システムが稼働するサーバー上で使用したい場合などは僅かですがリスクもあります。

また、VBScriptやVBAからOutlookなどのメーラーソフトを操作してメール送信することもできますが、その実行環境にメーラーソフトがインストールされていないと使えません。
こちらも手軽さに欠けます。

 

PowerShellの「Send-MailMessage」コマンドレット

VBScriptと同様に、Windows標準のスクリプト言語である「PowerShell」では「Send-MailMessage」コマンドレットを利用することで、メール送信が行えます。

PowerShellの「Send-MailMessage」コマンドレットであれば、当然587番ポートも使用可能です。

PowerShellの「Send-MailMessage」コマンドレットの詳しい説明はMicrosoftのLearnをご確認ください。

今回の記事では、VBScriptやVBAからPowerShellを呼び出して、むりやりSTARTTLSを使用してメールを送信する具体的な実装方法を紹介していきます。

 

「Send-MailMessage」コマンドレットは非推奨

上記にリンクを掲載した「Send-MailMessage」コマンドレットに関するMicrosoftのページでも記載されていますが、今回紹介する「Send-MailMessage」コマンドレットはMicrosoftも使用を推奨していません。
リンク先の記載内容では以下のように書かれています。

コマンドレットは Send-MailMessage 廃止されました。 このコマンドレットは、SMTP サーバーへのセキュリティで保護された接続を保証するものではありません。 PowerShell ですぐに置き換える機能はありませんが、 は使用 Send-MailMessageしないことをお勧めします。 詳細については、「 プラットフォーム互換性に関するメモ DE0005」を参照してください。

廃止されたと記載されていますが、実際には使用可能です。
最新の.NET Coreのバージョンまで確認はしていませんが、.NET Framework 4.8 の環境までは今のところ使えるようです。

PowerShellの「Send-MailMessage」コマンドレットは、.NET Frameworkの「SmtpClientクラス(名前空間:System.Net.Mail)」を利用しており、それが廃止対象のため、このコマンドレットも廃止と案内されています。

しかし、実際には、.NETに標準で組み込まれたMicrosoft製のメール送信機能において、SmtpClientクラスの代替機能は存在しないため、現在のCDOのように、廃止扱いされたあとも永続的に使い続けられるのでは?と思っています。

ただし、これはあくまで個人的な推測でしかないので、「Send-MailMessage」コマンドレットは、現在推奨されていないことを理解したうえで利用してください。

 

【補足】CDO.Messageでは587番ポート自体は使用可能

ここでsmtpにおけるメール送信時のプロトコルやポート番号について混乱している人も少なくなさそうなので、CDO.Messageと587番ポートについて補足説明をしておきます。

一般的に587番ポートと言えば、「STARTTLS」が使われます。
メールを送信するプロトコルのsmtpでは、元々25番ポートが使用され、通信はサーバーに平文で送られます。
よって、その通信を傍受することで、メールの内容はすべて自由に閲覧できてしまいます。

「STARTTLS」はsmtpの通信プロトコルを拡張し、通信を暗号化してセキュアに利用できるようにする仕組みです。

詳しくは以下のWikipediaの記事をご参照ください。

STARTTLSを大まかに説明すると以下です。

  • メール送信時にサーバーとの通信を暗号化してくれる。
  • 587番ポートを使用する。
  • 通信開始時には平文でやり取りし、サーバーが暗号化に対応していれば暗号化通信に切り替える。

ただし、ネットでCDO.Messageを利用したサンプルコードを検索すると、587番ポートを指定したコードを掲載している記事も多く存在します。
しかし、CDO.MessageはSTARTTLSに対応していない旨の記事も複数見かけます。
587番ポートと言えば「STARTTLS」です。

ここで以下のように疑問に思うはずです。

中の人
なかの人

で、結局CDO.Messageは587番ポートを使用できるの?

結論で言えばできます。

CDO.Messageが対応していないのはあくまで「STARTTLS」です。

具体的には、STARTTLSの通信開始時に発生する平文での通信は可能だが、その後メールサーバー側が暗号化通信に対応していても、CDO.Message側が暗号化通信に切り替えることができないという意味で非対応です。

尚、メールサーバー側では、STARTTLS機能を有効化する際に、暗号化通信を強制するか否かの設定が可能です。

よって、暗号化通信を強制しなければ、CDO.Messageから587番ポートを指定して平文のまま通信をし続けることができるため、CDO.Messageでメールの送信まで行えます。
もしメールサーバー側で暗号化通信を強制する設定になっていれば、CDO.Messageでは暗号化通信に切り替えることができないため、メールを送ることはできません。

587番ポートを使用してメールを送ったからと言って、必ずSTARTTLSで通信が暗号化される訳ではありません。
ポート番号と通信の暗号化は分けて認識しておくことがポイントです。

上記は混乱しがちなポイントなので、よく理解しておいていただくと良いかと思います。

STARTTLSでは、通信開始時は平文でやり取りをして、smtpサーバーが暗号化通信に対応していることを確認できてから、以降を暗号化通信に切り替えます。smtpサーバーで暗号化通信に対応していなければ、平文のまま通信を継続することができてsmtpの通信がブロックされることがないため、後述するSMTPSよりも広く利用されています。

後、465番ポート(SMTPS)はCDO.Messageに対応しています。
SMTPSでは、通信の最初からSSL又はTLSを使い通信を暗号化します。
STARTTLSのように、途中から暗号化通信に切り替えるようなことはしません。
よって、25番ポートと同様にCDO.Messageで通信をおこなうことが可能です。

SMTPSでは、サーバー側が暗号化通信に対応していなければ確実にメールを送れないことになり、メール送信側は通信先のsmtpサーバーが暗号化通信に対応していることを予め把握している必要があります。

このように、25番ポート(通常のsmtp)、465番ポート(SMTPS)、587番ポート(STARTTLS)は個々に挙動や仕組みが異なります。
何となくでも知っておくと良いかと思います。

 

VBSやVBAからPowerShellの「Send-MailMessage」を実行するサンプルコード

当項では、VBScriptやVBAからPowerShellの「Send-MailMessage」コマンドレットを実行するサンプルコードを紹介していきます。

当コードをコピペし、メール送信で必要になる情報を書き換えてもらえれば動作する想定です。
尚、当コードはVBScriptを前提に記述しており、VBAで使用する場合は、サンプルのFunctionプロシージャを呼び出すSubプロシージャを作ってもらう必要があるのと、Functionプロシージャ内のコメントアウト部分を一部有効化していただく必要があります。

  Option Explicit

      'PowerShellを呼び出して、その終了コードをメッセージボックスに表示します。
      Msgbox MailSend_PowerShell()

  Function MailSend_PowerShell()

      Dim objWshShell
      Dim objWshExec
      Dim psPolicyCngCmd
      Dim psMailSendCmd
      Dim psCmd
      Dim lastCode

      Dim psMailSrv
      Dim psPort
      Dim psEncode
      Dim psFromAddress
      Dim psToAddress
      Dim psSubject
      Dim psBody
      Dim psPswd

          'メール送信で必要な情報を指定します。
          psMailSrv = "'smtp.example.com'"    'smtpサーバのIPアドレス又はホスト名
          psPort = "587"                      'ポート番号
          psEncode = "'UTF8'"                 '文字コード
          psFromAddress = "'user01@example.com'"  '送信元メールアドレス
          psToAddress = "'user02@example.com'"    '送信先メールアドレス
          psPswd = "'xxxxxxxxx'"                  'smtp認証パスワード
          psSubject = "'テストメール'"             '件名
          psBody = """テストメールです。`r`nテスト`r`nテスト"""   '本文

          'PowerShellの実行ポリシーを定義
          psPolicyCngCmd = "Set-ExecutionPolicy RemoteSigned -Scope Process -Force;"
          'PowerShellのコマンドをワンライナーで定義
          psMailSendCmd = "try {$pwd = " & psPswd & " | ConvertTo-SecureString -AsPlainText -Force;$cred = New-Object System.Management.Automation.PSCredential " & psFromAddress & ",$pwd;Send-MailMessage -SmtpServer " & psMailSrv & " -Port " & psPort & " -Encoding " & psEncode & " -To " & psToAddress & " -From " & psFromAddress & " -Subject " & psSubject & " -Body " & psBody & " -Credential $cred;Exit 0;} catch {Exit 1};"
          
          'WshShellオブジェクトを生成します。
          Set objWshShell = CreateObject("WScript.Shell")
          
          'PowerShellに渡すコマンドを結合します。
          psCmd = psPolicyCngCmd & psMailSendCmd

          'PowerShellでコマンドを実行し、その結果を取得します。
          Set objWshExec = objWshShell.Exec("powershell " & psCmd)

          'WshShellオブジェクトのExecしたプロセスが終了するまで待ちます。
          Do While objWshExec.Status = 0
              WScript.Sleep 1000
              'VBAの場合はWScript.Sleepではなく、以下のコードを有効にしてください。
              'Application.Wait Now() + TimeValue("00:00:01")
          Loop
          '終了コードを変数で受け取ります。
          lastCode = objWshExec.ExitCode
          '当Functionの戻り値をセットします。
          MailSend_PowerShell = lastCode

          Set objWshExec = Nothing
          Set objWshShell = Nothing

  End Function

 

サンプルコードの解説

上記で紹介したサンプルコードですが、押さえておいてほしいポイントを幾つか紹介します。

 

改行を含む文字列渡し方に注意

処理冒頭の変数に値をセットする処理のなかで、変数「psBody」だけ値の代入方法が異なります。

psBody = """テストメールです。`r`nテスト`r`nテスト"""

PowerShellにおける文字列の改行(CRLF)では以下のように表現します。

`r`n

これをPowerShellへ認識させつつ、VBScriptのコード上で文字列として扱わせるために、この代入時だけダブルクォーテーション三つで囲っています。

 

実行ポリシーに調整が必要

PowerShellでは、実行時の権限を細かく指定できます。
その権限によっては、上手く動作しないケースもあります。
当記事では以下の様に実行ポリシーを指定しています。

'PowerShellの実行ポリシーを定義
psPolicyCngCmd = "Set-ExecutionPolicy RemoteSigned -Scope Process -Force;"

この実行ポリシーは、実行環境の状況により適切な値が異なります。
詳しくは以下のリンク先にある「Set-ExecutionPolicy」コマンドレットの仕様をご確認ください。

 

「Send-MailMessage」コマンドの解説

変数「psMailSendCmd」でSend-MailMessageコマンドレットを実行する詳細なコマンドを定義しています。
こちらも参考までに解説しておきます。

まず、当サンプルでは、VBScriptからPowerShellに渡すために、ワンライナー(1行)で記述してあります。
PowerShellでは、コマンド間をセミコロン( ; )で繋ぐことで、通常は複数行で記述するコードを同一行内に続けて記述することができます。

今回ワンライナーで1行に収めた記述を複数行に分けて書くと以下になります。

  try
  {
  	$pwd = パスワード文字列 | ConvertTo-SecureString -AsPlainText -Force
  	$cred = New-Object System.Management.Automation.PSCredential 送信元メールアドレス,$pwd
  	Send-MailMessage `
  		-SmtpServer smtpサーバー `
  		-Port ポート番号 `
  		-Encoding 文字コード `
  		-To 送信先メールアドレス `
  		-From 送信元メールアドレス `
  		-Subject 件名 `
  		-Body 本文 `
  		-Credential $cred
  	Exit 0
  }
  catch
  {
  	Exit 1
  }

「Send-MailMessage」コマンドレットでは複数の引数を持ち、こちらも1行で記述すると記述が長くなるため、上記のコードでは見やすくするために、バッククォート( ` )を挟み、コード内の改行をさせています。

PowerShell側のコードではtry-catchでエラー処理を入れており、メール送信が正常終了したら0、何らかの原因で処理に失敗したら1をVBScript側に返すようにしてあります。

また、パスワードの文字列はConvertTo-SecureStringコマンドレットで暗号化したものをSend-MailMessageコマンドレットに渡しています。

 

最後に

今回の記事では、VBScriptやVBAでSTARTTLS(ポート番号:587)を使用してメールを送信する方法を紹介しました。

最近ではスパム対策として、OP25B(Outbound port 25 Blocking)が広く利用されており、異なるネットワーク(異なるISP)内のメールサーバー宛に25番ポートを使用しても通信しようとしても、ISP側でブロックされるケースも増えています。

また、セキュリティの厳しいメールサーバーでは、25番ポートを開放せず、STARTTLSで暗号化通信が必須の制限を適用しているケースも増えています。

今回の記事で紹介した「Send-MailMessage」コマンドレットの使用は推奨されていないことへの留意も必要ですが、どうしてもVBScriptやVBAからSTARTTLSを使用してメールを送らないといけないケースであれば、今回サンプルコードが参考になるのではないかと思います。

今回も記事を読んでいただきましてありがとうございました。
それでは皆さまご機嫌よう!

タイトルとURLをコピーしました