先日Twitterで @akashi_keirin さんが下記ツイートをされていました。
Outlookで、開いたメールを閉じた後、表題に特定の文字列があるメールなら指定したフォルダに移動する、というマクロが書きたかった。
MailItemオブジェクトのCloseイベントでできると浅はかにも思ったが、当然失敗した。
悔しい😭
#VBA— チン☆テクラ【不純】 (@akashi_keirin) April 12, 2021
どのような理由で上記のような処理をされたいのか、ツイートを追ってみると、“メール内容を確認した後に特定のメールを隔離したい”とのこと。
四月から立場が変わって、ある種のメールがばかすか来るようになったんですよ。
確認した後、そいつらだけ特別なフォルダに隔離できたらいいなあ、と思いまして😅
くのへ氏なら、たぶん鼻くそほじくりながらでもやっちゃいそうですけどね😅
まだまだOutlookは超絶初心者です😭— チン☆テクラ【不純】 (@akashi_keirin) April 12, 2021
なるほど。
Outlookには仕分けルール機能があるので、指定した条件を満たす受信メッセージ、または送信メッセージに対して特定の処理を行えますが、この機能では“メールを読み終えたタイミング”で処理を実行することはできません。
非常に面白そうなテーマ(と言ってしまっては微妙かもしれませんが…)だったので、早速試してみることにしました。
Inspector_Closeイベントでは処理できず
最初は「メールを閉じたときの処理?じゃあInspectorオブジェクトのCloseイベントあたりにメールを移動する処理を書いておけば大丈夫余裕でしょ?」だなんて高を括っていたのですが、実際に動かしてみると「実行時エラー ‘-2147221240 (80040108)’ : このアイテムのプロパティとメソッドは、このイベント プロシージャ内では使用できません。」が発生。
どうやらInspectorオブジェクトのCloseイベントが発生したタイミングでは、まだメールが完全に閉じていないために移動処理ができないようです。
同様に、このイベントよりも前のタイミングで発生するMailItemオブジェクトのCloseイベントでも処理することはできません。
ここまで試した段階で、「これ以上無理にやらなくても良いかな?」とも思ったのですが、折角なのでもう少し突っ込んでみたいと思います。
setTimeoutによる非同期処理
『Closeイベントが発生したタイミングではメールがまだ閉じ切っていないために移動できない』のであれば、処理するタイミングをちょっとずらしてあげれば良い話です。
このようなとき、私は非同期で処理を実行します。
具体的には、下記のようなコードです。
(動作確認は32ビット版、64ビット版の両方で行いました。)
ThisOutlookSession
Option Explicit Private m_clsInspectors As clsInspectors Private Sub Application_Startup() Set m_clsInspectors = New clsInspectors End Sub
clsInspectors(クラスモジュール)
Option Explicit Private WithEvents m_Inspectors As Outlook.Inspectors Private WithEvents m_MailItem As Outlook.MailItem Private m_doc As Object Private Sub Class_Initialize() Set m_Inspectors = Application.Inspectors End Sub Private Sub Class_Terminate() Set m_MailItem = Nothing Set m_Inspectors = Nothing End Sub Private Sub m_Inspectors_NewInspector(ByVal Inspector As Inspector) If TypeOf Inspector.CurrentItem Is Outlook.MailItem Then Set m_MailItem = Inspector.CurrentItem End If End Sub Private Sub m_MailItem_Close(Cancel As Boolean) Dim src Const AttributeName As String = "TimeoutHandler" Const TargetFolderName As String = "移動先フォルダ" Set m_doc = CreateObject("htmlfile") src = "document.documentElement.getAttribute('" & AttributeName & "').MoveMailItem('" & m_MailItem.EntryID & "', '" & TargetFolderName & "');" m_doc.DocumentElement.setAttribute AttributeName, Me m_doc.parentWindow.setTimeout src, 1000 End Sub Public Sub MoveMailItem(ByVal MailItemEntryID As String, _ ByVal TargetFolderName As String) 'メールを指定したフォルダに移動 On Error Resume Next With Application.Session .GetItemFromID(MailItemEntryID).Move .GetDefaultFolder(olFolderInbox).Folders.Item(TargetFolderName) End With End Sub
実行画面
ポイントとなるのはMailItem_Closeイベントの処理ですね。
HTMLWindow2オブジェクトのsetTimeoutメソッドを使って、時間差でメール移動処理(MoveMailItem)を呼び出しています。
この方法は、私のような中途半端な者ではなく、本当に深い知識と経験を持った方々に10年以上前に教えていただいたコードで、たまに自分用のマクロに組み込むこともあるのですが、正直、まだこれ動くの・・・!?と思うコードです。
MSHTML(IE)を使ったトリッキーな処理ですので、いつ動かなくなってもおかしくはないでしょう。
近しい処理としては、いみひと氏が「VBAからJScriptのfunctionオブジェクトを使用する方法」(下記サイト)を紹介されていますが、このようなハックを編み出されるのは大変素晴らしく、また惜しげもなく公開されていて、誠に有難い限りです。
おわりに
さて、上で書いた通り、半ば無理やり“メールを閉じたときに指定したフォルダに移動する処理”を実行してみたのですが、個人的には、やはり仕分けルールを使ってメール受信時に振り分け等の処理を行った方が無難だと思います🙂
マクロを使わなくても様々な条件を指定できますし、「スクリプトを実行する」でマクロを組み合わせることもできます(下記記事参照)。
もしくは、クイックアクセスツールバーにメールを振り分けるマクロを登録しておいて、イベントを使った自動処理ではなく、任意のタイミングでマクロを実行しても良いかもしれません。
いずれにしても、今回のテーマは非常に興味深く、試行錯誤している間もとても楽しめました。
@akashi_keirin さん、ありがとうございました!
2021/4/18 追記:
そういえば、複数のメールが同時に開かれた場合については全く考慮していませんでした。
その場合は恐らく下記ページにあるような処理が必要になるのではないかと思うのですが、私の方では実際に試していないので分かりません。申し訳ない!💦
2021/4/22 追記:
@furyutei 氏から下記情報をいただきました。
すっきりとまとまっていて、使い勝手が良さそうなコードです。
こちらにヒントを得て
[VBA] Application.OnTimeを使用せずにプロシージャ/メソッドを指定時間経過後にコールバックする方法https://t.co/s1f39Gn9b6
を試しに。
OnTimeだとオブジェクト(クラスモジュールのインスタンス)のメソッドがコールバックできないので不便だったのが一応出来るように。 https://t.co/FwawfiCZNv— 風柳 (@furyutei) April 21, 2021
こちら↓については、たしかに実行できなくなっていました。
数日前までは実行できていたので、Windows Updateでmshtml.dllあたりが更新されたのかもしれません。
Outlookの方は4月22日時点でもコードを直すことなく実行できているので、HtmlDocumentのdocumentModeあたりに違いがあるのではないかと思うのですが、私の方では未確認です💦
なお、https://t.co/4LgaQKBmtx
の clsInspectors の Sub m_MailItem_Close 中と同等の処理(DocumentElement.setAttributeでオブジェクト(Me)をセット)を Excel VBA でやろうとするとエラーが出てしまいました。このあたり Outlook VBA とは違いがあるんですかね?(そちらはさわったことがない) pic.twitter.com/eXusncMfiT
— 風柳 (@furyutei) April 21, 2021
この記事へのコメントはありません。