指定したフォルダ直下の画像からエビデンスのエクセルファイルを自動生成するマクロ

2020/01/26追記
**※この記事のコードはバグだらけだったので以下の記事でデバッグしています！**
[エビデンス生成マクロがバグだらけだったのでデバッグしました](https://qiita.com/10inoino/items/9347ceccea87831f97c4)
使い方は変わらないので、以下の記事は取扱説明書、もしくはデバッグ前の参照ソースコードとして読んでもらえると幸いです:bow_tone1:

------------------------------


エビデンスって取るのめんどくさいですよね

最近はテストを自動化するツールなんかもあったりするみたいですが、それでも導入できずに手動でテストをしていたり、その他の理由でエビデンスを取らなければならないという現場も少なくないのではないでしょうか

最近、VBAやGASの勉強を始めまして、エビデンスのエクセルファイルを作成する作業だけでもVBAで自動化出来ないのかと思っていたら案の定ありました。

[Excelにエビデンスを張り付けるのを自動化した話。](https://qiita.com/snaruse0608/items/4d6022c43e65fe0eee4c)

クリップボードにある画像をエクセルに貼っていく感じですね。

ただ、これだとPCで撮ったエビデンスしか貼れません。

私のいる現場ではiOSやandroidの開発もあるので、ipadで撮ったスクリーンショットなども含めてエビデンスを作成したいと思い、勉強も兼ねて自分で作ってみました。

# 概要

任意のフォルダの直下にある画像ファイルの名前を読み取って、ブックの作成、シートの作成、エビデンスの貼り付けを自動で行います。

例えば、以下のように画像が保存してあるフォルダがあったとします。

<img width="780" alt="3.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/cebbbd77-6f7f-a7d2-0f79-c43e7b5c68d3.png">


このフォルダのpathを指定して、作成したいエビデンスの名前（今回はtest.xlsxとします）も指定すると、以下のようなエクセルファイルを作成します。

＜シート名：1＞
<img width="960" alt="1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/0c907c27-0bb4-016b-91df-5506d2f3e890.png">

＜シート名：2＞
<img width="960" alt="2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/0bf101ac-2772-d5cb-243d-041f1e8fc57b.png">

また、作成したエクセルファイルは画像があるpathの配下に保存されるので、マクロ実行後はこうなります。
<img width="788" alt="4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/ce8ca9df-5b08-d66d-b62d-e6ac91b39c71.png">


上記にあるPNG,JPG以外にも、TIF,BMPなどにも対応しています。

画像ファイル名の-(ハイフン)より前をシート名として、シートを作成しています。
ハイフンより前の文字列が同じ画像をハイフンより後ろの文字列の順に同じシートに貼り付けます。

実際に実行するとこんな感じになります。
![実行結果.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/289da44a-0b17-aa46-e3d8-e2da366a7cab.gif)

#コード
実際に書いたコードは以下の通りです。変数名をつけるセンスが無さすぎるので、いい変数名などご教授いただける優しい方がいらっしゃいましたら是非コメントお願いします。
リファクタリングなどのご意見も大歓迎です。

```vb:標準モジュール
Sub generate_sheet(evidence_name As String, PATH_ As String)

    Dim wb As Workbook '新しいworkbookを作成
    Set wb = Workbooks.Add
    Dim f_name As String '挿入する画像の名前
    Dim f_name_arr() As String '画像名の配列
    Dim swap As String 'ソート用のスワップ
    Dim i As Long 'Forループ用
    Dim j As Long 'Forループ用
    Dim k As Long 'Forループ用
    Dim Sheet_name As String '生成するシート名
    Dim ws As Worksheet 'ワークシート
    Dim sheet_flg As Boolean '生成しようとするシート名と同じシートがあるかどうかのフラグ
    Dim pic_exist_flg As Boolean '対象のファイルがあるかどうか判別するフラグ
    Dim file_name_flg As Boolean '指定されたディレクトリ配下に保存しようとしたファイル名と同名のファイルがあるか検索
    Dim pic_top As Long '挿入する画像のTop位置
    Dim shp As Shape '画像を一時的に代入するshape
    
    wb.Activate '生成したworkbookをアクティブにする
    
    '選択したpathの配下にファイルが無かったら抜ける
    f_name = Dir(PATH_ & "\*")
    If f_name = "" Then
        MsgBox PATH_ & "にはファイルがありません。"
        Application.Quit
        Exit Sub
    End If
    
    '配列にファイル名を詰める
    Do
        i = i + 1
        ReDim Preserve f_name_arr(1 To i)
        f_name_arr(i) = f_name
        f_name = Dir
    Loop While f_name <> ""
    
    'ソート
    For j = 1 To i
        For k = i To j Step -1
            If StrComp(f_name_arr(j), f_name_arr(k)) = 1 Then
                swap = f_name_arr(j)
                f_name_arr(j) = f_name_arr(k)
                f_name_arr(k) = swap
            End If
        Next k
    Next j
    
    '画像を挿入する
    For j = 1 To i
        f_name = f_name_arr(j)
        If f_name = evidence_name & ".xlsx" Then
            file_name_flg = True
        End If
        
        '拡張子がtif,tiff,png,jpg.jpeg,bmpじゃなかったら飛ばす
        If UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("png") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("jpg") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("jpeg") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("bmp") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("tif") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("tiff") Then
            'ファイル名に-（ハイフン）がなかったら飛ばす
            If InStr(f_name, "-") <> 0 Then
                Sheet_name = Left(f_name, InStr(f_name, "-") - 1)  '-(ハイフン)の前の文字列をファイル名とする
                pic_exist_flg = True
            Else
                GoTo Skip
            End If
        Else
            GoTo Skip
        End If
        
        sheet_flg = False  'sheet_flgの初期化
        '既に同じ名前のワークシートがあったらワークシートを生成しない
        For Each ws In Worksheets
            If ws.Name = Sheet_name Then
                sheet_flg = True
                Exit For
            End If
        Next ws
        
        '同名のワークシートがなければ、一番後ろにワークシートを生成
        If sheet_flg = False Then
            Worksheets.Add after:=Worksheets(Worksheets.Count)
            ActiveSheet.Name = Sheet_name
            pic_top = 20 '1枚目の画像は上から20px空ける
        End If
        
        '任意のワークシートに画像を挿入
        With Sheets(Sheet_name)
            Set shp = .Shapes.AddPicture( _
                Filename:=PATH_ & "\" & f_name, _
                linktofile:=False, _
                savewithdocument:=True, _
                Left:=20, _
                Top:=pic_top, _
                Width:=0, _
                Height:=0)
        End With
        
        With shp
            .ScaleHeight 1, msoTrue
            .ScaleWidth 1, msoTrue
            pic_top = pic_top + .Height + 20 '次の画像は20px空けたところに挿入する
        End With
Skip:
    Next
    
    If pic_exist_flg <> True Then
        MsgBox PATH_ & "には対象となるファイルがありません。"
        Application.Quit
        Exit Sub
    End If
    
    '最初から存在するシートを削除
    Application.DisplayAlerts = False
    Worksheets(1).Delete
    Application.DisplayAlerts = True
    
    '一枚目のシートをアクティブにする
    Worksheets(1).Activate
    
    If file_name_flg = True Then
        MsgBox PATH_ & "に同名のエクセルファイルがあります。" & vbCrLf & "ファイル名を変えて保存してください。"
    Else
        '指定したファイル名でエクセルファイルを保存
        wb.SaveAs _
        Filename:=PATH_ & "\" & evidence_name & ".xlsx", _
        FileFormat:=xlOpenXMLWorkbook
    End If
    
    'マクロを実行したエクセルファイルを閉じる
    Workbooks("evidence.xlsm").Close
End Sub
```

```vb:フォーム
Private Sub CommandButton1_Click()
    'ユーザーフォームのボタンを押したらシートを生成する関数を呼び出す
    Call generate_sheet(TextBox1.Text, TextBox2.Text)
    Unload Me
End Sub

Private Sub UserForm_Initialize()

    'ユーザーフォームが出ている間はエクセルファイルを見せない
    Application.Visible = False
    
    'ユーザーフォームのサイズ指定
    Application.WindowState = xlMaximized
    Me.Height = 220
    Me.Width = 450
    Me.Zoom = 100
End Sub

Private Sub UserForm_Terminate()
    'ユーザーフォームが閉じたらエクセルファイルを見せる
    Application.Visible = True
End Sub
```

```vb:ワークシート
Private Sub Workbook_Open()
    'ワークシートを開いた時にユーザーフォームを出す
    path_input.Show
End Sub
```


ユーザーフォームはこんな感じです。
<img width="428" alt="5.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508796/458c71cd-91de-f0e0-c54f-334dd2dc11a2.png">
FilenameのテキストボックスがTextBox1で、PathのテキストボックスがTextBox2になってます。


#流れの説明

まずワークシートが開かれた時にユーザーフォームが表示されます。

```vb
Private Sub Workbook_Open()
    'ワークシートを開いた時にユーザーフォームを出す
    path_input.Show
End Sub
```

そのユーザーフォームに作成したいエクセルファイルの名前と、貼り付けたい画像が保存してあるpathを入力して「エビデンス生成」のボタンを押すと、テキストボックスからそれぞれを取得して、関数を走らせます。

```vb
Private Sub CommandButton1_Click()
    'ユーザーフォームのボタンを押したらシートを生成する関数を呼び出す
    Call generate_sheet(TextBox1.Text, TextBox2.Text)
    Unload Me
End Sub
```

呼び出された関数で変数を定義するのと一緒に新しいワークブックを作成します。

```vb
    Dim wb As Workbook '新しいworkbookを作成
    Set wb = Workbooks.Add
    Dim f_name As String '挿入する画像の名前
    Dim f_name_arr() As String '画像名の配列
    Dim swap As String 'ソート用のスワップ
    Dim i As Long 'Forループ用
    Dim j As Long 'Forループ用
    Dim k As Long 'Forループ用
    Dim Sheet_name As String '生成するシート名
    Dim ws As Worksheet 'ワークシート
    Dim sheet_flg As Boolean '生成しようとするシート名と同じシートがあるかどうかのフラグ
    Dim pic_exist_flg As Boolean '対象のファイルがあるかどうか判別するフラグ
    Dim file_name_flg As Boolean '指定されたディレクトリ配下に保存しようとしたファイル名と同名のファイルがあるか検索
    Dim pic_top As Long '挿入する画像のTop位置
    Dim shp As Shape '画像を一時的に代入するshape
```

指定したpathの配下にファイルがなかったら処理を終了します。

```vb
    '選択したpathの配下にファイルが無かったら抜ける
    f_name = Dir(PATH_ & "\*")
    If f_name = "" Then
        MsgBox PATH_ & "にはファイルがありません。"
        Application.Quit
        Exit Sub
    End If
```


配列に指定したpath配下のファイルの名前を詰めていきます。ここではバリデーションをせずにとりあえず全部詰めます（後で使うので）。

```vb
    '配列にファイル名を詰める
    Do
        i = i + 1
        ReDim Preserve f_name_arr(1 To i)
        f_name_arr(i) = f_name
        f_name = Dir
    Loop While f_name <> ""
```

後の処理の都合でファイルが名前順に並んでくれないと困るので、ソートします。（おそらくフォルダ内で名前順にソートされているとは思うのですが）

```vb
    'ソート
    For j = 1 To i
        For k = i To j Step -1
            If StrComp(f_name_arr(j), f_name_arr(k)) = 1 Then
                swap = f_name_arr(j)
                f_name_arr(j) = f_name_arr(k)
                f_name_arr(k) = swap
            End If
        Next k
    Next j
```

ソートが終わったらいよいよシートを作成しつつ、画像を貼り付けます。
ここでバリデーションと保存しようとしているエクセルファイルの名前が既に存在するものではないかのチェックを行います。

バリデーションチェックを抜けたら、シート名となるハイフンより前の文字列を取り出します。

```vb
    '画像を挿入する
    For j = 1 To i
        f_name = f_name_arr(j)
        If f_name = evidence_name & ".xlsx" Then
            file_name_flg = True
        End If
        
        '拡張子がtif,tiff,png,jpg.jpeg,bmpじゃなかったら飛ばす
        If UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("png") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("jpg") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("jpeg") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("bmp") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("tif") Or _
            UCase(Right(f_name, InStr(f_name, ".") - 1)) = UCase("tiff") Then
            'ファイル名に-（ハイフン）がなかったら飛ばす
            If InStr(f_name, "-") <> 0 Then
                Sheet_name = Left(f_name, InStr(f_name, "-") - 1)  '-(ハイフン)の前の文字列をファイル名とする
                pic_exist_flg = True
            Else
                GoTo Skip
            End If
        Else
            GoTo Skip
        End If
```

ワークシートの名前をチェックし、作成しているワークシートと同名のものがなければ作成し、そのワークシートの一番上に画像を貼り付けます。

同名のワークシートがある場合はそのワークシートで貼り付けてある画像の下に新たな画像を貼り付けます。

この時にファイルがソートされていないと、画像を挿入する位置が変になるんです。

```vb
        sheet_flg = False  'sheet_flgの初期化
        '既に同じ名前のワークシートがあったらワークシートを生成しない
        For Each ws In Worksheets
            If ws.Name = Sheet_name Then
                sheet_flg = True
                Exit For
            End If
        Next ws
        
        '同名のワークシートがなければ、一番後ろにワークシートを生成
        If sheet_flg = False Then
            Worksheets.Add after:=Worksheets(Worksheets.Count)
            ActiveSheet.Name = Sheet_name
            pic_top = 20 '1枚目の画像は上から20px空ける
        End If
        
        '任意のワークシートに画像を挿入
        With Sheets(Sheet_name)
            Set shp = .Shapes.AddPicture( _
                Filename:=PATH_ & "\" & f_name, _
                linktofile:=False, _
                savewithdocument:=True, _
                Left:=20, _
                Top:=pic_top, _
                Width:=0, _
                Height:=0)
        End With
        
        With shp
            .ScaleHeight 1, msoTrue
            .ScaleWidth 1, msoTrue
            pic_top = pic_top + .Height + 20 '次の画像は20px空けたところに挿入する
        End With
Skip:
    Next
```

すべての画像を貼り終わったらその後の処理です。
まず、指定したpathの配下の画像がバリデーションでひっかかってしまった場合（画像ファイルでない、ファイル名にハイフンが入っていない）、アラートを表示させて処理を終了します。

```vb
    If pic_exist_flg <> True Then
        MsgBox PATH_ & "には対象となるファイルがありません。"
        Application.Quit
        Exit Sub
    End If
```

一枚でも新しいワークシートが作成できているのであれば、最初からあるワークシートは不要なので、削除して1枚目のワークシートをアクティブにします。

```vb
    '最初から存在するシートを削除
    Application.DisplayAlerts = False
    Worksheets(1).Delete
    Application.DisplayAlerts = True
    
    '一枚目のシートをアクティブにする
    Worksheets(1).Activate
```

保存しようとしているpath配下に同盟のエクセルファイルが存在する場合は、保存は行わず、メッセージボックスを表示させます。
同名のエクセルファイルが無い場合は保存します。

その後、マクロを実行しているエクセルファイルを閉じて終了です。

```vb
    If file_name_flg = True Then
        MsgBox PATH_ & "に同名のエクセルファイルがあります。" & vbCrLf & "ファイル名を変えて保存してください。"
    Else
        '指定したファイル名でエクセルファイルを保存
        wb.SaveAs _
        Filename:=PATH_ & "\" & evidence_name & ".xlsx", _
        FileFormat:=xlOpenXMLWorkbook
    End If
    
    'マクロを実行したエクセルファイルを閉じる
    Workbooks("evidence.xlsm").Close
End Sub
```

#やってみて
最初はvbaで別のものを作ろうとしていたのですが、触ってたらエビデンスってvbaでなんとかなんないの？っていう思いつきから作ってみた偶然の産物です。

いざ始めてみたらコーディングは1日で出来たので、vbaって意外に簡単にできるんだなっていうのが率直な感想ですね。

ただ、まだまだペーペーのエンジニアなので、色んな人からフィードバックもらいたいなと思ってQiitaに投稿してみました！

是非コメントで色々ご意見いただけると幸いです！

#追記
[【備忘録】Excel2010以降のVBAで画像の実体を挿入する](https://qiita.com/11295/items/c3b2dcadc7af0e2f1b3f)

こちらの記事にあるように、「ActiveSheet.Pictures.Insert」だと画像がリンク貼り付けになっちゃうので、作成したエビデンスを人に渡したりすると画像が見れない！っていうとんでもないバグが見つかりました。

完全に考慮不足…

ということで、「ActiveSheet.Pictures.Insert」から「ActiveSheet.Shapes.AddPicture」に直しました。