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

更新モジュールがあると、これを自動的に入れ替える「自動入れ替え機能」の組み込み用モジュールです。
大勢に配布したマクロ付きブックのマクロを入れ替える必要が発生した!!   その時になって、この見出しに飛びつくのではいけません。



ここで紹介するのは、繰り返し長期間使うようなマクロの仕組みでの、不具合対応や機能変更などに対応するために、モジュールを入れ替える仕掛けを事前に実装させておくというものです。



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



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



あちこちにマクロを投入したワークブックを多数配布した場合や、そのマクロブックの複製が作成されてしまう場合は、「一斉に入れ替える機能」を希望されるのかも知れませんが、 マクロでマクロを書き換える方法は一瞬コンパイル関係が壊れる処置であるため、どうしても動作が不安定になります。 このため、複数のワークブックのマクロを書き換えるような構造のものは動作が成功しないようです。



ここでの紹介は、事前に「入替え機能」だけは実装させておき、所定のモジュールファイルがあれば自動的に入れ替え処理が行なわれるという方法です。 「やればできるんだ」程度のサンプルです。



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

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

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

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

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

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


'***************************************************************************************************
'   モジュール入れ替え機能サンプル                                  ThisWorkbook(Class)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'04/11/07(1.00)新規作成
'19/10/28(1.10)*.xlsm化
'20/03/17(1.11)記述修正(標準化準拠)、他
'***************************************************************************************************
Option Explicit

'***************************************************************************************************
'   ■■■ ワークブックイベント ■■■
'***************************************************************************************************
'* 処理名 :Workbook_Open
'* 機能  :ブック起動イベント
'---------------------------------------------------------------------------------------------------
'* 返り値 :(なし)
'* 引数  :(なし)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2004年11月07日
'* 作成者 :井上 治
'* 更新日 :2020年03月17日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:
'***************************************************************************************************
Private Sub Workbook_Open()
    '-----------------------------------------------------------------------------------------------
    ' モジュール交換機能
    Call modPERLACE_MODULE.REPLACE_MODULE(g_cnsModuleUpdFile, _
                                          g_cnsModuleUpdSh, _
                                          g_cnsModuleUpdRange, _
                                          "", _
                                          g_cnsModuleUpdModule1)
    '-----------------------------------------------------------------------------------------------
    ' 本体処理(このプロシージャは入れ替え対象)
    Call Module1.TEST1
    ThisWorkbook.Saved = True
End Sub

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

標準モジュール「Module1」は当初は、

'100    ←バージョン情報(整数3桁) ※このバージョンによりModule自動更新が判断される
'***************************************************************************************************
'   モジュール入れ替え機能サンプル                                  Module1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'04/11/07(1.00)新規作成
'19/10/28(1.10)*.xlsm化
'20/03/17(1.11)記述修正(標準化準拠)、他
'***************************************************************************************************
Option Explicit
Option Private Module
'===================================================================================================
Public Const g_cnsTitle As String = "モジュール入れ替え機能"
' 交換用モジュールのファイル名
Public Const g_cnsModuleUpdFile As String = "REPLACE_Module1.bas"   ' 交換モジュールファイル
Public Const g_cnsModuleUpdSh As String = "設定"                    ' バージョン値格納シート
Public Const g_cnsModuleUpdRange As String = "$B$2"                 ' バージョン値格納セル
Public Const g_cnsModuleUpdModule1 As String = "Module1"            ' モジュール名

'***************************************************************************************************
'   ■■■ 外部公開プロシージャ ■■■
'***************************************************************************************************
'* 処理名 :TEST1
'* 機能  :主要(本体)処理
'---------------------------------------------------------------------------------------------------
'* 返り値 :(なし)
'* 引数  :(なし)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2004年11月07日
'* 作成者 :井上 治
'* 更新日 :2020年03月17日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:
'***************************************************************************************************
Public Sub TEST1()
    '-----------------------------------------------------------------------------------------------
    MsgBox "これは交換前のモジュールです。", , g_cnsTitle
End Sub

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

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

標準モジュール「Module1」は、

'101    ←バージョン情報(整数3桁) ※このバージョンによりModule自動更新が判断される
'***************************************************************************************************
'   モジュール入れ替え機能サンプル                                  Module1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'04/11/07(1.00)新規作成
'19/10/28(1.10)*.xlsm化
'20/03/17(1.11)記述修正(標準化準拠)、他
'***************************************************************************************************
Option Explicit
Option Private Module
'===================================================================================================
Public Const g_cnsTitle As String = "モジュール入れ替え機能"
' 交換用モジュールのファイル名
Public Const g_cnsModuleUpdFile As String = "REPLACE_Module1.bas"   ' 交換モジュールファイル
Public Const g_cnsModuleUpdSh As String = "設定"                    ' バージョン値格納シート
Public Const g_cnsModuleUpdRange As String = "$B$2"                 ' バージョン値格納セル
Public Const g_cnsModuleUpdModule1 As String = "Module1"            ' モジュール名

'***************************************************************************************************
'   ■■■ 外部公開プロシージャ ■■■
'***************************************************************************************************
'* 処理名 :TEST1
'* 機能  :主要(本体)処理
'---------------------------------------------------------------------------------------------------
'* 返り値 :(なし)
'* 引数  :(なし)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2004年11月07日
'* 作成者 :井上 治
'* 更新日 :2020年03月17日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:
'***************************************************************************************************
Public Sub TEST1()
    '-----------------------------------------------------------------------------------------------
    MsgBox "これは交換後のモジュールです。", , g_cnsTitle
End Sub

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

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

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

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

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

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

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

また、このような機能は、悪用を考えると「マクロウィルス」のようなことになってしまいます。
マイクロソフトは昨今、セキュリティ面での印象の改善に躍起になっており、その一環かも知れませんが、プロジェクトをマクロで操作するものを排除するような機能をExcel2002以降に実装しています。
初期設定のままで、本機能が働く状態になると、
プログラミングによるVisualBasicプロジェクトへのアクセスは信頼性に欠けます。
このように、実行時エラーになります。



このエラーメッセージでは対処が判らないので、今回の入れ替えモジュールでは、
マクロセキュリティ設定が必要です。
という内部メッセージに置き換えています。



ここでの対処はこのメッセージの通りですが、
セキュリティの設定変更
このように実行するPC側で「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」にチェックを付ける必要があります。

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

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

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

'***************************************************************************************************
'   モジュール入れ替え機能サンプル                                  modPERLACE_MODULE(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'   [参照設定]
'   ・Microsoft Scripting Runtime
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'04/11/07(1.00)新規作成
'19/10/28(1.10)*.xlsm化、64ビット版Office対応
'20/03/17(1.11)記述修正(標準化準拠)、他
'***************************************************************************************************
Option Explicit
Option Private Module
'===================================================================================================
Private Const g_cnsTitle As String = "モジュール自動更新"
Private Const g_cnsUpdGuide As String = "新しいバージョンの更新用モジュール(VBA)が存在します。"
#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

'***************************************************************************************************
'   ■■■ 外部公開プロシージャ ■■■
'***************************************************************************************************
'* 処理名 :REPLACE_MODULE
'* 機能  :Module入れ替え機能
'---------------------------------------------------------------------------------------------------
'* 返り値 :処理成否(Boolean)
'* 引数  :Arg1 = 入れ替えモジュールのファイル名(String,パスは自ブック所在に固定)
'*      Arg2 = バージョン情報の収容シート名(String)
'*      Arg3 = バージョン情報の収容セルアドレス(String)
'*      Arg4 = バージョン情報の収容シートの保護パスワード(String)
'*      Arg5 = 入れ替え対象モジュール名(String)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2004年11月07日
'* 作成者 :井上 治
'* 更新日 :2020年03月17日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:モジュール入替え時及び処理失敗時はExcelが終了します(VBA動作が不安定となるため)
'***************************************************************************************************
Public Function REPLACE_MODULE(ByVal strModuleFile As String, _
                               ByVal strSheetName As String, _
                               ByVal strRangeAddr As String, _
                               ByVal strPassWord As String, _
                               ByVal strModule As String) As Boolean
    '-----------------------------------------------------------------------------------------------
    Dim objFso As FileSystemObject                                  ' FileSystemObject
    Dim objTs As TextStream                                         ' TextStream
    Dim objWbk As Workbook                                          ' 本ブック
    Dim objMSGStyle As VbMsgBoxStyle                                ' メッセージアイコン
    Dim objVerSh As Worksheet                                       ' バージョン情報所在シート
    Dim objVBProject As Object                                      ' VBProject
    Dim objVBComponent As Object                                    ' VBComponent
    Dim lngVerN As Long                                             ' バージョン値(新)
    Dim lngVerO As Long                                             ' バージョン値(旧)
    Dim lngLines As Long                                            ' 行番号
    Dim blnOpen As Boolean                                          ' テキストOpen判定
    Dim blnClose As Boolean                                         ' Close指示
    Dim blnInteractive As Boolean                                   ' Interactive状態
    Dim strRec As String                                            ' テキストレコード
    Dim strFilename As String                                       ' 更新モジュールファイル名
    Dim strMSG As String                                            ' メッセージWORK
    Dim strErrMSG As String                                         ' エラ-メッセージ
    Dim strStep As String                                           ' 処理ステップ(表示用)
    REPLACE_MODULE = False
    Set objFso = New FileSystemObject
    Set objWbk = ThisWorkbook
    objMSGStyle = vbExclamation
    blnInteractive = Application.Interactive
    On Error GoTo REPLACE_MODULE_ERROR
    '-----------------------------------------------------------------
    strStep = "000"
    strFilename = objFso.BuildPath(objWbk.Path, strModuleFile)
    ' 同一フォルダに更新モジュールがないか、ブックが読み取り専用の時は終了(正常扱い)
    If Not objFso.FileExists(strFilename) Or objWbk.ReadOnly Then
        REPLACE_MODULE = True
        GoTo REPLACE_MODULE_EXIT
    End If
    strStep = "001"
    ' 更新モジュールファイルをテキストとしてOpen
    Set objTs = objFso.OpenTextFile(strFilename, ForReading, False, TristateUseDefault)
    blnOpen = True
    ' 1REC目は無視(Attribute VB_Name = "Module1")
    objTs.ReadLine
    ' レコード取得(2レコード目)
    strRec = objTs.ReadLine
    objTs.Close
    blnOpen = False
    strStep = "002"
    ' 新Version取得(2桁目から3桁の数値(整数)として取得)
    lngVerN = Val(Trim(Mid(strRec, 2)))
    Set objVerSh = objWbk.Worksheets(strSheetName)
    strStep = "003"
    ' 旧Version取得(指定セル:数値(整数)であること)
    lngVerO = Val(objVerSh.Range(strRangeAddr).Value)
    ' バージョンの新旧確認(更新済み等は正常終了)
    If lngVerO >= lngVerN Then
        REPLACE_MODULE = True
        GoTo REPLACE_MODULE_EXIT
    End If
    '=================================================================
    ' ※新しいバージョンのモジュールが見つかった時の確認処理
    '-----------------------------------------------------------------
    strStep = "010"
    ' 他ブックが開いている時は警告終了
    If Application.Workbooks.Count > 1 Then
        strErrMSG = g_cnsUpdGuide & vbCr
        strErrMSG = strErrMSG & "モジュールの更新後、一旦Excelを終了するため" & vbCr
        strErrMSG = strErrMSG & "他のブックを閉じてから再度起動して下さい。"
        objMSGStyle = vbInformation
        blnClose = True
        GoTo REPLACE_MODULE_EXIT
    End If
    ' モジュール入替えメッセージ
    strMSG = g_cnsUpdGuide & vbCr
    strMSG = strMSG & "  ( Ver" & Format(lngVerO, "000")
    strMSG = strMSG & " ⇒ Ver" & Format(lngVerN, "000") & " )" & vbCr
    strMSG = strMSG & "モジュールの更新を行ないますか?" & vbCr & vbCr
    strMSG = strMSG & "※更新後は上書き保存され一旦終了します。"
    ' モジュール入替えの確認(一旦Excelが終了するため)⇒キャンセルは不成功
    If MsgBox(strMSG, vbInformation + vbYesNo, g_cnsTitle) <> vbYes Then GoTo REPLACE_MODULE_EXIT
    '=================================================================
    ' ※モジュール入れ替え処理(本体)
    '-----------------------------------------------------------------
    strStep = "020"
    ' モジュール更新開始
    Application.StatusBar = "モジュール更新中....( →Ver" & Format(lngVerN, "000") & ")"
    ' VBプロジェクトを取得し、Moduleを入れ替える
    On Error Resume Next
    Set objVBProject = objWbk.VBProject
    ' Excelのセキュリティ設定によるエラー処置
    If ((Err.Number = 1004) And (Left(Err.Description, 7) = "プログラミング")) Then
        strErrMSG = "マクロセキュリティ設定が必要です。" & vbCr & vbCr
        strErrMSG = strErrMSG & "Excelリボンの「ファイル」タブから「オプション」を開き、" & vbCr
        strErrMSG = strErrMSG & "「トラストセンター(セキュリティセンター)の設定」へ進み、" & vbCr
        strErrMSG = strErrMSG & "「マクロの設定」にある" & vbCr
        strErrMSG = strErrMSG & "「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」に" & vbCr
        strErrMSG = strErrMSG & "チェックを付けてから本ブックを開いて下さい。"
        blnClose = True
        GoTo REPLACE_MODULE_EXIT
    End If
    On Error GoTo REPLACE_MODULE_ERROR
    strStep = "030"
    Set objVBComponent = objVBProject.VBComponents(strModule)
    ' 更新中のエラーを避けるためイベントを停止
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Interactive = False
    blnInteractive = False
    strStep = "031"
    ' Workbook側でModule1内プロシージャを参照しているため、
    ' Removeできないので、コードを書き換え一旦Excelを終了する
    With objVBComponent.CodeModule
        ' コードを削除(全行)
        lngLines = .CountOfLines
        If lngLines <> 0 Then .DeleteLines 1, lngLines
        ' Module1をBASファイルからインポートする
        .AddFromFile strFilename
    End With
    Set objVBComponent = Nothing
    Set objVBProject = Nothing
    strStep = "032"
    ' バージョンのセット
    With objVerSh
        If .ProtectContents = True Then
            .Unprotect strPassWord
            .Protect strPassWord, UserInterfaceOnly:=True
        End If
        .Range(strRangeAddr).Value = lngVerN
    End With
    '=================================================================
    ' ※終了処理(上書き保存させてExcelを終了)
    '-----------------------------------------------------------------
    strStep = "040"
    ' 上書き保存
    Application.StatusBar = False
    Application.EnableEvents = True
    objWbk.Save
    Sleep 200                           ' 処理間隔を空ける
    Application.Interactive = True
    Application.ScreenUpdating = True
    ' コンパイル済みモジュールを入れ替えているため、
    ' このまま動作させず一旦Excelを終了する
    strMSG = "モジュールの更新は正常に終了しました。" & vbCr
    strMSG = strMSG & "一旦、終了します。起動し直して下さい。" & vbCr
    strMSG = strMSG & "(このExcelブックだけもう一度起動して下さい)"
    MsgBox strMSG, vbInformation, g_cnsTitle
    ' 終了
    Application.Quit
    objWbk.Close False
    End

'===================================================================================================
' エラートラップ
REPLACE_MODULE_ERROR:
    ' 実行時エラー発生時に処理ステップ番号を含めて表示させます。
    strErrMSG = Err.Description & " Step:" & strStep
    objMSGStyle = vbCritical

'===================================================================================================
' 終了
REPLACE_MODULE_EXIT:
    If blnOpen Then objTs.Close
    Set objTs = Nothing
    Set objFso = Nothing
    ' Interactive復旧
    If Not blnInteractive Then
        Application.Interactive = True
        Application.EnableEvents = True
        Application.ScreenUpdating = True
    End If
    ' エラーメッセージがあれば表示
    If strErrMSG <> "" Then
        MsgBox strErrMSG, objMSGStyle, g_cnsTitle
    End If
    Application.StatusBar = False
    On Error GoTo 0
    ' Close指示か
    If blnClose Then
        objWbk.Saved = True
        objWbk.Close False
    End If
End Function

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

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