概要
指定の場所から仕様書ファイルをコピーしたり、あちこちに散らばった修正対象の複数の単語を検索して、あるかないか調べるのがいやになる瞬間がありませんか。
そんな私のために下記のツールを作りました。
・散らばった資料を「単語指定」で一気にコピーする 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
単語を入力すると、部分一致で、指定の場所から接頭辞つきでコピーしてくれます。
「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」

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

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

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

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

おわり。











