散らばった資料を一気に集めて、Excelを汚しながらレビューするためのバッチ+VBA集

概要

指定の場所から仕様書ファイルをコピーしたり、あちこちに散らばった修正対象の複数の単語を検索して、あるかないか調べるのがいやになる瞬間がありませんか。

そんな私のために下記のツールを作りました。
・散らばった資料を「単語指定」で一気にコピーする PowerShell
・Excelレビューを爆速化するハイライト系マクロ
・シート移動・確認を楽にする小ネタVBA

毎回AIで作るのも時間かかるのでね(デバッグ込みで2-4時間)、さっき作って手元の個人PCでも動いたやつを置いておきます。
AIはPowerShellとバッチ苦手ですよね…。よく止まるし50行ぐらいのファイルで人力デバッグいるし。
(PHPやJavaScriptは4000行あっても行けるのに…)

コードはダブルクリックで全選択できます。品質をあげるために使ってみてください。

環境

Windows11
PowerShell 5.1
Microsoft Office Professional Plus 2019

bat+ps1

あちこちに散らばった大量のファイル・フォルダを「3秒でコピーする」bat+ps1

単語を入力すると、部分一致で、指定の場所から接頭辞つきでコピーしてくれます。

bat実行場所に単語ごとにフォルダを作成します

「iフォルダ」の中に入ると重複には親ディレクトリ名が、ファイル重複には「_1」がつきます。
(原本と比較出来るように接頭辞【aaa】がつきます、あと無意識の上書き対策)

パス:ps1にハードコードしています
入力:半角スペース区切りで日本語も受け付けます
検索先:
ハードコピーしたパスの第一階層の中で、部分一致するファイル名とフォルダ名を検索します
※深い階層まではもぐりません、置き場が決まってて命名規則があるファイル郡を取る必要がある場合を想定しています。

動作が分かりづらいと思うので簡単に書いてみます。

ハードコードパス:
C:\エビデンス\
D:\仕様書\

入力「di fi i」

コピー元:
C:\エビデンス\dir1\file1.txt
C:\エビデンス\dir1\file2.txt
C:\エビデンス\dir1\aaa.txt
C:\エビデンス\file1.txt
C:\エビデンス\file1.txt
C:\エビデンス\aaa.txt

D:\仕様書\dir1\file1.txt
D:\仕様書\dir1\file2.txt
D:\仕様書\dir1\aaa.txt
D:\仕様書\file1.txt
D:\仕様書\file1.txt
D:\仕様書\aaa.txt

コピー先:
di/dir1/【aaa】file1.txt
di/dir1/【aaa】file2.txt
di/dir1/【aaa】aaa.txt

fi/【aaa_1】file1.txt
fi/【aaa_1】file2.txt
fi/【aaa】file1.txt
fi/【aaa】file2.txt

i/dir1_エビデンス/【aaa】file1.txt
i/dir1_エビデンス/【aaa】file2.txt
i/dir1_エビデンス/【aaa】aaa.txt
i/dir1_仕様書/【aaa】file1.txt
i/dir1_仕様書/【aaa】file2.txt
i/dir1_仕様書/【aaa】aaa.txt
i/【aaa_1】file1.txt
i/【aaa_1】file2.txt
i/【aaa】file1.txt
i/【aaa】file2.txt

コード本体(とても長いのでスキップ推奨)

copy.bat (Shift-jisで保存)

@echo off
setlocal

powershell -NoProfile -ExecutionPolicy Bypass ^
  -File "%~dp0search.ps1"

pause
endlocal

search.ps1 (UTF-8 BOMありで保存)

param(
    [string]$Line
)

function Convert-ToSafeName {
    param([string]$Name)
    if ($null -eq $Name) {
        return ""
    }
    return ($Name -replace '[\\/:*?"<>|]', '_')
}

function Get-RelativePathSafe {
    param(
        [string]$BasePath,
        [string]$TargetPath
    )

    $baseFull = [System.IO.Path]::GetFullPath($BasePath)
    $targetFull = [System.IO.Path]::GetFullPath($TargetPath)

    $mi = [System.IO.Path].GetMethod("GetRelativePath", [Type[]]@([string], [string]))
    if ($mi) {
        return [System.IO.Path]::GetRelativePath($baseFull, $targetFull)
    }

    if (-not $baseFull.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
        $baseFull += [System.IO.Path]::DirectorySeparatorChar
    }

    $baseUri = [System.Uri]::new($baseFull)
    $targetUri = [System.Uri]::new($targetFull)
    $relUri = $baseUri.MakeRelativeUri($targetUri)
    $rel = [System.Uri]::UnescapeDataString($relUri.ToString())
    return ($rel -replace '/', [System.IO.Path]::DirectorySeparatorChar)
}

function Add-Target {
    param(
        [System.Collections.Generic.List[object]]$List,
        [string]$Word,
        [string]$Source,
        [string]$MatchRoot,
        [string]$GroupBase,
        [string]$ParentName,
        [bool]$UseGroup
    )

    $List.Add([PSCustomObject]@{
        Word       = $Word
        Source     = $Source
        MatchRoot  = $MatchRoot
        GroupBase  = $GroupBase
        ParentName = $ParentName
        UseGroup   = $UseGroup
    })
}

if ([string]::IsNullOrWhiteSpace($Line)) {
    $Line = Read-Host "検索語を入力してください"
}

if ([string]::IsNullOrWhiteSpace($Line)) {
    Write-Host "検索語が空です"
    exit
}

# 空白(半角・全角)で分割
$Words = $Line -split '[\s ]+' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }

Write-Host "検索語:"
$Words | ForEach-Object { Write-Host " - $_" }
Write-Host ""

# ===== 設定 =====
$BasePaths = @(
    "G:\ここにのパスを指定",
    "L:\ここにパスを指定\する"
)
$DestRoot  = Get-Location
$PrefixBase = "【aaa】"
# =================

$targets = New-Object System.Collections.Generic.List[object]

foreach ($base in $BasePaths) {

    if (-not (Test-Path -LiteralPath $base)) {
        Write-Host "スキップ(存在しない): $base"
        continue
    }

    $baseName = [System.IO.Path]::GetFileName(($base.TrimEnd('\','/')))

    # 第一階層のみ
    Get-ChildItem -LiteralPath $base -Force | ForEach-Object {

        $item = $_

        foreach ($word in $Words) {

            if ($item.Name -like "*$word*") {

                if ($item.PSIsContainer) {
                    # フォルダ一致 → 中身を再帰取得
                    $matchRoot = $item.FullName
                    Get-ChildItem -LiteralPath $matchRoot -Recurse -File | ForEach-Object {
                        Add-Target -List $targets -Word $word -Source $_.FullName -MatchRoot $matchRoot -GroupBase $item.Name -ParentName $baseName -UseGroup $true
                    }
                }
                else {
                    # ファイル一致
                    $matchRoot = $base
                    Add-Target -List $targets -Word $word -Source $item.FullName -MatchRoot $matchRoot -GroupBase $item.Name -ParentName $baseName -UseGroup $false
                }
            }
        }
    }
}

if ($targets.Count -eq 0) {
    Write-Host "該当なし"
    exit
}

# ===== 一覧表示 =====
Write-Host "コピー対象一覧"
Write-Host "--------------------------"
$targets | ForEach-Object {
    Write-Host "$($_.Word)/$($_.Source)"
}
Write-Host "--------------------------"

$confirm = Read-Host "コピーしますか? (y のみ実行)"
if ($confirm -ne "y") {
    Write-Host "中断しました"
    exit
}

# ===== コピー =====
$groupMap = @{}

foreach ($t in $targets) {

    $src = $t.Source

    # --- 検索語フォルダ ---
    $wordSafe = Convert-ToSafeName $t.Word
    $wordRoot = Join-Path $DestRoot $wordSafe
    if (-not (Test-Path $wordRoot)) {
        New-Item -ItemType Directory -Path $wordRoot | Out-Null
    }

    $finalGroupPath = $wordRoot
    if ($t.UseGroup) {
        # --- 一致フォルダ_親フォルダ ---
        $groupBaseSafe = Convert-ToSafeName $t.GroupBase
        $parentSafe = Convert-ToSafeName $t.ParentName

        $groupKey = "$wordSafe|$groupBaseSafe|$parentSafe"
        if (-not $groupMap.ContainsKey($groupKey)) {
            $groupName = "${groupBaseSafe}_${parentSafe}"
            $groupPath = Join-Path $wordRoot $groupName

            $i = 1
            $finalGroupPath = $groupPath
            while (Test-Path $finalGroupPath) {
                $finalGroupPath = Join-Path $wordRoot "${groupName}_$i"
                $i++
            }

            $groupMap[$groupKey] = $finalGroupPath
            if (-not (Test-Path $finalGroupPath)) {
                New-Item -ItemType Directory -Path $finalGroupPath -Force | Out-Null
            }
        }

        $finalGroupPath = $groupMap[$groupKey]
    }

    # --- 相対パス保持 ---
    $relativePath = Get-RelativePathSafe -BasePath $t.MatchRoot -TargetPath $src
    $destPath = Join-Path $finalGroupPath $relativePath

    # --- 出力先ディレクトリ作成 ---
    $destDir = Split-Path $destPath -Parent
    if (-not (Test-Path $destDir)) {
        New-Item -ItemType Directory -Path $destDir -Force | Out-Null
    }

    # --- ファイル名にプレフィックス ---
    $fileName = [System.IO.Path]::GetFileName($destPath)
    $prefixedName = "${PrefixBase}$fileName"
    $finalFilePath = Join-Path $destDir $prefixedName

    # --- ファイル名重複対策 ---
    $j = 1
    while (Test-Path $finalFilePath) {
        $prefixedName = "【aaa_$j】$fileName"
        $finalFilePath = Join-Path $destDir $prefixedName
        $j++
    }

    Copy-Item -LiteralPath $src -Destination $finalFilePath
}

Write-Host "コピー完了"

VBA、マクロ

修正箇所のクイック確認に「コピーした複数文字列(改行区切り)を一発でハイライトしてくれる」マクロ

何に使うのかというと、Excelの資料をレビューをする際などにまとめてハイライトして修正箇所をチェックしやすくします。
Excelで資料見なきゃいけないけどやりたくない場合につかいましょう。
青とオレンジを作ってクイックアクセスツールバーに登録するのがおすすめです。
下記のマクロだとDataObjectの参照設定不要で使えました。

入力:改行区切り文字列(コピーすればOK)
検索対象:1シート全体
ハイライト対象:入力された文字列のあるセルの背景色(文字色変更ではない)

こういうことがしたい時におすすめです。
クイックアクセスツールバーにいれて汚していい資料を塗りつぶしながら品質を上げましょう。

コード本体

Sub HighlightWordsFromClipboard()
    Dim words() As String
    Dim w As Variant
    Dim rng As Range
    Dim cell As Range
    Dim hitCount As Long
    Dim mode As VbMsgBoxResult
    Dim clipText As String

    ' 検索モード選択
    mode = MsgBox( _
        "検索モードを選択してください" & vbCrLf & vbCrLf & _
        "はい:部分一致" & vbCrLf & _
        "いいえ:完全一致" & vbCrLf & _
        "キャンセル:終了", _
        vbYesNoCancel + vbQuestion, _
        "検索モード選択" _
    )

    If mode = vbCancel Then Exit Sub

    ' --- クリップボード取得 ---
    Dim obj As Object
    Set obj = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    obj.GetFromClipboard
    clipText = obj.GetText

    If Trim(clipText) = "" Then Exit Sub

    words = Split(clipText, vbCrLf)

    Application.ScreenUpdating = False

    hitCount = 0
    Set rng = ActiveSheet.UsedRange   ' 検索対象:そのシート

    For Each cell In rng.Cells
        If Not IsError(cell.Value) Then
            For Each w In words
                If Trim(w) <> "" Then
                    If mode = vbYes Then
                        ' 部分一致
                        If InStr(1, cell.Value, w, vbTextCompare) > 0 Then
                            cell.Interior.Color = RGB(255, 192, 0)
                            hitCount = hitCount + 1
                            Exit For
                        End If
                    ElseIf mode = vbNo Then
                        ' 完全一致
                        If StrComp(cell.Value, w, vbTextCompare) = 0 Then
                            cell.Interior.Color = RGB(255, 192, 0)
                            hitCount = hitCount + 1
                            Exit For
                        End If
                    End If
                End If
            Next w
        End If
    Next cell

    Application.ScreenUpdating = True

    MsgBox hitCount & " 件をハイライトしました", vbInformation, "完了"
End Sub

確認したセルの背景を「黒く塗りつぶす/白に戻す」トグルマクロ

順序があるシートで見た場所をマークするのに使っています。
背景色を戻すクイックアクセスツールバーやショートカットキーが存在しないので入れています。
クリックするたびに白と黒が入れ替わります。気分はマークシートですね。

Sub ToggleBlackFill()
    Dim c As Range
    Dim hasBlack As Boolean
    
    If TypeName(Selection) <> "Range" Then Exit Sub
    
    ' まず黒セルが含まれているかチェック
    hasBlack = False
    For Each c In Selection
        If c.Interior.Color = RGB(0, 0, 0) Then
            hasBlack = True
            Exit For
        End If
    Next c
    
    ' 黒が含まれていれば解除、なければ黒塗り
    For Each c In Selection
        If hasBlack Then
            c.Interior.Pattern = xlNone
        Else
            c.Interior.Color = RGB(0, 0, 0)
        End If
    Next c
End Sub

100枚以上あるシートの選択の頻繁な移動に便利な「シート選択表示」マクロ

参考

Excelの左下の間を右クリックすると出てくるやつです。

100枚ぐらいあるシートを移動するためだけにいちいち左下を右クリックしてられないですよね。
しかもこういうのを作ろうとすると必ずユーザーフォーム(VBAのUIみたいなコード化できないやつ)をおすすめされるのでね、この方法はユーザーフォーム使わないでできるので大変便利ですね。

Sub SheetSelectDialog()
   With CommandBars.Add(Temporary:=True)
       .Controls.Add(ID:=957).Execute
       .Delete
   End With
End Sub

エビデンスのサイズすら揃ってない職場用「倍率指定A1選択」マクロ

倍率指定用、大量のエビデンスを見なきゃいけないのにブックごとに倍率が揃ってない現場用
※ブックごとに自分が見やすい倍率に自力で揃えなきゃいけない人向けのマクロです。
倍率は必要に応じて毎回自分で見やすいサイズにいじって使っています。

Sub ZoomByA1()
    ActiveWindow.Zoom = 70   '倍率指定する(%)
    ActiveSheet.Range("A1").Select
End Sub

マウスでホーム(A1)飛びたい人向け

A1選択されてない資料をよく読む人向け

Sub SelectA1()
    ActiveSheet.Range("A1").Select
End Sub

いますぐ押しやすいボタンでzoomを上げたい「+10% -10%」マクロ

右下とかホイールで倍率上げ下げしたくない人向け

拡大率+10%

Sub ZoomPlus10()
    ActiveWindow.Zoom = ActiveWindow.Zoom + 10
End Sub

拡大率-10%

Sub ZoomMinus10()
    ActiveWindow.Zoom = ActiveWindow.Zoom - 10
End Sub

使い方:開発リボン表示方法から、クイックアクセスツールバー登録まで

最後に、開発リボン表示方法-マクロ作成-クイックアクセスツールバー登録までを追っていきます。

開発リボンを出す:
ファイル→オプション→リボンのユーザー設定→「開発」にチェックして「OK」

標準モジュールへの追加:
開発→コードの表示

開いているフォルダを右クリック→挿入→標準モジュール

出てきたWindowにコードを貼り付け

保存するとダイアログが出てくるので「戻る」をクリック

ファイル名を変更して、「Excelアドイン」を選択
(アドインのパスもメモ帳などに保存しておく「C:\Users\YOUR_NAME\AppData\Roaming\Microsoft\AddIns」など、もし動かなかったらトラストセンターから信頼済みの場所に登録してExcel再起動)

開発→Excelアドイン→参照→先程のアドインのファイルを選択→OK

アドインがチェックされているのを確認してOK

ファイル→オプション→クイックアクセスツールバー→コマンドの選択「マクロ」→さっき作ったマクロの関数名を選択→「追加>>」→OK
マクロはなぜか2個ぐらい出る時があるけどどっちでもいいと思う。

セルを選択して左上の増えたマークをクリック、セルが黒くなる(マクロの効果)はず。

アイコンはさっきのページで変更できるので変更しよう

Altキー+数字でショートカットもできる。

おわり。

タイトルとURLをコピーしました