次の方法で共有


Outlookを用いたメール送信処理が安定しません

質問

2019年3月29日金曜日 10:21

状況とご質問内容

VBAで作成したマクロをタスクスケジューラ起動で実行し、集計結果をメール送信しています。
メール送信にOutlookオブジェクトを採用しています。
しかし、Outlookを用いたメール送信が安定せず、
原因やどうしたら解消できるのか、調査方法などがわからず八方ふさがりの状況です。
何か少しでも思い当たることがございましたらご教授お願いできますでしょうか?

発生している問題・エラーメッセージ

下記VBAソースコードに記載しているfunction処理を行った際に処理が硬直します。
※エラーとしてGotoされず処理が停止します。サーバーのフリーズ、マクロの処理落ちは発生していません。
CreateObject(Application.Outlook)で失敗していると仮説を立て検;していますが、
Outlookが下記の状態になる状況を再現ができず検;ができていません。

この状態で手動によるOutlookの起動をした際に
エラーメッセージ 「同時に実行できる Outlook のバージョンは 1 つだけです。Outlook の別のバージョンが実行中でないかどうか確認してください。」が表示されます。
※類;質問を確認しましたが既存の下記質問とは事象が異なるようです。
Outlook を開くと「申し訳ございませ...

再現性

本事象は毎回必ず発生するわけではなく、発生する場合としない場合があります。
(単品で再実行すると発生せず再現ができておりません)

マクロの本数が1本の場合は発生せず、対象のマクロを増やして実行するようになってから発生し始めました。
*増えたことにより、順次処理ではなく並列処理になるケースもあります。

該当のソースコード

Function sendMail(infoDict As Object) As Long
On Error GoTo Err_Trap
    sendMail = 0

    Dim subject, mailBody, credit As String '変数設定:件名、メール本文、クレジット、添付
    Dim outlookObj As Object        'Outlookで使用するオブジェクト生成
    Dim mailItemObj As Object       'Outlookで使用するオブジェクト生成
    Dim olNs As Object              'Outlook.NameSpace
    Dim myFolder As Object          'Outlook.Folder

    Set outlookObj = CreateObject("Outlook.Application")
    Set mailItemObj = outlookObj.CreateItem(0) 'olMailItem  0   メールを操作するオブジェクト
    Set olNs = outlookObj.GetNamespace("MAPI")
    Set myFolder = olNs.GetDefaultFolder(6) 'olFolderTasks
    myFolder.Display

    mailItemObj.BodyFormat = 2                'html
    mailItemObj.To = infoDict(ML_KEY_TO)      'to宛先をセット
    mailItemObj.CC = infoDict(ML_KEY_CC)      'cc宛先をセット
    mailItemObj.subject = infoDict(ML_KEY_SBJ)   '件名をセット
    mailItemObj.Body = infoDict(ML_KEY_BODY) 'メール本文 改行 改行 クレジット


    mailItemObj.Send
    Sleep 60000            'wait 60s

    outlookObj.Quit
    Sleep 2000
    Set outlookObj = Nothing
    Set mailItemObj = Nothing
    Set olNs = Nothing

    Set myFolder = Nothing

    Exit Function
Err_Trap:
    outlookObj.Quit
    Sleep 2000
    Set outlookObj = Nothing
    Set mailItemObj = Nothing
    sendMail = -1
End Function

参考:仮説と検;結果

  • 仮説1 CreateObject(Application.Outlook)で失敗
    課題の事象で手動でのoutlookが起動ができないため、本処理で停止している可能性を考えました。
    しかし、再現ができない状況です。

  • 検;結果1 myfolder.Display処理で失敗
    上記処理を切り出し、単純にfor文で繰り返す検;をした際にエラー(-2147467259)が発生
    →エラーとなっているが、課題の事象とは別なので却下

  • 検;結果2 outlookObj.Quit処理で失敗
    検;結果1より相互的な起因で起きた事象と推測し本調査を行った際にエラー(462)が発生
    並列で検;した際にOutlookプロセスが他の処理でQuitされている場合に462エラーとなる
    →エラーとなっているが、課題の事象とは別なので却下

環境情報

Windows7 32bit Enterprise
VBA7.0
Outlook2013

すべての返信 (5)

2019年6月6日木曜日 6:30

田中二真さん、こんにちは。

全く同じという訳にはいかないものの、同じようなスクリプトを作って、for~nextで5回連続してfunctionを呼び出すようにして、再現試験したところ、5回中1~2回、エラートラップの中でエラーが生じて止まってしまう、という状況が起きました。

エラートラップが動作する原因を探るためDescriptionをDebug.Printしたところ、「リモート サーバーがないか、使用できる状態ではありません。」とか、又は「オブジェクトが必要です。」というものでした。また、そのエラーによるエラートラップ中のエラーは、outlookObj.Quitのところで生じておりました。

そこで、処理が止まってしまうエラー中のエラーがoutlookObj.Quitのところで生じており、その場合、outlookObjが立ち上がっていない状態のようでしたので、エラートラップ中の 「outlookObj.Quit」を次のように書き換えたところ、エラートラップ中のエラーで処理が止まることはなくなりました。

  If Not outlookObj Is Nothing Then
    outlookObj.Quit
  End If

ただし、エラートラップが機能するエラーの発生は残っており、再現試験におけるそのエラーは、outlookObjが立ち上がっていなかったことによるものでした。もともとの提示されたコードにおけるエラーも同じ原因だとすれば、上記の修正だけではエラーが生じたときにメールを送りそびれたままとなってしまいます。そこで、それを回避するためには、次の2つのやり方が有るかと思います。

(1) 最初の「Set outlookObj = CreateObject("Outlook.Application")」の前後をdo~loopで囲んで、outlookObjの立ち上がりを確認して、outlookObjがきちんと立ち上がるまでSet文を繰り返す、という方策が考えられます。

(2) 最初の「Set outlookObj = CreateObject("Outlook.Application")」をこのfunctionを呼び出す側のメインのプログラムに記して、引数でoutlookObjをこのfunctionに渡すことにより、functionの処理のたびにoutlookObjを立ち上げたり終わらせたり、という動作を回避する、という方策も考えられます。  以上、ご参考まで。


2019年6月6日木曜日 12:12

田中二真さん、こんばんわ
>この状態で手動によるOutlookの起動をした際にエラーメッセージ 「同時に実行できる Outlook のバージョンは 1 つだけです。Outlook の別のバージョンが実行中でないかどうか確認してください。」が表示されます。
>マクロの本数が1本の場合は発生せず、対象のマクロを増やして実行するようになってから発生し始めました。
*増えたことにより、順次処理ではなく並列処理になるケースもあります。
については、こちらが参考になるかもしれません。(該当しない所は読み飛ばしてください)

OUTLOOK VBA オブジェクトまとめ
https://qiita.com/Q11Q/items/ac14d96c00e707de5d13

Outlook VBAは、Excelから少し触ったことがある程度なので、アドバイスには至らないと思いますが、
私は、通信系のソフトなどを書く時に気を付ける事として、sleep 等を使う必要がどうしてもある場合は、
結果に対し、期待する結果が得られているかを検;するようにしております。
また、ご存知の通り、以下 https://qiita.com/Q11Q/items/ac14d96c00e707de5d13より抜粋

一度に使用可能な Outlook のインスタンスは 1 つ。
Outlook が起動していない場合に、New キーワードまたは CreateObject 関数によって Outlook の新しい非表示インスタンスが 1 つ作成される。
Outlook のインスタンスが既に実行されている場合に New キーワードまたは CreateObject 関数を使用すると、実行中のインスタンスへの参照が返される。

プロの方から見るとおかしいのかも知れませんが、
私が過去書いたものは、インスタンスを何度も作成しない構成にしています。
この場合、infoDictを呼ぶ形かなと思います。
参考にはならないと思いますが、興味があり、また正解を知りたいと思ったので返信いたしました。


2019年6月8日土曜日 1:04

Tkumi_Qさん、こんにちは。横から失;します。

「この場合、infoDictを呼ぶ形かなと思います。」とのことなのですが、インターネット上で探しても良く分かりません。もう少し説明を補足していただけると助かるのですが、いかがでしょうか。


2019年6月8日土曜日 6:24

KokemomoYamamomoさん こんにちは、
失;しました。表現を間違え(悪く)、誤解を与えてしまったようです。時間を使わせてしまったようで、すみません。

mailItemObjを設定するところ(前)で、infoDict を call  や functionで呼んでくる方が良いかと
または、infoDict を配列で入れ ループさせるとかして
処理の度、Outlook のインスタンスを作成、破棄するなどには問題(先の情報)があるので、
function内で    Set outlookObj = CreateObject("Outlook.Application")などはせず
mailItemObj.設定などとエラー、カウント処理に限定したほうが良いのではと

すみません、よく読むと
KokemomoYamamomoさんが既に上げています 2)の方法に類;するかなと思います。

>興味があり、また正解を知りたい は、Function処理の考え方などが自分と違ったので勉強の為


2019年6月8日土曜日 8:19

Takumi_Qさん、こんにちは。

解説、ありがとうございました。私の理解不足で、お手間を掛けさせてしまって、誠に申し訳ありません。理解しました。本当にありがとうございました。