【ExcelVBA】自分自身のVBAのソースコードから文字列を一括検索するには

この記事では、自分自身のVBAのソースコードから文字列を一括検索する方法についてご説明します。

【動画】自分自身のVBAのソースコードから文字列を一括検索する実際の動き

本題に入る前に、まずは次の動画をご覧ください。


VBProjectのVBComponentsコレクション内からLinesプロパティでソースコードを1行ずつ取得します。

取得したソースコードに検索する文字列が含まれるか確認し、含まれていたらそのソースコードをシートに出力しています。(G列)

なお、動画内のサンプルではソースコードだけでなく、Excelのファイル名やモジュール名、スコープやプロシージャ名などもあわせてシートに出力しています。

マクロ作成の流れ

STEP.1
VBComponentsコレクションから、モジュール名を取得する
VBComponentsコレクションから、モジュール名を取得します。
STEP.2
CodeModuleプロパティからLineプロパティを使ってソースコードを取得する
CodeModuleプロパティからLineプロパティを使ってソースコードを取得します。
STEP.3
STEP.2で取得したソースコードに検索する文字列が含まれているかを判定し、含まれていればシートに出力する
STEP.3で取得したソースコードに検索する文字列が含まれているかを判定し、含まれていればシートに出力します。
STEP.4
モジュール、プロシージャの数だけSTEP.2からSTEP.3まで繰り返す
モジュール、プロシージャの数だけSTEP.2からSTEP.3まで繰り返します。

Excelファイルの例

今回は次のExcelファイルを用意しました。

黄色いセルA2に検索したい文字列を入力します。

入力後に実行ボタンをクリックすると、検索して見つかったソースコードをA4の行から始まる表内に出力されます。

このサンプルでは他にも、モジュール名、スコープやプロシージャ名などもあわせてシートに出力されます。

【注意】VBComponentsコレクションを扱うにはある設定を行わないとエラーになり動作しない

VBComponentsコレクションを扱うにはある設定を行わないとエラーになり動作しません。

設定を行わないままVBComponentsコレクションを呼び出そうとすると下のようにエラーが発生して正常に動作しません。

ではその必要な設定はというと、次の画面(トラストセンター画面)で行う「VBA プロジェクト オブジェクト モデルへのアクセスを信頼する」のチェックを付ける設定です。

このチェックを付けておかないとエラーが先ほどのエラーが発生してしまうので、必ずチェックをつけましょう。

なお、チェックを付けるとセキュリティ面が弱くなってしまうデメリットがあるので、もし本マクロを使わないときはチェックを外しておきましょう。

補足

ただし、本マクロを使い終わった後のチェックの有無設定はExcelを使用している端末の環境の仕様を最優先し、その仕様に合わせて行って下さい。
あくまでExcelの設定はお使いの環境の仕様を絶対に守るようお使いくださいね。

トラストセンター画面の開き方及び「マクロの設定」の表示方法

トラストセンター画面の開き方及び「マクロの設定」の表示方法は次の通りです。

①「ファイル」をクリックする

「ファイル」をクリックします。

②「オプション」をクリックする

「オプション」をクリックします。

③「Excelのオプション」画面でトラストセンターをクリックしトラストセンターボタンをクリックする

「Excelのオプション」画面が表示されたら、「トラストセンター」をクリックします。

するとトラストセンターに関する画面が表示されるので、その中にある「トラストセンターの設定」ボタンをクリックします。

④トラストセンター画面の開き方及び「マクロの設定」が表示される

「トラストセンター」画面が表示され、「マクロの設定」をクリックすると、「マクロの設定」に関する画面が表示されます。

手順は以上になります。

コードの例

Option Explicit

Private Sub btn_exec_Click()

    Dim rowCnt          As Long         '値を出力する行位置
    Dim thisWb          As Workbook     '本マクロ用のワークブック用変数
    Dim ws              As Worksheet    'ワークシート用変数
    Dim filePath        As String       'VBAオブジェクトモジュールの名称を取得したいExcelファイルの格納先
    Dim buf             As String       'Excelのファイル名を受け取る用の一時格納用変数
    Dim fileName        As String       'ファイル名
    Dim wb              As Workbook     'ワークブック用変数
    Dim vbComp          As Object       'VBProjectのVBComponentsコレクション用変数
    Dim aryCnt          As Long         '配列用カウンタ
    Dim mdlNM           As String       'モジュール名
    Dim obj             As Object       'CodeModuleオブジェクトを格納するオブジェクト変数
    Dim cnt             As Long         'カウンタ
    Dim procName        As String       'プロシージャ名用変数
    Dim scopeStr        As String       'スコープ用変数
    Dim kindStr         As String       '種類用変数
    
    Dim meMdl           As String       '自分自身のモジュール
    
    'データを出力する先頭行位置
    Const bgnRowPos As Long = 5
        
    'プロシージャの数をカウントする用のカウンタを初期化する
    rowCnt = bgnRowPos
    
    '本マクロのブックを取得する
    Set thisWb = ActiveWorkbook
    
    '本マクロのブックのシート名を取得する
    Set ws = thisWb.Worksheets("work")
    
    'モジュールに関する各情報を出力するセルをクリアする
    ws.Range("A" & bgnRowPos & ":G1000").ClearContents
            
    ' 配列を初期化
    ReDim myArray(0)
    
    'VBProjectのVBComponentsコレクション内の各VBComponentに対して以下を繰り返すFor文
    For Each vbComp In ThisWorkbook.VBProject.VBComponents
    
        '動的配列の再宣言を行う(配列に格納されている値を残したまま)
        ReDim Preserve myArray(aryCnt)
        
        '取得したモジュール名を配列に格納する
        myArray(aryCnt) = vbComp.Name
        
        'カウンタを1増やす
        aryCnt = aryCnt + 1
        
    Next vbComp
    
    For aryCnt = 0 To UBound(myArray)
                
        'モジュール名を配列myArrayから取得する
        mdlNM = myArray(aryCnt)
    
        '自分自身でVBEを操作するために使うCodeModuleオブジェクトを変数objに代入する
        Set obj = ThisWorkbook.VBProject.VBComponents.Item(mdlNM).CodeModule
        
        With obj

            '対象モジュールの行数分ループするfor文
            For cnt = 1 To .CountOfLines

                'プロシージャ名を取得してprocNameに格納する
                procName = .ProcOfLine(cnt, 0)
                
                If obj.Lines(cnt, 1) <> "" Then
                
                    '行が空白でない場合
                    
                    'スコープの取得
                    
                    Select Case True
                                            
                        Case InStr(LCase(.Lines(cnt, 1)), "public ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「public 」が含まれている場合
                            
                            '変数scopeStrに「public」を格納する
                            scopeStr = "public"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "private ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「private 」が含まれている場合
                            
                            '変数scopeStrに「private」を格納する
                            scopeStr = "private"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "friend ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「Friend 」が含まれている場合
                            
                            '変数scopeStrに「friend」を格納する
                            scopeStr = "Friend"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "static ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「Static 」が含まれている場合
                            
                            '変数scopeStrに「static」を格納する
                            scopeStr = "static"
                            
                    End Select

                    '種類の取得
                    
                    Select Case True
                    
                        Case InStr(LCase(.Lines(cnt, 1)), "sub ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「sub 」が含まれている場合
                            
                            '変数kindStrに「sub」を格納する
                            kindStr = "sub"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "function ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「function 」が含まれている場合
                            
                            '変数kindStrに「function」を格納する
                            kindStr = "function"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property let ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property let 」が含まれている場合
                            
                            '変数kindStrに「Property Let」を格納する
                            kindStr = "Property Let"
                                                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property set ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property set 」が含まれている場合
                            
                            '変数kindStrに「property set」を格納する
                            kindStr = "Property Set"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property get ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property get 」が含まれている場合
                            
                            '変数kindStrに「Property Get」を格納する
                            kindStr = "Property Get"
                            
                    End Select
                    
                    If InStr(LCase(obj.Lines(cnt, 1)), LCase(ws.Range("findStr").value)) > 0 Then
                    
                        'A列のセルに「項番」を出力する
                        ws.Range("A" & rowCnt).value = rowCnt - bgnRowPos + 1

                        If mdlNM <> "" Then
                        
                            'mdlNMがブランクではない場合
                        
                            'B列のセルにモジュール名を出力する
                            ws.Range("B" & rowCnt).value = mdlNM
                            
                            'mdlNMをブランクにする
                            mdlNM = ""
                        
                        End If
                                                                        
                        If scopeStr <> "" Then
                        
                            'scopeStrに値が存在する場合
                            
                            If procName <> "" Then
                            
                                'プロシージャではない場合
                        
                                'C列のセルに「スコープ」を出力する
                                ws.Range("C" & rowCnt).value = scopeStr
                            
                            End If
                        
                        End If
                        
                        If kindStr <> "" Then
                        
                            'kindStrに値が存在する場合
                            
                            If procName <> "" Then
                            
                                'プロシージャではない場合
                        
                                'D列のセルに「種類」を出力する
                                ws.Range("D" & rowCnt).value = kindStr
                            
                            End If
                            
                        End If
                        
                        'F列のセルにプロシージャ名を出力する
                        ws.Range("E" & rowCnt).value = procName

                        '「コード内から検索する文字列」を含んだソースコードが何行目にあるのか出力する
                        ws.Range("F" & rowCnt).value = cnt
                        
                        '「コード内から検索する文字列」を含んだソースコードを出力する
                        ws.Range("G" & rowCnt).value = .Lines(cnt, 1)
                        
                        'rowCntの値を1つ増やす
                        rowCnt = rowCnt + 1
                        
                    End If
                    
                End If

                DoEvents

            Next cnt

        End With
        
    Next

    Set obj = Nothing
    
    If rowCnt = bgnRowPos Then
    
        'モジュールが1件もない場合(rowCntの初期値がbgnRowPosの値なので)
        
        MsgBox "検索した文字列が見つかりませんでした"
    
    End If
    
    '後処理
    Set obj = Nothing
    Set wb = Nothing
        
End Sub

注目すべきコード①

最初に見て頂きたいのは42行目から53行目です。

    'VBProjectのVBComponentsコレクション内の各VBComponentに対して以下を繰り返すFor文
    For Each vbComp In ThisWorkbook.VBProject.VBComponents
    
        '動的配列の再宣言を行う(配列に格納されている値を残したまま)
        ReDim Preserve myArray(aryCnt)
        
        '取得したモジュール名を配列に格納する
        myArray(aryCnt) = vbComp.Name
        
        'カウンタを1増やす
        aryCnt = aryCnt + 1
        
    Next vbComp

コードの説明

以上のコードは、モジュール名を取得してそのモジュール名を配列に格納する処理のコードです。

コードの詳細

65行目のコードでは、配列myArrayを要素数0で初期化しています。

68行目では、VBProjectのVBComponentsコレクション内の各VBComponentに対して処理を繰り返すFor文を用意し、vbcompにはモジュールが格納されます。

71行目では動的配列の再宣言を行い、74行目ではnameプロパティからモジュールの名称を取得して配列myArrayに格納します。

For文内の処理が全て完了すると、Excelのモジュール名が全て取得されます。

注目すべきコード②

次に見て頂きたいのは61行目です。

        '自分自身でVBEを操作するために使うCodeModuleオブジェクトを変数objに代入する
        Set obj = ThisWorkbook.VBProject.VBComponents.Item(mdlNM).CodeModule

コードの説明

以上のコードは、VBComponentsプロパティから取得したVBComponentオブジェクトのCodeModuleオブジェクトを取得する処理のコードです。

CodeModuleオブジェクトを取得して変数objに格納していますが、このオブジェクトによりVBAのソースコードを参照することができます。

ソースコード内にあるプロシージャを取得するのにこのオブジェクトが必要になります。

注目すべきコード③

次に見て頂きたいのは63行目から69行目です。

        With obj
            '対象モジュールの行数分ループするfor文
            For cnt = 1 To .CountOfLines
                'プロシージャ名を取得してprocNameに格納する
                procName = .ProcOfLine(cnt, 0)

コードの説明

以上のコードは、VBAのソースコードの行数分ループさせるループ処理のコードと、プロシージャ名を取得するコードです。

CountOfLinesプロパティはVBAのソースコードの行数のことです。

例えば、VBAのソースコードが10行の場合、CountOfLinesプロパティは10の値を返します。

また、ProcOfLineメソッドはプロシージャ名を取得するメソッドです。

取得したら変数procNameに格納します。

このprocNameの値はシートにプロシージャ名を出力するのに使います。

注目すべきコード④

次に見て頂きたいのは71行目から73行目です。

                If obj.Lines(cnt, 1) <> "" Then
                
                    '行が空白でない場合

コードの説明

以上のコードは、コードの行が空白でないか判定するために必要な条件文のIFです。

コードの行からプロシージャ名を取得するので、空白行は対象外にします。

注目すべきコード⑤

次に見て頂きたいのは77行目から107行目です。

                    'スコープの取得
                    
                    Select Case True
                                            
                        Case InStr(LCase(.Lines(cnt, 1)), "public ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「public 」が含まれている場合
                            
                            '変数scopeStrに「public」を格納する
                            scopeStr = "public"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "private ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「private 」が含まれている場合
                            
                            '変数scopeStrに「private」を格納する
                            scopeStr = "private"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "friend ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「Friend 」が含まれている場合
                            
                            '変数scopeStrに「friend」を格納する
                            scopeStr = "Friend"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "static ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「Static 」が含まれている場合
                            
                            '変数scopeStrに「static」を格納する
                            scopeStr = "static"
                            
                    End Select

コードの説明

以上のコードは、スコープを取得するコードの処理です。

プロシージャに使われているスコープ(public、private、friend、static)がどれなのかを判定します。

スコープがどれなのかは、行に対してInStr関数を使って判定します。

コードの詳細

79行目のコードでは、InStrの引数に「public 」を指定し、戻り値が0以上の場合はスコープがpublicなので、84行目で変数scopeStrに「public」を格納しています。

ただしpublicの文字列が「Public」のように大文字小文字が混ざっているかもしれないので、LCase関数を使って文字全てを小文字に変換してからInstr関数を実行しています。

なお、変数scopeStrの値は、シートの表のC列に出力するのに使います。

86行目のコードでは、InStrの引数に「private 」を指定し、戻り値が0以上の場合はスコープがprivateなので、91行目で変数scopeStrに「private」を格納しています。

93行目のコードでは、InStrの引数に「friend 」を指定し、戻り値が0以上の場合はスコープがfriendなので、98行目で変数scopeStrに「friend」を格納しています。

100行目のコードでは、InStrの引数に「static 」を指定し、戻り値が0以上の場合はスコープがstaticなので、105行目で変数scopeStrに「static」を格納しています。

注目すべきコード⑥

次に見て頂きたいのは111行目から148行目です。

                    '種類の取得
                    
                    Select Case True
                    
                        Case InStr(LCase(.Lines(cnt, 1)), "sub ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「sub 」が含まれている場合
                            
                            '変数kindStrに「sub」を格納する
                            kindStr = "sub"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "function ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「function 」が含まれている場合
                            
                            '変数kindStrに「function」を格納する
                            kindStr = "function"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property let ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property let 」が含まれている場合
                            
                            '変数kindStrに「Property Let」を格納する
                            kindStr = "Property Let"
                                                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property set ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property set 」が含まれている場合
                            
                            '変数kindStrに「property set」を格納する
                            kindStr = "Property Set"
                            
                        Case InStr(LCase(.Lines(cnt, 1)), "property get ") > 0
                        
                            'プロシージャ名が含まれているソースコードに「property get 」が含まれている場合
                            
                            '変数kindStrに「Property Get」を格納する
                            kindStr = "Property Get"
                            
                    End Select

コードの説明

以上のコードは、プロシージャまたはpropertyの種類を取得するコードの処理です。

プロシージャに使われているプロシージャまたはpropertyの種類(sub、function、property let、Property Set、Property Get)がどれかなのかを判定します。

スコープがどれなのかは、行に対してInStr関数を使って判定します。

コードの詳細

113行目のコードでは、InStrの引数に「sub 」を指定し、戻り値が0以上の場合はプロシージャの種類がsubなので、118行目で変数scopeStrに「sub」を格納しています。

ただしsubの文字列が「Sub」のように大文字小文字が混ざっているかもしれないので、LCase関数を使って文字全てを小文字に変換してからInstr関数を実行しています。

なお、変数kingStrの値は、シートの表のD列に出力するのに使います。

120行目のコードでは、InStrの引数に「function 」を指定し、戻り値が0以上の場合はプロシージャの種類がfunctionなので、125行目で変数scopeStrに「function」を格納しています。

127行目のコードでは、InStrの引数に「property let 」を指定し、戻り値が0以上の場合はpropertyの種類がproperty letなので、132行目で変数scopeStrに「property let」を格納しています。

134行目のコードでは、InStrの引数に「property set 」を指定し、戻り値が0以上の場合はpropertyの種類がproperty setなので、139行目で変数scopeStrに「property set」を格納しています。

141行目のコードでは、InStrの引数に「property get 」を指定し、戻り値が0以上の場合はpropertyの種類がproperty getなので、146行目で変数scopeStrに「property get」を格納しています。

注目すべきコード⑦

次に見て頂きたいのは176行目です。

                    If InStr(LCase(obj.Lines(cnt, 1)), LCase(ws.Range("findStr").value)) > 0 Then

コードの説明

以上のコードは、ソースコードに検索する文字列が含まれているかを判定しているコードです。

もし検索する文字列が含まれている場合は、「注目すべきコード⑧」の処理に進みます。

ソースコードに検索する文字列が含まれているかを判定するにはInStr関数を使い、0以上の場合は含まれているので0以上かどうかをIf文で判定しています。

注目すべきコード⑧

次に見て頂きたいのは153行目です。

                        'A列のセルに「項番」を出力する
                        ws.Range("A" & rowCnt).value = rowCnt - bgnRowPos + 1

コードの説明

以上のコードは、A列のセルに「項番」を出力するコードです。

注目すべきコード⑨

次に見て頂きたいのは155行目から165行目です。

                        If mdlNM <> "" Then
                        
                            'mdlNMがブランクではない場合
                        
                            'B列のセルにモジュール名を出力する
                            ws.Range("B" & rowCnt).value = mdlNM
                            
                            'mdlNMをブランクにする
                            mdlNM = ""
                        
                        End If

コードの説明

以上のコードは、B列のセルにモジュール名を出力するコードです。

mdlNMがブランクでない場合はB列のセルにモジュール名を出力し、ブランクの場合は特に何もしません。

注目すべきコード⑩

次に見て頂きたいのは167行目から180行目です。

                        If scopeStr <> "" Then
                        
                            'scopeStrに値が存在する場合
                            
                            If procName <> "" Then
                            
                                'プロシージャではない場合
                        
                                'C列のセルに「スコープ」を出力する
                                ws.Range("C" & rowCnt).value = scopeStr
                            
                            End If
                        
                        End If

コードの説明

以上のコードは、C列のセルにスコープ名を出力するコードです。

scopeStrがブランクでない場合はC列のセルにスコープ名を出力し、ブランクの場合は特に何もしません。

注目すべきコード⑪

次に見て頂きたいのは182行目から195行目です。

                        If kindStr <> "" Then
                        
                            'kindStrに値が存在する場合
                            
                            If procName <> "" Then
                            
                                'プロシージャではない場合
                        
                                'D列のセルに「種類」を出力する
                                ws.Range("D" & rowCnt).value = kindStr
                            
                            End If
                            
                        End If

コードの説明

以上のコードは、D列のセルに種類を出力するコードです。

kindStrがブランクでない場合はD列のセルに種類を出力し、ブランクの場合は特に何もしません。

注目すべきコード⑫

次に見て頂きたいのは197行目から204行目です。

                        'F列のセルにプロシージャ名を出力する
                        ws.Range("E" & rowCnt).value = procName

                        '「コード内から検索する文字列」を含んだソースコードが何行目にあるのか出力する
                        ws.Range("F" & rowCnt).value = cnt
                        
                        '「コード内から検索する文字列」を含んだソースコードを出力する
                        ws.Range("G" & rowCnt).value = .Lines(cnt, 1)

コードの説明

以上のコードは、プロシージャ名、「コード内から検索する文字列」を含んだソースコードが何行目にあるのか、「コード内から検索する文字列」を含んだソースコードをセルに出力する処理のコードです。

コードの詳細

198行目のコードでは、E列のセルにプロシージャ名を出力します。

201行目のコードでは、F列のセルに「コード内から検索する文字列」を含んだソースコードが何行目にあるのか出力します。

204行目のコードでは、G列のセルに「コード内から検索する文字列」を含んだソースコードを出力します。

動作確認

今回は以下のExcelファイルを用意しました。

マクロを実行すると、自分自身のExcelファイルのVBAのソースコードから、「debug」の文字列を検索して特定して表に各情報を出力します。

「debug」の文字列の在り処

ThisWorkbookモジュールの9行目に「debug」の文字列が存在しています。

frm__0224のモジュールの5行目に「debug」の文字列が存在しています。

mdl__0224のモジュールの27行目に「debug」の文字列が存在しています。

cls_0224のモジュールの77行目に「debug」の文字列が存在しています。

マクロ実行後

マクロを実行すると、表にソースコードや、Excelのファイル名やモジュール名、スコープやプロシージャ名などが出力されました。

最後に

この記事では、自分自身のVBAのソースコードから文字列を一括検索する方法についてご説明しました。

VBAのソースコードから何か文字列を検索したい場合は、VBE画面を表示させてないといけません。

VBE画面を開いて検索するその手間が面倒だと感じている場合は本記事を参考にしてみてくださいね。

また、不要なコードやコメントを削除したい時、どこにあったかうろ覚え…といったときにサクッと探したい時にも使えるかと思います。

プログラミングのスキルを習得するなら

プログラミングのスキルを習得したい、今のスキルをもっと高めたい、そう考えているなら「プログラミングスクール」がおすすめです。

プログラミングのスキルの基礎を身につけるなら「TechAcademy」で1週間の無料体験があるので、これで「プログラミングの基礎」を学ぶのにおすすめですよ。

→ TechAcademyの「1週間 無料体験」はこちら