モジュール自動入れ替え機能

更新モジュールがあると、これを自動的に入れ替える「自動入れ替え機能」の組み込み用モジュールです。
大勢に配布したマクロ付きブックのマクロを入れ替える必要が発生した。 その時になって、この見出しに飛びつくのではいけません。
ここで紹介するのは、繰り返し長期間使うようなマクロの仕組みでの、不具合対応や機能変更などに対応するために、モジュールを入れ替える仕掛けを事前に実装させておくというものです。

なお、これをやろうとする場合は、本ページは最後までよく読んで下さい。この機能は単にマクロを有効にしただけでは機能しません。 「VBAプロジェクトへのアクセスを信頼する」の設定が必要ですし、セキュリティソフトがマクロコードを書き換える類の動作をブロックする場合があります。

仕組み全体を見直して、主機能は最初から別のマクロブックやアドインに転出させておき、機能が変わる場合はそれを上書きするだけで済むようにしておけばここで説明する方法自体も不要になります。

複数の利用者にマクロを仕込んだワークブックを配布した場合に、後になってマクロを改変しなければならないことはよくあることです。定例的な業務になるものであれば「アドイン化」でプログラム部分をデータブックと分離することをお勧めしますが、そこまでの使用頻度でないという場合でも、何らかの対策を採っておくと、いざという時に無駄な作業を強いられずに済みます。

この「モジュール入れ替え機能」は、ブックの立ち上げ時にブックの所在フォルダに更新用モジュール(*.bas)があるか、また、そのバージョンが以前に更新したものより新しいかを自動的に判断して、そのモジュールを自動的に入れ替えるものです。
標準モジュールの入れ替えを目的とするなら、特に改造することなく「modPERLACE_MODULE.bas」を目的のブックにインポートさせて、立ち上げマクロの先頭に呼び出す記述を加えるだけで利用できます。

最近のPCでは、モジュールの更新作業に時間は掛かりませんが、
モジュール更新中....
処理状態はステータスバーに表示されます。

更新が完了すると、自動的に上書き保存されて、
モジュール更新完了のメッセージ
このようにメッセージが表示されます。ここで「OK」をクリックすると、一旦Excelが終了します。
これは、コンパイルチェックを受けているプロジェクトのモジュールを書き換えていることによって、Excelが不安定になるのを防ぐためです。

サンプル「Module入れ替え機能サンプル.xls」がダウンロードできるので、確認してみて下さい。
Module入れ替え機能サンプル.xls」と同じフォルダに新しいバージョンの更新用モジュールがない場合は、そのまま起動時のプロシージャから標準モジュールの「TEST1」プロシージャが呼ばれます。
当初のバージョンは「100」で、
これは交換前のモジュールです。
このようにメッセージ表示されます。

起動時のプロシージャは、

'*******************************************************************************
'   モジュール入れ替え機能サンプル(ThisWorkbook)
'
'   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'*******************************************************************************
Option Explicit

'*******************************************************************************
' ブック起動イベント
'*******************************************************************************
Private Sub Workbook_Open()

    ' モジュール交換処理
    Call REPLACE_MODULE(cnsModuleFile, cnsSH, cnsRange, "", cnsModule1)

    ' 本体処理(このプロシージャは入れ替え対象)
    Call TEST1
    ThisWorkbook.Saved = True
End Sub

'--------------------------------<< End of Source >>----------------------------
これだけが記述されていて、ここで呼ばれている「REPLACE_MODULE」がモジュール入れ替え機能のプロシージャ、「TEST1」は入れ替え対象である標準モジュール「Module1」内のプロシージャです。

標準モジュール「Module1」は当初は、
'100    ←バージョン情報(整数3桁) ※このバージョンによりModule自動更新が判断される
'*******************************************************************************
'   モジュール入れ替え機能サンプル(Module1)
'
'   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'*******************************************************************************
Option Explicit
Public Const cnsTitle = "モジュール入れ替え機能"
' 交換用モジュールのファイル名
Public Const cnsModuleFile = "REPLACE_Module1.bas"
Public Const cnsSH = "設定"             ' バージョン格納シート
Public Const cnsRange = "$B$2"          ' バージョン格納セル
Public Const cnsModule1 = "Module1"     ' モジュール名

'*******************************************************************************
' 主要(本体)処理
'*******************************************************************************
Public Sub TEST1()
    MsgBox "これは交換前のモジュールです。",, cnsTitle
End Sub

'--------------------------------<< End of Source >>----------------------------
と記述されていて、上記のメッセージが表示されます。

ここで、「入れ替え用Module」フォルダにある入れ替え用モジュール「REPLACE_Module1.bas」をサンプル「Module入れ替え機能サンプル.xls」と同じフォルダに移し、サンプル「Module入れ替え機能サンプル.xls」を起動させると、最初に説明したモジュール入れ替えが行なわれて、一旦Excelが終了します。
そこで、再度、サンプル「Module入れ替え機能サンプル.xls」を起動させると、
これは交換「後」のモジュールです。
このように、前回と違うメッセージが表示されます。

標準モジュール「Module1」は、
'101    ←バージョン情報(整数3桁) ※このバージョンによりModule自動更新が判断される
'*******************************************************************************
'   モジュール入れ替え機能サンプル(Module1)
'
'   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'*******************************************************************************
Option Explicit
Public Const cnsTitle = "モジュール入れ替え機能"
' 交換用モジュールのファイル名
Public Const cnsModuleFile = "REPLACE_Module1.bas"
Public Const cnsSH = "設定"             ' バージョン格納シート
Public Const cnsRange = "$B$2"          ' バージョン格納セル
Public Const cnsModule1 = "Module1"     ' モジュール名

'*******************************************************************************
' 主要(本体)処理
'*******************************************************************************
Public Sub TEST1()
    MsgBox "これは交換「後」のモジュールです。",, cnsTitle
End Sub

'--------------------------------<< End of Source >>----------------------------
このように入れ替えられたのが確認できます。

入れ替え動作はこれで理解していただけたと思いますが、あとは「バージョン管理」の問題が残っています。

本機能では、モジュールのバージョン情報を整数で表記するものとし、処理するワークブックのどれかのシートのどこかのセルに収容することを前提としています。
そこで、サンプル「Module入れ替え機能サンプル.xls」では「設定」シートのB2セルにバージョンの値を保持させています。この値が当初は「100」で、モジュールの入れ替え後は「101」になっていることも合わせて確認しておいて下さい。

一方、入れ替えられる側のモジュールのバージョンは、そのモジュールの先頭行にコメントマークの次に記述されているものを読み取って判定されます。(先頭行に数値として認識できるものがない場合は、バージョン「0」として扱われるので、入れ替えは起こりません。

本「モジュール入れ替え機能」は、なるべく汎用的に利用できるように「入れ替えモジュールファイル名」「バージョン登録シート名」「バージョン登録セル」「バージョン登録シートの保護パスワード」「入れ替えモジュール名」を引数として指定できるようになっています。

このような機能を望んでいた方は多いと思います。「アドイン化」ではハードルが高いけれど、これなら実現できると考える方には、いくつか問題点があるので、これだけは念頭に置いて下さい。

まず、VBプロジェクトがパスワードロックされている場合は、そもそも動作しません。
プロジェクトが保護されているため、操作を実行することができません。
パスワードロックを解除するメソッドがあっては、パスワードの意味なしということです。
この入れ替え機能を実装するワークブックは、VBプロジェクトのパスワードロックをしないで運用することが前提となります。

また、このような機能は、悪用を考えると「マクロウィルス」のようなことになってしまいます。
マイクロソフトは昨今、セキュリティ面での印象の改善に躍起になっており、その一環かも知れませんが、プロジェクトをマクロで操作するものを排除するような機能をExcel2002以降に実装しています。
初期設定のままで、本機能が働く状態になると、
プログラミングによるVisualBasicプロジェクトへのアクセスは信頼性に欠けます。
このように、実行時エラーになります。
この実行時エラーの件について、改善を行ないました。   本件について、2007/06/10に改善を行ないました。
上記のような「実行時エラー」ではなく、内部メッセージが表示されます。
                      マクロセキュリティ設定が必要です。
このメッセージに対処方法もある程度、記載してありますので、ユーザーが対応できると思います。
このメッセージで「OK」をクリックするとブックは閉じられます。
「Excel2002では VBEを操作するマクロがエラーになる。」でも説明していますが、
セキュリティの設定変更
このように設定変更する必要があります。
Excel2003では、場合によってはこの設定を変更できないことがあり、この状態ではここで紹介しているコードは実行不可能のようです。
Excel2003は変更できない。
これは、以前のバージョンからアップグレードしたようなケースで起きる現象のようで、以前のバージョンでデジタル署名が用いられている状態でこのような状況になります。 Office 2003が通常通り新規にインストールされている場合には発生しません。
こちらはExcel2003でも変更できる。

さらに、本体の構文も調整が必要な場合があります。
この「自動入れ替え機能」自体はモジュールから分離されているので問題ありませんが、実際に入れ替え対象となるモジュールには他のモジュールから参照されるPublic変数があったり、呼び出されるプロシージャがあったりして、入れ替えのタイミングで一時的に「コンパイルエラー」に該当する状況になります。
本機能の作成にあたっては、イベントが有効になっているとか、入れ替え時にモジュール自体を削除するなど、「コンパイルエラー」が発生してデザインモードに落ちてしまう現象をいくつか検証して、これらを回避するように考慮していますが、完全ではありません。
複数のモジュールで構成されるプロジェクトにこの機能を実装する場合は、これらの点に考慮して「自動入れ替え機能」自身を充分に検証し、必要な場合はモジュール間の参照を使わないとか、Call呼び出しではなく、Application.Run呼び出しにするとかの対応を追加して下さい。

これらもクリアさせるなら、データブックとプログラムブックを分けてしまうことですが、この一例が「アドイン化」です。

実際に組み込む「modPERLACE_MODULE.bas」のコードです。
ここまでの説明の通りで構わなければ、このモジュールは改変せずに利用できると思います。

'*******************************************************************************
'   モジュール入れ替え機能サンプル(modPERLACE_MODULE)
'
'   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'*******************************************************************************
Option Explicit
Private Const cnsTitle = "モジュール自動更新"
Private Const cnsYen = "\"
#If VBA7 Then
' Sleep
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
' Sleep
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If

'*******************************************************************************
' Module入れ替え処理
'-------------------------------------------------------------------------------
' 【引数】
'  @strModuleFile :入れ替えモジュールのファイル名(パスは自ブック所在に固定)
'  AstrSheetName  :バージョン情報の収容シート名
'  BstrRangeAddr  :バージョン情報の収容セルアドレス
'  CstrPassWord   :バージョン情報の収容シートのパスワード
'  DstrModule     :入れ替え対象モジュール名
'*******************************************************************************
Public Sub REPLACE_MODULE(ByVal strModuleFile As String, _
                          ByVal strSheetName As String, _
                          ByVal strRangeAddr As String, _
                          ByVal strPassWord As String, _
                          ByVal strModule As String)
    Dim xlAPP As Application
    Dim WBK As Workbook             ' 本ブック
    Dim SH As Worksheet             ' バージョン情報の所在シート
    Dim vbeVBPROJECT As Object      ' VBProject
    Dim vbeVBCOMPONENT As Object    ' VBComponent
    Dim strPATHNAME As String       ' 本ブックのフォルダ名
    Dim strFILENAME As String       ' 更新モジュールのファイル名(フルパス)
    Dim intFF As Integer
    Dim strREC As String
    Dim lngVer As Long              ' バージョン
    Dim lngLines As Long

    Set xlAPP = Application
    Set WBK = ThisWorkbook
    strPATHNAME = WBK.Path
    ' 同一フォルダにModuleがあるか確認する
    If Right$(strPATHNAME, 1) <> cnsYen Then strPATHNAME = strPATHNAME & cnsYen
    strFILENAME = strPATHNAME & strModuleFile
    If Dir(strFILENAME, vbNormal) = "" Then
        Set WBK = Nothing
        Exit Sub
    End If
    ' ModuleのOPEN
    intFF = FreeFile
    Open strFILENAME For Input Shared As #intFF
    Line Input #intFF, strREC   ' 1REC目は無視(Attribute VB_Name = "Module1")
    ' レコード取得(2レコード目)
    Line Input #intFF, strREC
    Close #intFF
    ' Version取得
    lngVer = Val(Trim(Mid(strREC, 2)))
    Set SH = WBK.Worksheets(strSheetName)
    ' バージョン確認
    If ((SH.Range(strRangeAddr).Value >= lngVer) Or (WBK.ReadOnly = True)) Then
        Set SH = Nothing
        Set WBK = Nothing
        Exit Sub
    End If
    If MsgBox("更新用モジュール(VBA)が存在します。" & vbCr & _
        "   ( Ver" & SH.Range(strRangeAddr).Value & " → Ver" & _
        CStr(lngVer) & " )" & vbCr & _
        "モジュールの更新を行ないますか?" & vbCr & vbCr & _
        "更新後は上書き保存され一旦終了します。", _
        vbInformation + vbYesNo, cnsTitle) <> vbYes Then
        Set SH = Nothing
        Set WBK = Nothing
        Exit Sub
    End If
    If xlAPP.Workbooks.Count > 1 Then
        MsgBox "モジュールの更新後、一旦Excelを終了するため" & vbCr & _
            "他のブックを閉じてから再度起動して下さい。", _
            vbInformation, cnsTitle
        Set SH = Nothing
        WBK.Saved = True
        WBK.Close False
        Set WBK = Nothing
        End
    End If
    ' モジュール更新開始
    xlAPP.StatusBar = "モジュール更新中....( →Ver" & CStr(lngVer) & ")"
    ' VBプロジェクトを取得し、Moduleを入れ替える
    On Error Resume Next
    Set vbeVBPROJECT = WBK.VBProject
    ' Excelのセキュリティ設定によるエラー処置
    If ((Err.Number = 1004) And (Left(Err.Description, 7) = "プログラミング")) Then
        MsgBox "マクロセキュリティ設定が必要です。" & vbCr & vbCr & _
            "Excelのツールメニューから「マクロ」「セキュリティ」を開き、" & vbCr & _
            "「信頼できる発行元」タブにある" & vbCr & _
            "「Visual Basicプロジェクトへのアクセスを信頼する」に" & vbCr & _
            "チェックを付けてから本ブックを開いて下さい。", _
            vbExclamation, cnsTitle
        Set SH = Nothing
        WBK.Saved = True
        WBK.Close False
        Set WBK = Nothing
        End
    End If
    On Error GoTo 0
    Set vbeVBCOMPONENT = vbeVBPROJECT.VBComponents(strModule)
    ' 更新中のエラーを避けるためイベントを停止
    xlAPP.ScreenUpdating = False
    xlAPP.EnableEvents = False
    xlAPP.Interactive = False
    ' Workbook側でModule1内プロシージャを参照しているため、
    ' Removeできないので、コードを書き換え一旦終了する
    With vbeVBCOMPONENT.CodeModule
        ' コードを削除(全行)
        lngLines = .CountOfLines
        If lngLines <> 0 Then .DeleteLines 1, lngLines
        ' Module1をBASファイルからインポートする
        .AddFromFile strFILENAME
    End With
    Set vbeVBCOMPONENT = Nothing
    Set vbeVBPROJECT = Nothing
    ' バージョンのセット
    With SH
        If .ProtectContents = True Then
            .Unprotect strPassWord
            .Protect strPassWord, UserInterfaceOnly:=True
        End If
        .Range(strRangeAddr).Value = lngVer
    End With
    ' 上書き保存
    xlAPP.StatusBar = False
    xlAPP.ScreenUpdating = True
    WBK.Save
    Set SH = Nothing
    Sleep 200                           ' 処理間隔を空ける
    xlAPP.Interactive = True
    xlAPP.ScreenUpdating = True
    ' コンパイル済みモジュールを入れ替えているため、
    ' このまま動作させず一旦終了する
    MsgBox "モジュールの更新は正常に終了しました。" & vbCr & _
        "一旦、終了します。起動し直して下さい。" & vbCr & _
        "(このExcelブックだけもう一度起動して下さい)", _
        vbInformation, cnsTitle
    ' 終了
    xlAPP.Quit
    WBK.Close False
    End
End Sub

'--------------------------------<< End of Source >>----------------------------
以上、組み込み利用を意識して作成しましたが、複数モジュールが存在したり、フォームやクラスモジュール、シートやブックのモジュールまで考慮すると、用途によって改変しなくてはならない場合もあります。
ですが、主要処理を標準モジュールに記述し、各イベントプロシージャからもこれを呼び出すような形態にすれば、入れ替えの対象を標準モジュールに限定してもほとんどの場合に対応できると思います。

ダウンロードはこちら。
←modPERLACE_MODULE.zip
      (22KB)