Outlookのメールの保存を半自動化してみた

Outlookで受信したメールをPCのローカルドライブに保存しているのですが、手作業で100通近く処理するととても面倒なので、マクロで自動化してみました。

試した環境は下記です。

  • Outlook 2013
  • Windows 8.1

目次

  1. 保存の方針
  2. コード
  3. 試してみた
  4. まとめ

保存の方針

「これは」というメールは、PCのローカルドライブに保存するようにしています。本文をPDF変換して添付ファイルもろともZipファイルにしていたのですが、件数が多くなると手間がばかになりません。というわけで、この作業を半自動化したいと思います。

メールをOutlook 2013からフォルダにドロップするとMSG形式のファイルとしてコピーされるので、最初はMSGファイルをC#でパースしようと考えました。MSDNにMSG形式の仕様書が公開されているのですが、ソフトウェア素人の私には解読不能でした。ということで、C#でのパースはあきらめて、OutlookのVisual Basic for Application (VBA)でいきたいと思います。

Outlookで受け取るメールはテキスト形式とHTML形式とRTF形式の3種類があるのですが、本文と書式(見た目)と添付ファイルが残っていれば良いので、MSG形式のファイルと本文をテキストに変換したファイルと添付ファイルをZIPファイルにまとめて保存するようにします。

また、ファイル名は下記にするようにしました。

[送信日][タグ(複数)][送信元アドレス]_タイトル.zip

コード

マクロは下記のようにしてみました。

#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
#End If

Sub SaveAsZip()


    On Error GoTo errorEx

    Dim openedItem As Outlook.Inspector
    Dim targetItem As Object

    Set openedItem = Application.ActiveInspector
    If Not TypeName(openedItem) = "Nothing" Then
        ' メールのメタ情報を取得する
        Set targetItem = openedItem.CurrentItem

        Dim title As String
        title = targetItem.Subject
        title = Trim(title)
        title = Replace(title, "\", "_")
        title = Replace(title, "/", "_")
        title = Replace(title, "?", "_")
        title = Replace(title, ":", "_")
        title = Replace(title, "*", "_")
        title = Replace(title, """", "_")
        title = Replace(title, ">", "_")
        title = Replace(title, "<", "_")
        title = Replace(title, "|", "_")

        Dim senderAddress As String
        senderAddress = "[" & targetItem.SenderEmailAddress & "]"

        Dim sendDate As Date
        sendDate = targetItem.SentOn
        Dim sendDateString As String
        sendDateString = "[" & Format(sendDate, "YYYYMMDD HHNN") & "]"

        Dim mailCategolies As String
        mailCategories = targetItem.Categories
        mailCategory = Split(mailCategories, ",")
        mailCategories = ""
        Dim str As Variant
        For Each str In mailCategory
            mailCategories = mailCategories & "[" & Trim(str) & "]"
        Next str

        ' 保存するフォルダ名を設定する
        Dim pathName As String
        pathName = "c:\mail\"

        ' 保存するファイル名を設定する
        Dim filename As String
        filename = sendDateString & mailCategories & senderAddress & "_" & title
        filename = LeftB(filename, 220 - LenB(pathName))
        filename = Left(filename, Len(filename))

        ' ファイルアクセス用のオブジェクトの生成
        Dim fso As Object
        Set fso = CreateObject("Scripting.FileSystemObject")
        Set app = CreateObject("Shell.Application")

        ' Zipファイル名を設定する
        Dim zipPath As String
        zipPath = pathName & filename & ".zip"
        If fso.FileExists(zipPath) = True Then
            fso.DeleteFile zipPath
        End If

        ' Zipファイルの生成
        With fso.CreateTextFile(zipPath, True)
            .Write "PK" & Chr(5) & Chr(6) & String(18, 0)
            .Close
        End With

        ' 圧縮フォルダのオブジェクトの生成
        Set zipFolder = app.NameSpace(fso.GetAbsolutePathName(zipPath))

        ' メール本文を保存して、圧縮フォルダに移動する
        Dim itemCount As Integer
        targetItem.SaveAs pathName & filename & ".txt", olTXT
        targetItem.SaveAs pathName & filename & ".msg", olMSG
        itemCount = zipFolder.Items().Count
        zipFolder.MoveHere (pathName & filename & ".txt")
        Do Until (itemCount < zipFolder.Items().Count)
            Sleep 100
        Loop
        itemCount = zipFolder.Items().Count
        zipFolder.MoveHere (pathName & filename & ".msg")
        Do Until (itemCount < zipFolder.Items().Count)
            Sleep 100
        Loop

        ' 添付ファイルを保存して、圧縮フォルダに移動する
        Dim attachedFiles As Attachments
        Set attachedFiles = targetItem.Attachments
        Dim filesName As String
        filesName = ""
        Dim I As Integer
        For I = 1 To targetItem.Attachments.Count
            itemCount = zipFolder.Items().Count
            targetItem.Attachments.Item(I).SaveAsFile pathName & targetItem.Attachments.Item(I).DisplayName
            zipFolder.MoveHere (pathName & targetItem.Attachments.Item(I).DisplayName)
            filesName = filesName & targetItem.Attachments.Item(I).DisplayName & "|"
            Do Until (itemCount < zipFolder.Items().Count)
                Sleep 100
            Loop
        Next
    Else
        MsgBox "メールを開いてからマクロを実行してください。"
    End If
    Exit Sub

errorEx:
    MsgBox "エラーが発生しました。"
End Sub

処理の流れはコメントに書いてある通りなのですが、ちょっと説明を書いておきます。

pathNameという変数に保存するフォルダのパスを設定します。メールを保存する場所応じて書き換え必要です。

その下のファイル名設定の箇所ですが、LeftB()とLeft()を使って文字列を短くしています。これは、ファイルシステムのパス名とファイル名の文字数制限に合わせるためのものです。ファイル名にメールのタイトルを使うのですが、メールのタイトルには1バイト文字と2バイト文字が混じります。すると、LeftB()でバイト単位で抜き出すと抜き出した文字列の最後が2バイト文字の最初の1バイトだけみたいな状態になることがあります。Len関数で文字数をカウントするとこの半分だけの文字は1文字とはカウントしないようなので、Left()にLen()の結果を指定して抜き出すことで、末尾に半分だけの文字がある場合はそれを削除します。

MoveHereの後ろにDoループがあります。これは、ファイルを保存する場合に生じる問題を回避するためのものです。MoveHereという関数が、前のファイルの圧縮が終わる前に次のファイルを圧縮しようとするようで、圧縮するファイルが複数あるとエラーになるのです。これを回避するために、ウェイトの目的でDoループを入れています。流れとしては、下記の感じです。

  1. Zipファイルの中のファイル数を記録しておく。
  2. MoveHereでファイルを圧縮フォルダに移動する命令を発行する。
  3. Zipファイルの中のファイル数を調べて、記録しておいたファイル数と比較する。ファイル数が増えていたらループを抜ける。

「BASIC → インタープリタ → 命令を順番に実行する」 と短絡的に考えていたので、てっきり必ず順々に命令が実行されるものと思っていました。

試してみた

早速試してみます。

下図の様なテストメールを準備しました。「Outlook」と「WordPress」という分類が付いていて、テキストファイルとExcelファイルが添付されています。

160326-1-01

「開発」タブの「コード」のリボンの「マクロ」メニューから、マクロを実行します。そうすると、下図の様なZipファイルが生成されます。

160326-1-02

Zipファイルの中には、下図の様にMSGファイルとテキストファイルと添付されていたファイルが入っています。

160326-1-03

まとめ

上記のコードにはエラーが起きたときの処理が書いてありませんので、もし参考にされる場合は注意してください。特に上記のコードでは、既存のファイルを強制的に上書きします。例えば、同名のファイルが存在したら処理を止めるとか、ファイル名に秒単位の時刻を含めるとか、アレンジしてください。

久しぶりにVBっぽいプログラムを書いたら、ものすごい違和感が・・・。すっかりC#に慣れてしまったようです。それに、Visual StudioのIDEがとても素晴らしい物だということがよくわかりました。インテリセンスさんの居ない開発環境はつらい。

公開日