【入門編】【上級者向け】マルチスレッドを擬似的に実現する!長時間処理を別プロセスへ逃がす設計パターン – Outlook VBA解析バイブル

スポンサーリンク

こんにちは!Outlook VBAの沼…いや、広大でエキサイティングな自動化の世界へようこそ。

マクロの記録から一歩踏み出し、「業務を完全にハックしたい」と願うあなたへ。今回は、Outlook VBAの限界を軽々と突破し、周囲をあっと言わせる【上級者向け】マルチスレッドを擬似的に実現する!長時間処理を別プロセスへ逃がす設計パターンを伝授します。

「受信メールを検知して、重いPDFの解析や外部DBへの書き込みを行ったら、Outlook全体がフリーズして固まってしまった……」
そんな絶望を味わったことはありませんか?

大丈夫。ここをクリアすれば、あなたのOutlook VBAスキルは間違いなくプロの領域に達します。優しく、そして本質的なところまで深く、一緒に紐解いていきましょう!

—

なぜOutlookは「重い処理」でフリーズするのか?

まず、Outlook VBAの宿命を知ることから始めましょう。

私たちが普段使っているOutlookのイベント(`NewMailEx` など)は、「メインスレッド(単一の作業レーン)」の上で動いています。つまり、受信イベントの中で「重い処理」を始めると、Outlookは他のすべての作業(メールの閲覧、返信、スケジュールの確認など)をストップさせ、あなたにこう訴えかけます。

> 「今、一生懸命仕事中だから触らないで!」(応答なし)

プログラミング初学者が最初に直면する最大の壁がこれです。C#やJavaのようなモダンな言語であれば「マルチスレッド」を使って裏で処理を逃がせますが、Outlook VBAには「スレッド」という概念そのものが存在しません。

じゃあ、諦めるしかないのか?
いいえ、伝説のエンジニアたちは考えました。「VBA自身が処理するのをやめ、別の子分(プロセス)に仕事丸投げして、自分はすぐにお仕事モード(待受状態)に戻ればいいじゃないか」と。

これこそが、今回紹介する「別プロセスへの逃がし(非同期化)アーキテクチャ」です。

—

概念図:本体を止めない「影武者」の召喚

イメージはこうです。

[Outlook本体 (メインスレッド)]
│
├─ 1. メール受信!
├─ 2. 「おい、あとの重い処理は頼んだ!」 (VBScriptを裏で起動) ──┐
│ ▼
└─ 3. 【即座に解放】次のメール受信待ちへ 🚀 [WScript.Shell]
│
(完全に独立した別プロセス)
│
▼
・重いPDF解析
・重いDB書き込み
・外部API連携 (完了!)

Outlookは「言伝(ことづて)」をしたら秒速で仕事に復帰します。実際の重たい労働は、OSの片隅でこっそり起動した「VBScript(WScript.Shell)」という名の影武者がすべて引き受けてくれるわけです。

—

実装コード:一歩先を行くプロの作法

それでは、実際にこのアーキテクチャを実装してみましょう。
今回は「メールを受信したら、その件名と本文を外部の重い処理(今回はシミュレーションとしてテキストファイルへの書き込みと一定時間のスリープ)に渡す」コードを記述します。

ステップ1:受信をフックする `ThisOutlookSession`

まずはOutlook側のメインエンジンです。`NewMailEx` イベントを使い、メールを受け取ったら瞬時にVBScriptをキックします。

‘ ==============================================================================
‘ 【ThisOutlookSession モジュールに記述】
‘ ==============================================================================
Option Explicit

Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
On Error GoTo ErrorHandler

‘ NewMailExは複数のIDがカンマ区切りで届くことがあるため分割する
Dim arrEntries() As String
arrEntries = Split(EntryIDCollection, “,”)

Dim i As Long
For i = LBound(arrEntries) To UBound(arrEntries)
Dim objItem As Object
Set objItem = Application.Session.GetItemFromID(arrEntries(i))

‘ 届いたアイテムがメールであるか判定
If TypeOf objItem Is Outlook.MailItem Then
Dim mail As Outlook.MailItem
Set mail = objItem

‘ 【重要】Outlook本体を止めないため、ここで重い処理をせず
‘ データをVBScriptに渡し、別プロセスとして爆誕させる!
Call InvokeAsyncProcess(mail.Subject, mail.Body)

End If
Next i

Exit Sub

ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
End Sub

‘ ==============================================================================
‘ 外部プロセス(VBScript)を非同期で非ブロック起動するプロシージャ
‘ ==============================================================================
Private Sub InvokeAsyncProcess(ByVal mailSubject As String, ByVal mailBody As String)
Dim wsh As Object
Set wsh = CreateObject(“WScript.Shell”)

‘ 一時的な作業用テキストファイルにデータを退避(引数の文字化けやエスケープ対策)
Dim tempFilePath As String
tempFilePath = Environ(“TEMP”) & “\outlook_async_work.txt”

Dim fileNum As Integer
fileNum = FreeFile
Open tempFilePath For Output As #fileNum
Print #fileNum, mailSubject
Print #fileNum, mailBody
Close #fileNum

‘ VBScriptのパスを生成
Dim vbsPath As String
vbsPath = Environ(“TEMP”) & “\heavy_process.vbs”

‘ 実行するVBScriptの中身を動的に生成する
Call CreateWorkerVBScript(vbsPath, tempFilePath)

‘ 【核心】WScript.Shellの Run メソッド
‘ 第2引数: 0 = ウィンドウ非表示 (WindowStyle)
‘ 第3引数: False = 非同期実行 (WaitOnReturn = False -> ここがキモ!)
wsh.Run “wscript.exe “”” & vbsPath & “”””, 0, False

Set wsh = Nothing
End Sub

‘ ==============================================================================
‘ ワーカーとなるVBScriptファイルを動的に作成するヘルパー関数
‘ ==============================================================================
Private Sub CreateWorkerVBScript(ByVal vbsPath As String, ByVal dataPath As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

Dim ts As Object
Set ts = fso.CreateTextFile(vbsPath, True)

‘ 別プロセスで動くVBScriptのコードを書き出す
ts.WriteLine “Option Explicit”
ts.WriteLine “Dim fso, ts, subject, body”
ts.WriteLine “Set fso = CreateObject(“”Scripting.FileSystemObject””)”
‘ データの読み込み
ts.WriteLine “If fso.FileExists(“”” & dataPath & “””) Then”
ts.WriteLine ” Set ts = fso.OpenTextFile(“”” & dataPath & “””, 1)”
ts.WriteLine ” subject = ts.ReadLine”
ts.WriteLine ” body = ts.ReadAll”
ts.WriteLine ” ts.Close”
‘ 【ここで「重い処理」をシミュレート】(例: 3秒間スリープ、外部ログへ書き込みなど)
ts.WriteLine ” WScript.Sleep 3000″
ts.WriteLine ” Dim logPath”
ts.WriteLine ” logPath = fso.GetSpecialFolder(2) & “”\heavy_process_result.log”””
ts.WriteLine ” Set ts = fso.OpenTextFile(logPath, 8, True)”
ts.WriteLine ” ts.WriteLine Now & “” – 処理完了: “” & subject”
ts.WriteLine ” ts.Close”
ts.WriteLine “End If”

ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub

—

コードのここがスゴい!エンジニアの解説

1. `WScript.Shell.Run` の第3引数 `False` の威力
ここが今回の最大の肝です。`Run “path”, 0, False` と指定することで、VBScriptが裏で実行されるのを「待たずに(Non-blocking)」VBA側のコードが即座に次の行へ進みます。これによりOutlookが一切フリーズしなくなります。
2. 一時ファイル経由でのデータ受け渡し
VBAから別プロセスに文字列を渡す際、コマンドライン引数として直接渡すと、改行コードや特殊文字でバグる原因になります。一度 `%TEMP%` 領域にテキストとして書き出し、それを別プロセスに読み込ませる手法は、実務で使える非常に堅牢な設計パターンです。
3. ウィンドウ非表示 (`0`) によるUXの向上
黒いコマンドプロンプトやVBSの実行画面がパッとポップアップして消えると、ユーザーは「何が起きたんだ?」と不安になります。第2引数を `0` にすることで、完全にバックグラウンドで静かに処理を完結させます。

—

陥りやすい罠と注意点(ここをクリアすれば完璧!)

実務でこのパターンを導入する際、初心者がハマりがちなポイントを先回りして解説しておきます。

  • セキュリティソフトの誤検知

一時フォルダ(`%TEMP%`)に `.vbs` ファイルを動的に生成して実行するため、社内のセキュリティソフト(EDRなど)によっては「不審なスクリプトの実行」としてブロックされる場合があります。本格的なエンタープライズ環境で運用する場合は、VBScriptではなく、あらかじめコンパイルされた小さな `.exe`(C#などで作成)を呼び出す形にリファクタリングするのがベストです。

  • デバッグの難しさ

別プロセスで動くため、VBAの通常のブレークポイント(F9)が効きません。もしVBScript側でエラーが起きている場合は、ワーカーVBScript内の `WScript.Sleep` を一時的に外したり、エラーログを出力するコード (`On Error Resume Next` を剥がすなど) を仕込んで挙動を追うようにしてください。

—

おわりに

お疲れ様でした!今回は、Outlook VBAの最大の弱点である「シングルスレッドの呪縛」を、VBScriptとの合わせ技(プロセス分離)によって見事に解決するアーキテクチャを解説しました。

「マクロを実行すると画面が固まる」というフェーズは、今日で終わりです。
この設計パターンを手に入れたあなたなら、どれほど重いファイル処理やAPI連携が絡むメール自動化案件が来ても、涼しい顔でスマートにシステムを構築できるはずです。

ここをクリアしたあなたなら、もうOutlook VBAの基本はバッチリです!
ぜひ、あなたの業務の現場でこのテクニックを試してみてくださいね。それでは、また次の知見でお会いしましょう!

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