2018年、IBMの長きに渡って提供されてきたNotesがインドの企業に売却されて、事実上の終焉を迎えました。もとより、現代ではGoogle WorkspaceやMicrosoft365といったモダンな企業向けソリューションがある時代に於いて、Notesはあまりにも古い。また、レガシー資産が多数ある事で、移行できずにズルズルと引きずってしまう大企業もありました。

しかし、クローズドなNotesの開発ノウハウはすでに現代的なものではないので、ウェブアプリケーション等に移行し、メール等も国際標準のものへ移行すべきということで、過去にウェブ上の資料を元に作成し改良を加えた「NotesメールをOutlookに移行させるツール」の情報をまとめて置こうと思います。

※Google Workspaceへの移行の場合は、標準搭載のデータ移行サービスを利用してDomino 8.5.2のIMAPから移行が可能のようです。

図:起動してみた様子

今回作成したツール

  • Notes to Outlook v5 - Microsoft Access 64bitで作成しています。

Access VBAにて作成しています。関連リンクにある様々な先人の方々のコードが残されており、これらを元に修正を加えて形にしています。利用する為には、使用するPCにNotesクライアントがインストールされていて、起動させておく必要があります。

Access 2016で動作確認していますので、持っていない人はAccess 2016 Runtimeをインストールしておくと動作させることが可能です。また、今回はOutlookをターゲットにしていますが、以下のエントリーを参考にGmailへインポートも可能です

※こちらのアプリは限定公開となっています

※2023年8月8日、Notesのアプリで使ってるDBのビューデータを引き抜くものを新たにExcelにて作成しました

Google Apps ScriptでGmail APIにてメールデータを変換して追加する【GAS】

VBAでLotus NotesのDB内ビュー全データを引き抜く

作成した理由

Microsoft365移行にともなって、Notesのメールのバックアップファイル類からOutlookへの移行について、情報システム部門からの解答は「出来ないので諦めてくれ」というにべもない解答が返ってきた事がきっかけ。実際にはウェブ上ではすでに多くの変換系のアプリや移行支援サービス等があったにも関わらず。

確かにこれらのサービスは有償で、中には怪しい中華系アプリみたいなものも多く手を出しにくいのも事実。しかし、事務系に於けるメールは色々な過去のやり取りや手法を残していた資産でもあるため、移行要望が強かった為、調べてみたところ、VBAでNotes自身を操作してEML形式に変換出来るのがわかったのでチャレンジして、実際に150名近くのメールボックスの移管に成功しました。

※ウェブのツールの多くはNSFファイルを直接OutlookのPSTというメールボックス形式に変換するものが多いのですが、実際の効果の程は不明です。

また、その後の調査によると、NotesのDBに対して、ODBCドライバ経由で接続させたり、メールだけじゃなく掲示板やカレンダー、タスクといったようなデータも取り出せるようです。

アプリの詳細

アプリケーションの仕様

本アプリケーションは以下のような仕様になっています。

  • サーバ接続ではなくローカルに切り出したNSF形式のバックアップファイルを取り込んで取り出す仕組みです。
  • 取り出したメールはEML形式で1通ずつ出力されます。
  • デスクトップのtempnotesというディレクトリを自動作成してそちらに出力します。
  • アドレス変換機能でNotes上の名称からSMTPのメールアドレスへ変換します。(○○@名前といった独自のアドレスのこと)
  • 100%完璧な出力ではなく90%程度。Notes独自の通知メールであったり、lnk形式のショートカットの添付等についてはスルーする仕様です
  • 変換にあたっておかしな特殊文字については、変換や置き換えを実行しています。
  • メールは基本的には添付ファイル付きの場合は、一度取り出してから再構成しています。
  • フォルダ分けをしていた場合には、それらも忠実に再現するように出力先にフォルダ単位でエクスポートするようになっています。但し、フォルダ名に使えないおかしな特殊文字についてはなるべく変換するようにしています。
  • 事前に受信トレイ直下に「インポート」というフォルダを作成する必要があります。
  • Outlookにアドオンを入れていると起動時にダイアログが表示されてしまい、処理が止まるので予めOutlookを起動してこれらのダイアログを閉じた状態でプログラムを利用する必要があります。
  • メールサブジェクトは30文字でカットするようにしています(異常に長いとエラーになるため)

使い方

起動してVBAが有効な状態であるならば、「Notes to Outlook」というタブが開かれて、いくつかのコマンドがすでに表示されてると思います。これらを順番に実行していく事で、NSFファイルから取り込んで、Outlookの所定のフォルダに順番にインポートしてゆきます。ファイルの数によって処理時間は異なっていくので、大量にある場合には夜に実行するか?昼休みなどに実行すると良いでしょう。

プログラム設定

ここは使わなくても問題ないですが、Notesのメールのユーザ名を通常のメールアドレスに変換する場合に利用します。Notesは通常のSMTPとは異なりユーザ名で送信出来るようになってしまってるので、これらの名称をアドレスに変換してEMLファイルにする場合に利用します。

社員ID、社員名、メールアドレスの3つ入れておくだけです。

送信者アドレスはこのアプリで取り込み対象としてるメインのアカウントのメールアドレスを、プレフィックスにはNotesメール特有のhoge@yournameとなってるhogeの部分を入れて保存をクリックします。

図:氏名とアドレス変換テーブルです

Notesからデータ取り込み

Notesを自動的に起動しますが、キャンセルでOK。バックアップファイルとして切り出しているであろう、メールの塊のNSFファイルを指定します。NSFファイルの中から仕様にしたがって、所属フォルダの情報、メール本文、添付ファイル、メールアドレスなどを取り出して、フォルダ名リストの構築とデータをT_Mailテーブルに格納します。

添付ファイルもAccessの添付ファイル型のフィールドに複数格納してる状態になるので、Accessのファイルサイズの制限である2GBを超えないように注意する必要があります。そこまでメールボックスを割り当ててる企業は早々いないと思いますが。なお別のNSFファイルを取り込むとこれらのテーブルのデータはデリートされて洗い替えで取り込まれるので注意が必要です。

添付ファイル取り出し

この機能はメールを取り込むのではなく、所定のフォルダに添付ファイルだけを一括で取り出す機能になります。前述の作業の後に実行する事で、メールデータから添付ファイルのあるものを発見し、個別のファイルとして出力します。

通常は使わなくても良い機能です。

emlファイル生成

デスクトップのtempnotesというフォルダ内に、T_Mailの内容及びフォルダ名リストに従って、テーブルに取り込んだデータから、Outlookにインポート可能な個別のEML形式のファイルを生成します。Outlookはこの他MSG形式も取り込めるようになってるのですが、本アプリはEML形式でのみ出力します。

この段階ではまだOutlookに取り込んでいませんが、EML形式のファイルはBoxなどのクラウドストレージサービスでも対応してるため、アップロードすると検索インデックスの対象になり、全文検索が可能になるため、あえてOutlookにインポートせずにBoxにアップロードするという手段を取るのも手軽で利便性がアップします。ファイル自体はダブルクリックすると、デフォルトメーラーで直接開かれます。

取得済みデータ一覧

1つ目の手順でNotesのNSFから取り込んだ内容が詰まってるT_Mailテーブルの内容を閲覧可能にしてるだけのフォームです。通常は使わなくても問題ありませんが、どんな内容が取り込まれているのか?また、フィルタして不要なデータは削除してしまうといった事がこのフォーム上で可能です。

EML取り込み

tempnotesに出力されたEMLファイル及び、その中に生成されてるサブフォルダ内のEMLファイルまで走査して、Outlookの受信トレイ直下のインポートディレクトリ内に、インポートを行います。1通ずつオープンしてインポートするのでなかなか時間が掛かる作業です。

インポートにあたりエラーがなければ、取り込んだEMLファイルは順次削除されて、失敗したものだけがtempnotesフォルダに残るようになっています。

ソースコード

これらのコードはその殆どが関連リンク先で公開されてるコードを加工したものになります。VBSのコードをVBAに変換したものも含まれます。主要なコード部分だけを掲載しています。

受信トレイ以外のフォルダ構成を取得する

Public Function allNotesView(filepath As String)
    '変数を宣言
    Dim mailFile As String
    mailFile = filepath
    
    'DB接続用
    Dim dbs As DAO.Database
    Set dbs = CurrentDb
    Dim rs As DAO.Recordset
    Set rs = dbs.OpenRecordset("フォルダ名リスト", dbOpenDynaset)

    Dim session As Object, db As Object
    Dim server As String, notesDB As String
    Dim items As Variant, docs As Variant, views As Variant
    Dim view As Object, doc As Object, item As Object
    Dim i As Long
    
    Set session = CreateObject("Notes.NotesSession")
    server = ""
    notesDB = mailFile
    
    Set db = session.GetDatabase(server, notesDB)
    If Not db.IsOpen Then db.Open
    views = db.views
    For i = LBound(views) To UBound(views)
        'フォルダであったら、処理をする
        If Left(views(i).Name, 1) = "(" Then
            'ただのビューなので処理をしない
        Else
            With rs
                .AddNew
                !フォルダ名 = views(i).Name
                !出力先フォルダ名 = views(i).Name
                !delflg = 0
                .Update
            End With
        End If
    Next i
    
    '終了処理
    Set dbs = Nothing
    Set db = Nothing
    Set session = Nothing
    Set rs = Nothing
    
End Function

NSFからテーブルに取り込む

Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Declare PtrSafe Function GetCurrentProcess Lib "kernel32" () As Long
Declare PtrSafe Function SetPriorityClass Lib "kernel32" (ByVal hProcess As Long, ByVal dwPriorityClass As Long) As Long

'優先順位クラスフラグ
Const HIGH_PRIORITY_CLASS = &H80&
Const IDLE_PRIORITY_CLASS = &H40&
Const NORMAL_PRIORITY_CLASS = &H20&
Const REALTIME_PRIORITY_CLASS = &H100&
'Notes EMBEDDEDOBJECTの種類
Const EMBED_ATTACHMENT = 1454
Const EMBED_OBJECT = 1453
Const EMBED_OBJECTLINK = 1452

Sub getMailSub(view As Object, statusman As String, expfolder As String, Optional subjectCriteria As String)
    'On Error GoTo Err_handler
    'エラーハンドリング用
    Dim result As Variant
    Dim errormsg As Variant

    '個々のメールの内容を取得する
    Dim AcDB As DAO.Database
    Dim items As Variant, docs As Variant
    Dim doc As Object, item As Object, RichTextBody As Object
    Dim i As Long, j As Long
    Dim b As Long
    Dim buf As Variant, attaches As Variant, attach As Object
    Dim useraddress As String
    
    'Access
    Dim rs As DAO.Recordset
    Dim myDate As Date 'strUpdateで重複チェック
    Dim strWhere As String 'Dcountのcriteria
    Dim subjectHitFlag As Boolean 'Subjectが検索対象文字列を含むか否かのフラグ
    Dim mySubject As String
    Dim FSO As New FileSystemObject
    Dim ret As Variant
    Dim bufcnt As Variant
    Dim tempaddress As Variant
    
    Dim destDrive As String
    destDrive = CreateObject("WScript.Shell").SpecialFolders.item("Desktop") & "\tempnotes\"

    Set AcDB = CurrentDb()
    Set rs = AcDB.OpenRecordset("T_mail")
    Set doc = view.getFirstDocument
    
    'Notesメールのレコード数を取得する
    Dim doccount As Variant
    Dim counter As Long
    counter = 1
    doccount = view.EntryCount

    'Notesメールのプレフィックス
    Dim prefix As String
    prefix = DLookup("prefix", "setting", "ID=1")
    
    '同じファイルが添付されるのを防ぐ為の変数
    Dim befattach As String
    Dim nowattach As String
    
    '添付ファイル名用
    Dim tempAttach As String
    Dim strsubject As Variant
    
    'プログレスバー標記用変数
    Dim varReturn As Variant

    Do Until doc Is Nothing
    
        'プログレス表示
        varReturn = Application.SysCmd(acSysCmdInitMeter, counter & "個目の" & statusman & "トレイのレコードを取込中...", doccount)
        
        subjectHitFlag = False
        'Subjectにより判断する
        On Error Resume Next
        buf = doc.getfirstitem("Subject").Values
        mySubject = buf(0)
        subjectHitFlag = True
        
        '不在通知の場合送信日付がないので現在の日付を便宜的に充てる
        If Left(mySubject, 4) = "不在通知" Then
            myDate = Now()
        Else
            '送信日の有無をチェック
            If doc.getfirstitem("PostedDate") Is Nothing Then
                myDate = Now()
            Else
                buf = doc.getfirstitem("PostedDate").Values
                myDate = buf(0)
            End If
        End If
        
        
        strWhere = "[strPosted] = " & "'" & CStr(myDate) & "'"
        '主キーが登録済みでなければAddNew。Updateは今後の課題
        Debug.Print DCount("[strPosted]", "T_mail", strWhere)
        'If DCount("[strPosted]", "T_mail", strWhere) = 0 And subjectHitFlag Then
        
        'Subjectは30文字にカットする
        strsubject = Left(mySubject, 30)

        rs.AddNew
        rs!strPosted = CStr(myDate)
        rs!PostedDate = myDate
        rs!Subject = strsubject
        
        'SendToがなく、BCCのみのメールが存在する
        If Not doc.getfirstitem("SendTo") Is Nothing Then
            buf = doc.getfirstitem("SendTo").Values
            
            'sendtoの件数を取得する
            bufcnt = UBound(buf) - LBound(buf) + 1
          
            '全部のsendtoをピックアップする
            For b = 0 To bufcnt - 1
              If Right(buf(b), 3) = prefix Then
                  'DLookupでまずテスト
                  tempaddress = DLookup("メールアドレス", "アドレス変換テーブル", "社員ID='" & Mid(buf(b), 4, 6) & "'")
              
                  '返り値がNullの時とそうでないときの処理
                  If IsNull(tempaddress) Then
                      'Null値なので、そのまま値を追加する
                      tempaddress = buf(b)
                  End If
              Else
                  tempaddress = buf(b)
              End If
            
              '2件目移行ならカンマを追加する
              If b > 0 Then
                  rs("SendTo") = rs("SendTo") & "," & tempaddress
              Else
                  'CC情報を追記する
                  rs("SendTo") = rs("SendTo") & tempaddress
              End If
            Next b
        End If
        
        'Fromアドレスを修正する
        buf = doc.getfirstitem("From").Values
        If Right(buf(0), 3) =  prefix Then
            'DLookupでまずテスト
            tempaddress = DLookup("メールアドレス", "アドレス変換テーブル", "社員ID='" & Mid(buf(0), 4, 6) & "'")
            
            '返り値がNullの時とそうでないときの処理
            If IsNull(tempaddress) Then
                'Null値なので、そのまま値を追加する
                rs!From = buf(0)
            Else
                'メールアドレスがあるのでメアドに変換
                rs!From = tempaddress
            End If
            
        Else
            rs!From = buf(0)
        End If

        rs!From = tempaddress
        If Not doc.getfirstitem("CopyTo") Is Nothing Then
            buf = doc.getfirstitem("CopyTo").Values
            
            'CCの件数を取得する
            bufcnt = UBound(buf) - LBound(buf) + 1

            '全部のCCをピックアップする
            For b = 0 To bufcnt - 1
                If Right(buf(b), 3) =  prefix Then
                    'DLookupでまずテスト
                    tempaddress = DLookup("メールアドレス", "アドレス変換テーブル", "社員ID='" & Mid(buf(b), 4, 6) & "'")
                
                    '返り値がNullの時とそうでないときの処理
                    If IsNull(tempaddress) Then
                        'Null値なので、そのまま値を追加する
                        tempaddress = buf(b)
                    End If
            
                Else
                    tempaddress = buf(b)
                End If

                '2件目移行ならカンマを追加する
                If b > 0 Then
                    rs!cc = rs!cc & "," & tempaddress
                Else
                    'CC情報を追記する
                    rs!cc = rs!cc & tempaddress
                End If
            Next b
        End If
        
        If Not doc.getfirstitem("BlindCopyTo") Is Nothing Then
            buf = doc.getfirstitem("BlindCopyTo").Values

            'CCの件数を取得する
            bufcnt = UBound(buf) - LBound(buf) + 1
            
            '全部のCCをピックアップする
            For b = 0 To bufcnt - 1
                If Right(buf(b), 3) = prefix Then
                    'DLookupでまずテスト
                    tempaddress = DLookup("メールアドレス", "アドレス変換テーブル", "社員ID='" & Mid(buf(b), 4, 6) & "'")
                
                    '返り値がNullの時とそうでないときの処理
                    If IsNull(tempaddress) Then
                        'Null値なので、そのまま値を追加する
                        tempaddress = buf(b)
                    End If
                Else
                    tempaddress = buf(b)
                End If
            
                '2件目移行ならカンマを追加する
                If b > 0 Then
                    rs!bcc = rs!bcc & "," & tempaddress
                Else
                    'CC情報を追記する
                    rs!bcc = rs!bcc & tempaddress
                End If
            Next b
            
        End If
        
        '送信フラグを設定する
        If view.Name = "($Sent)" Then
          rs!sendFlag = True
        Else
          rs!sendFlag = False
        End If
        
        '出力先フォルダ名を設定
        rs!出力先 = expfolder
        
        'Fromのアドレスがユーザアドレスと一致する時送信フラグを設定する
        Dim test As Variant
        test = DLookup("useraddress", "setting", "ID=1")
        
        If IsNull(test) Then
            test = ""
        Else
            
        End If
        
        If useraddress = "" Then
            '判定しない
            rs!sendFlag = False
        Else
            'ユーザアドレスと一致するか判定
            If rs!From = useraddress Then
                '一致するのでフラグを立てる
                rs!sendFlag = True
            Else
                '一致しないのでフラグを立てない
                rs!sendFlag = False
            End If
        End If
        
        Set RichTextBody = doc.getfirstitem("Body")
        'Bodyが無いメールがあるのでその対策を追加
        If Not RichTextBody Is Nothing Then
          rs!Body = RichTextBody.Text
          If doc.HASEMBEDDED Then
            attaches = Empty
            If IsArray(RichTextBody.EMBEDDEDOBJECTS) Then attaches = RichTextBody.EMBEDDEDOBJECTS
            If IsArray(attaches) Then
              For i = LBound(attaches) To UBound(attaches)
                'HTMLメールの画像は添付ファイルとして認識されないが、電子署名用のsmime.p7sは添付ファイルと認識される
                If attaches(i).Type = EMBED_ATTACHMENT Then '1454
                  Set attach = attaches(i)

                  'ファイル名がおかしいかどうかチェックする
                  tempAttach = replaceNGchar(attach.Name)
                  tempAttach = StrConvU(tempAttach, vbNarrow)
                  tempAttach = replaceUniChar(tempAttach)

                  '一時フォルダに添付ファイルを解凍する
                  attach.EXTRACTFILE destDrive & tempAttach
                  
                  '現在の添付ファイルの名前を格納
                  nowattach = tempAttach
                  
                  'Access2007からの添付ファイル型および複数の値を持つフィールドは、そのフィールドの中に
                  'レコードセットを保持している感じである。
                  
                  If FSO.GetExtensionName(tempAttach) = "lnk" Then
                    'ショートカットファイルなので添付をせずスルーする
                  Else
                      
                      '前の添付ファイル名と同じものの場合添付をスルーする(エラーになるため)
                      If nowattach = befattach Then
                        '直前の添付ファイル名と同じなので同じレコードには添付しない
                      Else
                        With rs.Fields("attach").Value
                          .AddNew
                           '.lnkファイルは 3058 インデックスまたは主キーには、NULL 値を使用できませんというエラーになる
                           'その他少数のファイルで、同様のエラーで添付できないものがあるが原因不明。
                          .Fields("FileData").LoadFromFile destDrive & "\" & tempAttach
                          .Update
                        End With
                        
                      End If
                      
                      '添付ファイル名を格納する
                      befattach = tempAttach
                      
                  End If

                  'ファイルを削除する
                  FSO.DeleteFile destDrive & tempAttach
                  If Err.Number <> 0 Then Debug.Print "Delete Err", tempAttach, Err.Number, Err.Description
                  Set attach = Nothing
                  
                End If
              Next i
              
              '添付ファイル情報をクリアする
              nowattach = ""
              befattach = ""

            End If 'isArray
          End If 'HASEMBEDDED
        End If 'RichTextBody
        DoEvents: DoEvents: DoEvents
        rs.Update
        'End If 'Dcount
        
        Set doc = view.GETNEXTSIBLING(doc)
        Sleep 10
        
        'インジケータの数値をアップ
        counter = counter + 1
        varReturn = Application.SysCmd(acSysCmdUpdateMeter, counter)
    
    Loop

    Set rs = Nothing
    Set FSO = Nothing
    Set AcDB = Nothing
    
    '終了処理
    Access.Application.SysCmd (acSysCmdRemoveMeter)
    Exit Sub

'エラー処理後に実行する
ExitProcedure:
    '問い合わせ
    result = MsgBox("エラー!!" & vbCrLf & errormsg & vbCrLf & "このままこのメールだけ処理をスキップして次に進むこともできます。進みますか?", vbYesNo + vbDefaultButton2 + vbExclamation)
    If result = vbYes Then
        '処理は継続されます
        On Error Resume Next
    Else
        MsgBox "処理はキャンセルされました。"
        Exit Sub
    End If
    
'エラー発生時の処理
Err_handler:
    errormsg = "エラー番号:" & Err.Number & vbCrLf & "エラーの種類:" & Err.Description
    
    Resume ExitProcedure
    
End Sub

EMLファイルを生成する

Public Members() As Variant
Option Explicit
'emlファイルを作るルーチン
Private Sub CreateEmlFile(ByVal MailFrom As String, _
                          ByVal MailTo As String, _
                          ByVal MailCc As String, _
                          ByVal MailBcc As String, _
                          ByVal MailSubject As String, _
                          ByVal MailBody As String, _
                          ByVal MailPath As String, _
                          ByVal MailFileName As String, _
                          ByRef AttachArray() As Variant, _
                          Optional ByVal Charset As String = "UTF-8")
  Const adTypeText = 2
  Const adSaveCreateOverWrite = 2
  Dim testarr As String
  Dim arrlength As Long
  Dim i As Long
  Dim filepath As String
  
  On Error Resume Next
 
  With CreateObject("CDO.Message")
    .BodyPart.Charset = Charset
    .From = MailFrom
    .To = MailTo
    If Len(Trim(MailCc)) > 0 Then .cc = MailCc
    If Len(Trim(MailBcc)) > 0 Then .bcc = MailBcc
    .Subject = MailSubject
    .TextBody = MailBody
    
    '添付ファイルパスがある場合だけ処理
    testarr = AttachArray(1)

    If testarr = "NOFILE" Then
        '何もしない
    Else
        '配列の要素数をカウントする
        arrlength = UBound(AttachArray)
        
        For i = 1 To arrlength
            'ファイルのパスを取り出す
            filepath = AttachArray(i)
            'ファイルを添付する
            .AddAttachment filepath
        Next i
        
        '.AddAttachment AttachmentFilePath
    End If

    With .GetStream
      '.Charset = "UTF-8"
      .Charset = Charset
      .Type = adTypeText
      .SaveToFile MailPath & "\" & MailFileName, adSaveCreateOverWrite
    End With
  End With
End Sub

添付ファイルだけを取り出す

'添付ファイルだけを取り出すルーチン
Public Function attachexport()
    '変数の宣言
    Dim folpath As Variant
    Dim GetFolderName As String
    Dim reccnt As Long
    Dim FSO As New FileSystemObject
    Dim counter As Long
    Dim exportpath As String
    Dim v As Variant
    
    'プログレス用変数
    Dim varReturn As Variant

    '保存先フォルダの指定ダイアログの表示
    With Application.FileDialog(msoFileDialogFolderPicker)
      'ダイアログのタイトルを設定
      .Title = "添付ファイルの保存先の指定"
      'ダイアログを表示
      folpath = .Show
      If folpath <> 0 Then
        'フォルダが選択されたとき
        'そのドライブ名+フォルダ名を返り値に設定
        GetFolderName = Trim(.SelectedItems.item(1))
      Else
        'フォルダが選択されなければ長さゼロの文字列を返す
        GetFolderName = ""
      End If
    End With
    
    'キャンセルされた場合の処理
    If GetFolderName = "" Then
        MsgBox "処理はキャンセルされました。"
        Exit Function
    End If
    
    'データベースに接続
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim rsf As DAO.Recordset
    Dim strSQL As Variant
    Set db = CurrentDb
    Set rst = db.OpenRecordset("T_mail")
    Set rsf = db.OpenRecordset("フォルダ名リスト", dbOpenDynaset)
    
    'レコード件数が0ならば処理を中止
    reccnt = rst.RecordCount
    If reccnt = 0 Then
        MsgBox "処理すべきレコードが0件でした"
        Exit Function
    End If
    
    '一時処理用変数
    Dim tempcc As Variant
    Dim tempbcc As Variant
    Dim tempsubject As Variant
    Dim tempAttach As Variant
    Dim attachman As Variant
    
    '指定の保存先に保存用フォルダの作成
    Do Until rsf.EOF
        'ファイルが存在する場合、ファイルを開く
        If FSO.FolderExists(GetFolderName & "\" & rsf!出力先フォルダ名) = True Then
            'フォルダはあるのでスルーする
        Else
            'フォルダがないので作成する
            MkDir GetFolderName & "\" & rsf!出力先フォルダ名
        End If
        
        '次のリストへ移動
        rsf.MoveNext
    Loop
    
    '砂時計を表示
    DoCmd.Hourglass True
    
    'ループでemlファイルを作成
    Do Until rst.EOF
        '何もしないイベント(応答なしを防ぐ為)
        DoEvents

        'プログレス表示
        varReturn = Application.SysCmd(acSysCmdInitMeter, counter & "個目のデータを変換中...", reccnt)
        
        '変数を初期化
        tempAttach = ""
        
        '出力先を取得
        exportpath = GetFolderName & "\" & rst!出力先
        
        '添付ファイルを取り出す
        With rst("attach").Value
            While (Not .EOF)
                For Each v In .Fields
                    If (v.Name = "FileName") Then
                        '添付ファイルがあるので、添付ファイルフォルダに取り出し
                        .Fields("FileData").SaveToFile exportpath
                        Exit For
                    Else
                        '添付ファイルなし
                    End If
                Next
                .MoveNext
            Wend
        End With

        '次のレコードへ移動
        rst.MoveNext
        varReturn = Application.SysCmd(acSysCmdUpdateMeter, counter)
    Loop

    '終了処理
    Set db = Nothing
    Set rst = Nothing
    Set rsf = Nothing
    Access.Application.SysCmd (acSysCmdRemoveMeter)
    DoCmd.Hourglass False
    
    MsgBox reccnt & "件のNotesデータから添付ファイルを取り出しました。"
    
End Function

EML形式を連続インポートする

Const ForReading = 1    '// ファイルを読み取り専用として開きます。このファイルには書き込むことができません。
Const ForWriting = 2    '// ファイルを書き込み専用として開きます。
Const ForAppending = 8  '//ファイルを開き、ファイルの最後に追加して書き込みます。

'Filesysmtem Object等を宣言
Public FSO As Object
Public WSH As Object
Public OutlookApp As Object
Public fldImport As Object

'一時フォルダの指定
Public tempman As String
Public oLog As Object

'Sleep
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)

'EML形式をOutlookにコピーする
Public Function importEml()
    'グローバル設定
    Set FSO = CreateObject("Scripting.FileSystemObject")
    Set WSH = CreateObject("WScript.Shell")
    Set OutlookApp = CreateObject("Outlook.Application")
    tempman = CreateObject("WScript.Shell").SpecialFolders.item("Desktop") & "\tempnotes"

    'Outlook側インポートフォルダの指定
    Const olFolderInbox = 6
    Set fldImport = OutlookApp.session.GetDefaultFolder(olFolderInbox)
    Set fldImport = fldImport.Folders("インポート")
    fldImport.Display
    
    'ログの出力用
    Dim fn
    fn = FSO.getParentFolderName(tempman) & "\" & _
         FSO.GetBaseName(tempman) & "_" & _
         Replace(Left(Now(), 10), "/", "") & ".log"
    If FSO.FileExists(fn) = False Then
        Set oLog = FSO.CreateTextFile(fn)
    Else
        Set oLog = FSO.OpenTextFile(fn, ForAppending, True)
    End If
    
    'EMLインポート開始
    log "START:" & FSO.GetFolder(tempman).Name
    Call LoopFolder(FSO.GetFolder(tempman))
    
    '終了処理
    OutlookApp.ActiveExplorer.Close
    log "インポートは終了しました。"
    oLog.Close
    
    MsgBox "インポート処理終了"
    Set oLog = Nothing

End Function

'サブフォルダまで含めてEML取り込み
Sub LoopFolder(objFolder)
    Dim objSubFolder
    Dim objFile

    log "LOOP: " & objFolder

    'ファイルを登録
    For Each objFile In objFolder.files
        '// 拡張子が .eml ならインポート処理
        If LCase(Right(objFile.Name, 4)) = ".eml" Then
           OpenEml objFile
        End If
    Next

    'フォルダがあれば再帰
    For Each objSubFolder In objFolder.SubFolders
        LoopFolder objSubFolder
    Next
End Sub

'emlファイルを開いてインポートする
Sub OpenEml(emlFile)
    '// エラー無視
    log "OPEN EML: " & emlFile.Name

    '// メールが開いていたら閉じる
    While Not OutlookApp.ActiveInspector Is Nothing
        OutlookApp.ActiveInspector.Close
        Sleep 500
    Wend

    '// eml ファイルを Outlook で開くコマンドを実行
    WSH.Run "outlook /eml """ & FSO.getParentFolderName(emlFile) & _
        "\" & emlFile.Name & """"

    '// Outlook 起動待ち
    While OutlookApp.ActiveInspector Is Nothing
        Sleep 500
    Wend
    
    Debug.Print Err.Number

    '// メールフォルダ移動
    OutlookApp.ActiveInspector.CurrentItem.Move fldImport
    
    '// 取り込んだファイルは削除(エラーなしの場合)
    If Err.Number = 0 Then
        emlFile.Delete
    End If
End Sub

'ログ出力
Sub log(strMsg)
    '// エラー無視
    On Error Resume Next

    '// ログファイルに出力
    oLog.WriteLine (Now() & " " & strMsg)
End Sub

その他

VBAにてメール以外のNSFテーブルへ接続して読み書きをする手段もあるようなのですが、まだ自分は確立できていません。そこで「読み取り専用」ではありますが、Notes DBのODBC Driverを利用してAccessやExcelで接続しデータを引っこ抜く方法をここに残しておきます。

注意点

利用する為には、いくつかの注意点があります。

  1. Notes AdministratorではなくNotes本体がインストールされてる必要性があります。
  2. Notes 8.5の場合は、32bit版のODBCドライバである必要があります(64bit版は利用できません)。
  3. また、その場合利用するExcelやAccessも32bit版を利用する必要があります(64bit版では接続が出来ません)
  4. 日本語名のDBは名前が文字化けしますが利用可能です。

また、例えばOffice2016 VL版はWindowsインストーラでインストールされていますが、Access2016単品版はクイックインストール(C2R)版なので同居が出来ないといった問題があります。Accessを利用する場合はOfficeのProfessional版を使うか?Accessのみでの運用が必要です。

ODBCドライバのインストール

NotesDBにExcelやAccessから接続してデータを抜き取る為のODBCドライバをインストールします。利用するのは32bit版となります。

  1. ODBCドライバを入手しておく
  2. 解凍してsetup.exeを実行する
  3. インストール項目は全部を選択する
  4. インストールが完了する
  5. インストールそのものは非常に簡単に完了します。
  6. 但し、インストール時にNotesの場所が見つからないみたいなエラーが出た場合には、以下の処理を追加します
  7. システム環境設定を開く
  8. ユーザ環境設定のPathを選択して、編集をクリック
  9. 新規追加で、「C:\Program Files (x86)\IBM\Lotus\Notes\」を追加する
  10. 再起動してドライバを入れ直す

図:環境変数に値を追加するのが面倒

ODBC接続設定(DSN)を作成する

以下の手順でODBC接続を作成する必要があります。今回はAdminでの接続設定を作成します。

  1. コントロールパネルからODBCアドミニストレータ(32bit)を開く
  2. システムDSNを開く
  3. 追加をクリック
  4. Lotus Notes SQL Driverを選択
  5. 文字が見切れてるので、Shift+Tabを2回押してから、spaceを押すとフルサイズになって表示される
  6. Data Source Nameは適当につける。
  7. 下の方のAdd Userをクリックして、adminのIDファイルを指定して追加する
  8. パスワードを入力する
  9. Domino Serverのプルダウンを開くと「サーバーリスト」が出てくるので選択(パスワードを求められる)
  10. Databaseはプルダウンで選択する
  11. OKをクリックできるのでクリックするとDSNが作成される

図:管理者権限で接続が必要です。

データを抜き取る

作成したODBCを使って、Excelで抜いてみようと思います。Accessのほうがリンクテーブルでとれるので非常に楽なのでExcelだと使い勝手が悪いですが、IDとPWがわかっていればPower Queryでも接続して取得が可能のようです。但し読み取り専用なので、データの取得しか出来ません。

VIEW, Tableについては抜くことが出来ることを確認しています。以下はExcel2016の例なので最新版だとまた手順が異なる場合があります。

  1. Excelを起動する
  2. データタブのその他のデータソースをクリック
  3. データ接続ウィザードをクリック
  4. ODBC DSNをクリックして次へ
  5. 作成した名前のDSNが出てくるので選択する
  6. データベースとテーブルの選択にて、取り出すテーブルを選択する
  7. 貼り付ける場所を指定する
  8. データが抜き取られる(テーブル形式で貼り付けがされる)

権限不足やエラーなどで抜けないようなテーブルもあったりしますが、概ねテーブルデータをそのまま引っこ抜けているので、AdministratorのDesignerと合わせれば概ねアプリケーションのGUIとバックエンドを把握することが可能です。個別のドキュメントが何処のテーブルに格納されてるのかがわかれば、個別データも抜けると思います。

図:DB毎にDSN作らなければいけなのが面倒だけど

関連リンク