1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファイルコンテンツ検索_キーワード検索_コピペするだけで簡単

1
Last updated at Posted at 2025-09-13

■ 概要

これは業務(仕事)で、下記の場合に当てはまる場合にとても役に立つツールとして、自分のために残すツール作成キットである。
・入りたての現場で、右も左もわからないのに資料の中身から特定のキーワードを検索したい。
・大量のファイルの中から特定のキーワードを検索してその結果をファイルに出力させたい。
・オンプレミス環境でも自分のツールを作りたい。
・楽して簡単なツールを作成したい。
・勉強したい。

■ 作成ファイル一覧(文字コード「UTF-8」、「BOM」付き、改行コード「LF」で保存してください)

Search-Files.ps1 (PowerShellスクリプト本体)
config.json (動作設定ファイル)
README.txt (取扱説明書)
input_path.csv (検索対象フォルダ指定ファイル)
keywords.csv (検索キーワード指定ファイル)

1. PowerShellスクリプト (Search-Files.ps1)

#================================================================================
# 概要
#   指定されたフォルダ内を再帰的に検索し、キーワードを含むファイルの詳細情報を出力するスクリプト。
#
# 説明
#   - 設定ファイル(config.json)で、検索方法(AND/OR、大文字/小文字区別など)を細かく指定できます。
#   - 入力ファイル(input_path.csv, keywords.csv)で、検索対象フォルダとキーワードを指定します。
#   - テキスト、Word、Excelファイルの中身を横断的に検索します。
#   - 260文字を超える長いパスを持つファイルにも対応します。
#   - 検索結果、処理ログ、エラーログを、実行日時が名前についたテキストファイルとして自動生成します。
#   - 処理中に特定のファイルでエラーが発生しても、そのファイルをスキップして処理を継続します。
#================================================================================


# --- 初期設定 ---

# スクリプト実行中にエラーが発生した場合、処理を即座に停止するように設定します。
# これにより、予期せぬ動作を防ぎ、try-catch構文でエラーを確実に捕捉できます。
$ErrorActionPreference = "Stop"

# スクリプトファイル自身の場所(パス)を取得します。
# config.jsonなどの関連ファイルを読み込む際の基準点となります。
$scriptPath = $PSScriptRoot


# --- 関連ファイルのパスを定義 ---

# 設定ファイル (検索条件などを定義)
$configFile = Join-Path $scriptPath "config.json"
# 検索対象フォルダを指定するファイル
$inputPathFile = Join-Path $scriptPath "input_path.csv"
# 検索キーワードを指定するファイル
$keywordsFile = Join-Path $scriptPath "keywords.csv"
# 260文字を超える長いパスのファイルを一時的にコピーするためのフォルダ
$tempDir = Join-Path $scriptPath "temp_search_files"


# --- 出力ファイル名の生成 ---

# ファイル名が重複しないように、現在の日時を使ってユニークな名前を作成します。
$timestamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'
# 検索結果を保存するファイル
$outputFile = Join-Path $scriptPath "SearchResult_$timestamp.txt"
# 処理中に発生したエラーを記録するファイル
$errorLogFile = Join-Path $scriptPath "ErrorLog_$timestamp.txt"
# スクリプトの開始・終了時刻や進捗状況を記録するファイル
$normalLogFile = Join-Path $scriptPath "ProcessLog_$timestamp.txt"


# --- ログ出力用の関数を定義 ---

# ログファイルにメッセージを書き込むための共通関数です。
function Write-Log ($message, $filePath) {
    try {
        # メッセージの先頭に現在の日時を付けて、指定されたログファイルに追記します。
        Add-Content -Path $filePath -Value "$(Get-Date -Format 'yyyy/MM/dd HH:mm:ss') - $message"
    } catch {
        # 万が一、ログファイルへの書き込み自体に失敗した場合にコンソールへエラーを表示します。
        Write-Host "致命的なエラー: ログファイル '$filePath' に書き込めません。"
    }
}

# 処理の進捗などを記録するための関数です (通常の処理ログ用)。
function Write-LogInfo ($message) {
    Write-Log $message $normalLogFile
}

# 発生したエラーを記録するための関数です (エラーログ用)。
function Write-LogError ($message) {
    Write-Log $message $errorLogFile
}


# --- 設定ファイルと入力ファイルの読み込み ---

try {
    # (1) 設定ファイル(config.json)を読み込みます。
    if (-not (Test-Path $configFile)) { throw "設定ファイル 'config.json' が見つかりません。" }
    # JSON形式のテキストをPowerShellが扱えるオブジェクトに変換します。
    $config = Get-Content $configFile -Raw | ConvertFrom-Json

    # (2) 検索対象フォルダのパス(input_path.csv)を読み込みます。
    if (-not (Test-Path $inputPathFile)) { throw "検索パス指定ファイル 'input_path.csv' が見つかりません。" }
    $searchPath = (Import-Csv $inputPathFile).Path
    # パスが空文字でないかチェックします。
    if ([string]::IsNullOrEmpty($searchPath)) {
        throw "'input_path.csv' にパスが指定されていません。"
    }
    # 指定されたフォルダが実際に存在するかを、長いパスにも対応した形式で検証します。
    $longPathForTest = $searchPath
    if ($longPathForTest -notlike "\\*") { $longPathForTest = "\\?\$longPathForTest" }
    if (-not (Test-Path -LiteralPath $longPathForTest -PathType Container)) {
        throw "'input_path.csv' に指定されたフォルダパスが無効です: '$searchPath'"
    }

    # (3) 検索キーワード(keywords.csv)を読み込みます。
    if (-not (Test-Path $keywordsFile)) { throw "キーワード指定ファイル 'keywords.csv' が見つかりません。" }
    # カンマ区切りのキーワードを分割し、前後の余白を削除して配列に格納します。
    $keywords = (Import-Csv $keywordsFile).Keyword -split ',' | ForEach-Object { $_.Trim() }
    if ($keywords.Count -eq 0) { throw "'keywords.csv' からキーワードが読み込めませんでした。" }

} catch {
    # 上記のいずれかの読み込みに失敗した場合、処理は続行不可能なのでエラーログに記録して終了します。
    $errorMessage = "スクリプトの初期化に失敗しました: $($_.Exception.Message)"
    Write-Host $errorMessage -ForegroundColor Red
    Write-LogError $errorMessage
    exit 1 # スクリプトを強制終了します。
}


# --- 検索対象ファイルのリストアップ ---

Write-Host "検索対象ファイルの一覧を作成しています..."
try {
    # config.jsonから検索対象の拡張子を取得します (例: .txt, .docx)。
    $includePatterns = $config.include_extensions | ForEach-Object { $_.ToLower() }

    # 指定されたフォルダ以下を再帰的に探索し、対象拡張子のファイルだけを取得します。
    $allFiles = Get-ChildItem -LiteralPath $searchPath -Recurse -File | Where-Object { $includePatterns -contains $_.Extension.ToLower() }
    $targetFiles = $allFiles

    # config.jsonで指定された「除外フォルダ」を含むパスのファイルを除外します。
    if ($config.exclude_folders.Count -gt 0) {
        $excludeFolderRegex = ($config.exclude_folders | ForEach-Object { [regex]::Escape($_) }) -join '|'
        $targetFiles = $targetFiles | Where-Object { $_.FullName -notmatch $excludeFolderRegex }
    }
    
    # config.jsonで指定された「除外ファイル名」を含むファイルを除外します。
    if ($config.exclude_filenames -ne $null -and $config.exclude_filenames.Count -gt 0) {
        $excludeFilenameRegex = ($config.exclude_filenames | ForEach-Object { [regex]::Escape($_) }) -join '|'
        $targetFiles = $targetFiles | Where-Object { $_.Name -notmatch $excludeFilenameRegex }
    }
    
    # 最終的な検索対象ファイル数を画面とログに出力します。
    Write-Host "検索対象ファイル数: $($targetFiles.Count) 件"
    Write-LogInfo "検索対象ファイル数: $($targetFiles.Count) 件"

} catch {
    # ファイルリストの作成に失敗した場合、エラーログに記録して終了します。
    $errorMessage = "ファイルリストの作成中にエラーが発生しました: $($_.Exception.Message)"
    Write-Host $errorMessage -ForegroundColor Red
    Write-LogError $errorMessage
    exit 1
}


# --- キーワードが含まれているかチェックする関数 ---

function Check-Keywords {
    param(
        [string]$text,       # チェック対象の文字列 (セルの値や行の内容など)
        [array]$keywords,    # 検索キーワードの配列
        [psobject]$config    # 設定内容 (AND/OR, 大文字/小文字の区別など)
    )

    # そもそもチェック対象のテキストが空なら、false (不一致) を返します。
    if ([string]::IsNullOrWhiteSpace($text)) { return $false }

    $matchCount = 0
    # config.json の設定に応じて、大文字/小文字を区別するかどうかを決定します。
    $compareOption = if ($config.case_sensitive) { "CaseSensitive" } else { "IgnoreCase" }
    
    # 各キーワードがテキスト内に存在するかチェックします。
    foreach ($kw in $keywords) {
        # 'regex' モードの場合はキーワードを正規表現パターンとして、'literal' モードの場合は単純な文字列として扱います。
        $pattern = if ($config.search_mode -eq 'regex') { $kw } else { [regex]::Escape($kw) }
        
        # -match 演算子で文字列のマッチングを行います。
        if ($text -match $pattern) {
            $matchCount++
        }
    }

    # 'AND' 条件の場合は、すべてのキーワードが見つかった場合のみ true を返します。
    if ($config.search_logic -eq 'AND') {
        return $matchCount -eq $keywords.Count
    } 
    # 'OR' 条件の場合は、一つでもキーワードが見つかれば true を返します。
    else {
        return $matchCount -gt 0
    }
}


# --- メインの検索処理 ---

$totalFiles = $targetFiles.Count
$processedCount = 0   # 処理済みのファイル数
$foundCount = 0       # キーワードが見つかったファイル数

# 開始時刻を記録し、画面とログに出力します。
$startTime = Get-Date
Write-Host "処理開始: $startTime"
Write-LogInfo "処理開始: $startTime"
$lastProgressTime = $startTime # 進捗ログを記録した最後の時刻

# WordやExcelを操作するための準備 (COMオブジェクト)
$wordApp = $null
$excelApp = $null

# 長いパス対応のための一時フォルダを作成します。
if (-not (Test-Path -LiteralPath $tempDir)) { New-Item -Path $tempDir -ItemType Directory | Out-Null }

try {
    # リストアップしたファイルを1つずつループして処理します。
    foreach ($file in $targetFiles) {
        $processedCount++
        $originalFilePath = $file.FullName.Replace('\\?\', '') # ログ出力用の整形済みパス
        $fileToProcessPath = $file.FullName                  # 実際に処理する用のパス
        $isTempFile = $false                                  # 一時ファイルを使用したかどうかのフラグ
        $fileExtension = $file.Extension.ToLower()            # 拡張子を小文字で取得

        # 画面に進捗状況を表示します。
        Write-Progress -Activity "ファイルを検索中" -Status "$originalFilePath" -PercentComplete (($processedCount / $totalFiles) * 100)

        # 10分ごとに進捗をログファイルに記録します。
        $now = Get-Date
        if (($now - $lastProgressTime).TotalMinutes -ge 10) {
            $elapsed = New-TimeSpan -Start $startTime -End $now
            $progressMessage = "進捗: $processedCount / $totalFiles 件 (経過時間: $($elapsed.ToString()))"
            Write-Host "[$now] $progressMessage"
            Write-LogInfo $progressMessage
            $lastProgressTime = $now
        }

        # この try-catch ブロックで、個々のファイルの処理中に発生したエラーを捕捉します。
        # これにより、1つのファイルでエラーが起きてもスクリプト全体が停止せず、次のファイルに進むことができます。
        try {
            $results = @() # このファイルで見つかった結果を格納する配列

            # ファイルパスが240文字を超え、かつOfficeファイルの場合、一度一時フォルダにコピーしてから処理します。
            # これは、OfficeのCOMオブジェクトが長いパスを直接開けないことがあるためです。
            if ($file.FullName.Length -gt 240 -and (@(".doc", ".docx", ".xls", ".xlsx", ".xlsm") -contains $fileExtension)) {
                $tempFileName = "$(Get-Random)- $($file.Name)"
                $tempFilePath = Join-Path $tempDir $tempFileName
                Copy-Item -LiteralPath $file.FullName -Destination $tempFilePath
                $fileToProcessPath = $tempFilePath
                $isTempFile = $true
            }

            # ファイルの拡張子に応じて処理を分岐します。
            switch ($fileExtension) {
                # --- テキストファイル (.txt, .csv) の処理 ---
                { @(".txt", ".csv") -contains $_ } {
                    try {
                        # ファイルを1行ずつ読み込み、キーワードが含まれるかチェックします。
                        Get-Content -LiteralPath $fileToProcessPath | ForEach-Object {
                            $lineNum = $_.ReadCount # 現在の行番号
                            if (Check-Keywords -text $_ -keywords $keywords -config $config) {
                                # 見つかった場合、結果を「ファイルパス:行番号:行の内容」の形式で保存します。
                                $results += "$originalFilePath`:$lineNum`:`$_"
                            }
                        }
                    } catch {
                        # このファイルの処理中にエラーが起きたら、エラー内容をログに記録します。
                        throw "テキストファイル処理エラー: $($_.Exception.Message)"
                    }
                }

                # --- Wordファイル (.doc, .docx) の処理 ---
                { @(".doc", ".docx") -contains $_ } {
                    try {
                        # WordアプリケーションのCOMオブジェクトがなければ作成します。
                        if ($null -eq $wordApp) { $wordApp = New-Object -ComObject Word.Application; $wordApp.Visible = $false }
                        
                        # ファイルを読み取り専用で開きます。
                        $doc = $wordApp.Documents.Open($fileToProcessPath, $false, $true)
                        
                        # Word文書内のすべての段落を順番にチェックします。
                        for ($i = 1; $i -le $doc.Paragraphs.Count; $i++) {
                            $paragraph = $doc.Paragraphs.Item($i)
                            $text = $paragraph.Range.Text
                            if (Check-Keywords -text $text -keywords $keywords -config $config) {
                                $pageNum = $paragraph.Range.Information(3) # 段落が存在するページ番号を取得
                                # 見つかった場合、結果を「ファイルパス:ページ番号:段落の内容」の形式で保存します。
                                $results += "$originalFilePath`:$pageNum ページ`:`$($text.Trim())"
                            }
                        }
                        # 文書を保存せずに閉じます。
                        $doc.Close([ref]$false)
                    } catch {
                        # このファイルの処理中にエラーが起きたら、エラー内容をログに記録します。
                        throw "Wordファイル処理エラー: $($_.Exception.Message)"
                    }
                }
                
                # --- Excelファイル (.xls, .xlsx, .xlsm) の処理 ---
                { @(".xls", ".xlsx", ".xlsm") -contains $_ } {
                    try {
                        # ExcelアプリケーションのCOMオブジェクトがなければ作成します。
                        if ($null -eq $excelApp) { 
                            $excelApp = New-Object -ComObject Excel.Application
                            $excelApp.Visible = $false       # Excel画面を表示しない
                            $excelApp.DisplayAlerts = $false # 警告ダイアログを表示しない
                        }

                        # ファイルを読み取り専用で開きます。
                        $workbook = $excelApp.Workbooks.Open($fileToProcessPath, $false, $true)
                        
                        try {
                            # ブック内のすべてのシートを順番に処理します。
                            foreach ($sheet in $workbook.Worksheets) {
                                
                                # --- セルの内容を検索 (高速な一括読み込み方式) ---
                                if ($null -ne $sheet.UsedRange -and $sheet.UsedRange.Count -gt 0) {
                                    
                                    # シートの使用範囲の値を一度にメモリ上の配列に読み込みます。これが高速化のポイントです。
                                    $dataValues = $sheet.UsedRange.Value2
                                    
                                    # 読み込んだデータの型に応じて処理を分岐します。
                                    if ($dataValues -is [array]) {
                                        # データが配列の場合
                                        if ($dataValues.Rank -eq 2) {
                                            # 【パターンA】2次元配列の場合 (複数行・複数列のデータ)
                                            # 行と列のインデックスでループします。
                                            for ($row = 1; $row -le $dataValues.GetLength(0); $row++) {
                                                for ($col = 1; $col -le $dataValues.GetLength(1); $col++) {
                                                    $text = [string]$dataValues[$row, $col]
                                                    if (Check-Keywords -text $text -keywords $keywords -config $config) {
                                                        # この方式ではセル番地の特定が困難なため、ヒットした内容のみ記録します。
                                                        $results += "$originalFilePath`:$($sheet.Name)`:検索ヒット`:`$text"
                                                    }
                                                }
                                            }
                                        } else {
                                            # 【パターンB】1次元配列の場合 (1行または1列のみのデータ)
                                            foreach ($cellValue in $dataValues) {
                                                $text = [string]$cellValue
                                                if (Check-Keywords -text $text -keywords $keywords -config $config) {
                                                    $results += "$originalFilePath`:$($sheet.Name)`:検索ヒット`:`$text"
                                                }
                                            }
                                        }
                                    } else {
                                        # 【パターンC】単一の値の場合 (1セルのみのデータ)
                                        $text = [string]$dataValues
                                        if (Check-Keywords -text $text -keywords $keywords -config $config) {
                                            $results += "$originalFilePath`:$($sheet.Name)`:検索ヒット`:`$text"
                                        }
                                    }
                                }
                                # --- セルの検索ここまで ---
                                
                                # --- コメントの内容を検索 (従来通り) ---
                                foreach ($comment in $sheet.Comments) {
                                    $text = $comment.Text()
                                    if (Check-Keywords -text $text -keywords $keywords -config $config) {
                                        $results += "$originalFilePath`:$($sheet.Name)`:コメント($($comment.Parent.Address($false,$false)))`:`$text"
                                    }
                                }
                                # --- コメントの検索ここまで ---
                            }
                        } finally {
                            # シートの処理中にエラーが発生しても、必ずブックを閉じる処理を実行します。
                            $workbook.Saved = $true
                            $workbook.Close($false)
                        }
                    } catch {
                        # このファイルの処理中にエラーが起きたら、エラー内容をログに記録します。
                        throw "Excelファイル処理エラー: $($_.Exception.Message)"
                    }
                }
            }

            # このファイルから1件でも検索結果が見つかった場合、ファイルに出力します。
            if ($results.Count -gt 0) {
                $foundCount++
                Write-Host "[$foundCount] キーワード発見: $originalFilePath" -ForegroundColor Green
                # 結果を SearchResult ファイルに追記します。
                $results | ForEach-Object { Add-Content -Path $outputFile -Value $_ }
            }

        } catch {
            # ファイル処理全体で発生したエラーをここで捕捉します。
            $errorMessage = "ファイル処理エラー ($originalFilePath): $($_.Exception.Message)"
            Write-Host "警告: $errorMessage - このファイルはスキップされました。" -ForegroundColor Yellow
            Write-LogError $errorMessage
        } finally {
            # 一時ファイルを作成していた場合は、ここで必ず削除します。
            if ($isTempFile) {
                Remove-Item -LiteralPath $fileToProcessPath -Force -ErrorAction SilentlyContinue
            }
        }
    }
}
finally {
    # --- スクリプト終了時の後片付け処理 ---
    # この finally ブロックは、スクリプトが正常終了しても、途中でエラー停止しても、必ず実行されます。
    
    Write-Host "クリーンアップ処理を実行しています..."

    # (1) Wordアプリケーションを終了します。
    if ($null -ne $wordApp) {
        try { $wordApp.Quit() } catch {}
        # メモリからCOMオブジェクトを解放します。
        [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wordApp) | Out-Null
        Remove-Variable wordApp
    }

    # (2) Excelアプリケーションを終了します。
    if ($null -ne $excelApp) {
        try { $excelApp.Quit() } catch {}
        # メモリからCOMオブジェクトを解放します。
        [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excelApp) | Out-Null
        Remove-Variable excelApp
    }

    # (3) 一時フォルダを中身ごと削除します。
    if (Test-Path -LiteralPath $tempDir) {
        Remove-Item -Recurse -Force -LiteralPath $tempDir
    }

    # (4) ガーベジコレクションを強制実行し、メモリを解放します。
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()

    # 終了時刻と全体の処理時間を計算し、画面とログに出力します。
    $endTime = Get-Date
    $elapsed = New-TimeSpan -Start $startTime -End $endTime
    $finalMessage = "処理終了: $endTime`n処理時間: $($elapsed.ToString())"
    Write-Host $finalMessage
    Write-LogInfo "処理終了: $endTime"
    Write-LogInfo "処理時間: $($elapsed.ToString())"
}


# --- 完了メッセージ ---

Write-Host "`n処理が完了しました。"
if (Test-Path $outputFile) {
    Write-Host "検索結果は '$outputFile' に保存されました。"
} else {
    Write-Host "キーワードに一致するファイルは見つかりませんでした。"
}
if (Test-Path $errorLogFile) {
    Write-Host "いくつかのファイルでエラーが発生しました。詳細は '$errorLogFile' を確認してください。" -ForegroundColor Yellow
}
Write-Host "処理ログは '$normalLogFile' に保存されました。"







2. 設定ファイル (config.json)

{
    "_comment": "--- このファイルでスクリプトの動作を細かく設定します ---",


    "search_logic": "OR",
    "_comment_search_logic": "キーワードの検索論理を指定します。'AND' (全てのキーワードを含む) または 'OR' (いずれかのキーワードを含む) を選択してください。",


    "search_mode": "literal",
    "_comment_search_mode": "検索モードを指定します。'literal' (単純な文字列として検索) または 'regex' (正規表現として検索) を選択してください。",


    "case_sensitive": false,
    "_comment_case_sensitive": "検索時に大文字と小文字を区別するかどうか。区別する場合は true, しない場合は false を設定してください。",
    
    
    "include_extensions": [
        ".txt", ".csv",
        ".doc", ".docx",
        ".xls", ".xlsx", ".xlsm"
    ],
    "_comment_include_extensions": "★【変更点】検索対象にしたいファイルの拡張子をリスト形式で指定します。ここに無い拡張子は完全に無視されます。",


    "exclude_folders": [
        ".git",
        "node_modules",
        "$RECYCLE.BIN",
        "System Volume Information"
    ],
    "_comment_exclude_folders": "検索対象から除外したいフォルダ名をリスト形式で指定します。この名前を含むフォルダは再帰的に無視されます。",


    "exclude_filenames": [
        "~$",
        ".tmp",
        "ファイルサイズが大き過ぎるものを指定するなど"
    ],
    "_comment_exclude_filenames": "検索対象から除外したいファイル名に含まれる文字列をリスト形式で指定します。この文字列を含むファイルは無視されます。(例: '~$' はWordやExcelの一時ファイルを除外します)ファイルサイズが大き過ぎるものを指定するなども〇"
}

3. 取扱説明書 (README.txt)

============================================================
 高機能ファイル内テキスト検索スクリプト README
============================================================

このツールは、指定したフォルダ内にある複数のファイルを横断的に検索し、
キーワードが含まれるファイルの場所と内容を特定するためのPowerShellスクリプトです。

-----

■ このツールで出来ること

  * テキストファイル、Word、Excelファイルの中身を一度に検索できます。
  * AND検索(すべてのキーワードを含む)とOR検索(いずれかのキーワードを含む)を切り替えられます。
  * 大文字・小文字を区別するかどうかを設定できます。
  * 正規表現を使った高度なパターン検索が可能です。
  * 特定のフォルダや、特定の文字列を含むファイル名を検索対象から除外できます。
  * Excelファイルでは、セルの中身だけでなくコメントの中身も検索します。
  * 260文字を超えるような非常に長いパスにあるファイルも検索できます。
  * 検索の進捗状況が画面に表示され、詳細な処理ログもファイルに保存されます。

-----

■ ファイル構成

このツールを使用するには、以下のファイルがすべて同じフォルダに存在する必要があります。

  - `Search-Files.ps1` : スクリプト本体 
  - `config.json` : 動作を細かく設定するファイル 
  - `input_path.csv` : 検索を開始するフォルダを指定するファイル 
  - `keywords.csv` : 検索したいキーワードを指定するファイル 
  - `README.txt` : この説明書 

-----

■ 準備 (スクリプト実行前に行ってください)

1. 【必須】検索対象フォルダの指定

`input_path.csv` をメモ帳などのテキストエディタで開き、
検索を開始したいフォルダのフルパスを1行だけ記述してください。

(例)
Path
C:\Users\YourName\Documents


2. 【必須】キーワードの指定

`keywords.csv` をテキストエディタで開き、検索したいキーワードを
カンマ(`,`)区切りで記述してください。

(例)
Keyword
プロジェクトA,請求書,2025年


3. 【任意】動作設定の変更

`config.json` をテキストエディタで開くと、検索の細かい動作を
変更できます。ご自身の目的に合わせて設定値を変更してください。

  * `search_logic`: キーワードの検索条件

      * `"OR"`: `keywords.csv` の中のいずれか1つでもキーワードが含まれていればヒットします。
      * `"AND"`: `keywords.csv` の中のすべてのキーワードが含まれている場合のみヒットします。

  * `search_mode`: キーワードの解釈方法

      * `"literal"`: キーワードを単純な文字列として検索します。
      * `"regex"`: キーワードを「正規表現」という特殊なパターンとして解釈して検索します(詳細は後述)。

  * `case_sensitive`: 大文字と小文字を区別するか

      * `false`: "Apple" と "apple" を同じものとして検索します。
      * `true`: "Apple" と "apple" を違うものとして厳密に区別します。

  * `include_extensions`: 検索対象にするファイルの拡張子

      * ここに記載された拡張子のファイルのみを検索します。リストにないものは無視されます。
      * (例) `[".txt", ".csv", ".docx", ".xlsx"]`

  * `exclude_folders`: 検索対象から除外するフォルダ名

      * ここに記載された名前がフォルダパスのどこかに含まれている場合、そのフォルダ以下はすべて検索対象外となります。
      * (例) `"node_modules"` を指定すると `C:\Project\A\node_modules` などが丸ごと除外されます。

  * `exclude_filenames`: 【新機能】検索対象から除外するファイル名

      * ここに記載された文字列がファイル名に含まれている場合、そのファイルは検索対象外となります。
      * (例) `"~"` や `"_backup"` を指定すると、`~$ファイルA.docx` や `設定_backup.txt` といったファイルが除外されます。

-----

■ 実行方法

1.  PowerShellを起動します。
    (Windowsのスタートメニューを右クリックし、「ターミナル」や「Windows PowerShell」を選択)

2.  `cd` コマンドを使い、本スクリプトが保存されているフォルダに移動します。
    (例) `cd C:\Tools\FileSearch` 

3.  以下のコマンドをコピー&ペーストして実行します。

    
    powershell.exe -ExecutionPolicy Bypass -File ".\Search-Files.ps1"
    

    ※「-ExecutionPolicy Bypass」は、PCのセキュリティ設定でスクリプトがブロックされるのを一時的に回避するためのおまじないのようなものです。

-----

■ 実行結果

スクリプトを実行したフォルダに、日時が付いた以下のファイルが自動的に作成されます。

  *`SearchResult_日付_時刻.txt`
    キーワードが見つかったファイルの情報が記録されます。このファイルが作成されなければ、ヒットしたファイルは無かったということです。
    出力形式はファイルの種類によって異なります。

      - テキストファイル: `ファイルパス:行番号:その行の内容`
      - Wordファイル: `ファイルパス:ページ番号 ページ:その段落の内容`
      - Excelファイル: `ファイルパス:シート名:セル番地:セルの内容`
      - Excel(コメント): `ファイルパス:シート名:コメント(セル番地):コメントの内容`

  *`ProcessLog_日付_時刻.txt`
    スクリプトの開始時刻、終了時刻、対象ファイル数、10分ごとの進捗状況などが記録されます。正常に動作したかの確認に利用できます。

  *`ErrorLog_日付_時刻.txt`
    処理中に何らかのエラー(パスワード付きでファイルが開けない等)が発生した場合に、その詳細が記録されます。このファイルが作成されなければ、エラー無く処理が完了したということです。

-----

■ 正規表現 (RegEx) の使い方

`config.json` の `search_mode` を `"regex"` に変更すると、
キーワードとして正規表現パターンを使用できます。

正規表現は、文字列の「パターン」を表現するための特殊な記法です。
プログラマーにとって非常に強力なツールとなります。

【一般的な正規表現パターンの例】

  - 電話番号 (日本形式): `\d{2,4}-\d{2,4}-\d{4}` 
    (例: 03-1234-5678, 090-1234-5678 にマッチ) 

  - 郵便番号 (日本形式): `\d{3}-\d{4}` 
    (例: 123-4567 にマッチ) 

  - メールアドレス: `[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}` 
    (一般的なメールアドレスの形式にマッチ) 

  - 日付 (yyyy-mm-dd形式): `\d{4}-\d{2}-\d{2}` 
    (例: 2025-09-13 にマッチ) 

※ `keywords.csv` に上記のようなパターンを記述して検索してみてください。

-----

■ 注意事項

  - WordやExcelファイルを検索するには、お使いのPCにMicrosoft Officeがインストールされている必要があります。 
  - 非常に多くのファイル(数万件以上)を対象にすると、処理に時間がかかる場合があります。その場合も10分ごとに進捗がログに出力されますので、動作状況を確認できます。
  - パスワードで保護されたファイルは開けないため、エラーとしてログに記録されます。
  - スクリプトの改造は自己責任でお願いいたします。

4. 検索対象フォルダ指定ファイル (input_path.csv)

Path
C:\temp\search_target

5. 検索キーワード指定ファイル (keywords.csv)

Keyword
サンプル,テスト,見積書

■ 実行手順

上記の5つの内容を、それぞれテキストファイルとして同じフォルダに保存してください。
input_path.csv と keywords.csv を、あなたの目的に合わせて編集します。
必要であれば config.json の設定を変更します。
README.txt に記載されている「実行方法」に従って、PowerShellからスクリプトを実行してください。


下記は業務的なドキュメント

高機能ファイル内容検索スクリプト ドキュメント

📝 概要

このスクリプトは、指定されたフォルダ配下のファイルを再帰的に検索し、
config.json で指定された拡張子のファイルに対して
keywords.csv のキーワードを含むかどうかを調査します。

  • 結果は SearchResult_yyyy-MM-dd_HH-mm-ss.txt に保存
  • 進捗や開始終了時刻は ProcessLog_yyyy-MM-dd_HH-mm-ss.txt に保存
  • エラーは ErrorLog_yyyy-MM-dd_HH-mm-ss.txt に保存

📂 ファイル構成

/Search-Files.ps1       # スクリプト本体
/config.json            # 検索設定ファイル
/input_path.csv         # 検索開始フォルダを指定
/keywords.csv           # 検索キーワードを指定
/README.md              # 説明書

🔄 処理フロー図


⚙️ 実運用手順書

1. 検索対象フォルダを指定

input_path.csv を編集して、検索開始したいフォルダを指定します。

Path
C:\Users\YourName\Documents

2. 検索キーワードを指定

keywords.csv を編集して、検索したい単語をカンマ区切りで記述します。

Keyword
プロジェクトA,請求書,2025年

3. 設定ファイルを確認

config.json で検索条件を設定します。

例:

{
  "search_logic": "OR",
  "search_mode": "literal",
  "case_sensitive": false,
  "include_extensions": [".txt", ".csv", ".docx", ".xlsx"],
  "exclude_folders": ["node_modules", ".git"]
}
  • search_logic: AND / OR
  • search_mode: literal / regex
  • case_sensitive: 大文字小文字を区別するか

4. 実行方法

PowerShell を起動し、スクリプトのあるフォルダに移動して以下を実行します。

powershell.exe -ExecutionPolicy Bypass -File ".\Search-Files.ps1"

5. 出力ファイルの確認

  • SearchResult_xxx.txt → キーワード一致結果
  • ProcessLog_xxx.txt → 開始・進捗・終了ログ
  • ErrorLog_xxx.txt → エラー内容

📊 ログ出力仕様

  • 開始時刻: スクリプト処理開始時に記録
  • 進捗: 10分ごとに件数・経過時間・時刻を記録
  • 終了時刻と経過時間: 処理終了後に記録
  • 検索対象件数: 対象拡張子のみをカウント

⚠️ 注意事項

  • Word/Excel 検索には Microsoft Office が必要です
  • 数十万件規模のファイル検索は時間がかかることがあります
  • 大量データ検索時はログを参照して進捗を確認してください

✅ まとめ

このスクリプトは 業務での文書横断検索 に利用でき、

  • 進捗ログ
  • エラーログ
  • 検索結果の分離保存

が可能なため、監査対応・トラブル調査などに有効です。


下記は異なる形の詳細ドキュメント「技術解説(コードの詳細解説付き)」

PowerShellで作る高機能ファイル内容検索スクリプト【技術解説】

📝 概要

本記事では、PowerShell で実装した フォルダ内ファイル横断検索スクリプト の仕組みを技術的に解説します。

  • 指定フォルダを再帰的に検索
  • config.json で指定された拡張子だけを対象に
  • keywords.csv に記載されたキーワードを検出
  • 検索結果・通常ログ・エラーログをファイル出力

進捗ログは 10分ごと に出力され、終了時には経過時間も記録されます。
大規模検索や監査ログ用途に利用可能です。


🔄 処理フロー図(Mermaid)


⚙️ ファイル構成

Search-Files.ps1   # スクリプト本体
config.json        # 検索設定
input_path.csv     # 検索開始フォルダを指定
keywords.csv       # 検索キーワードを指定
README.md          # 利用説明書

💡 コード解説

ここではスクリプトの主要部分を抜粋して解説します。
(フルコードはリポジトリ参照)


1. 初期設定

$ErrorActionPreference = "Stop"
$scriptPath = $PSScriptRoot
  • エラー発生時に即停止する設定(エラーを握りつぶすと誤検出につながるため)
  • $PSScriptRoot はスクリプト自身の場所を取得し、関連ファイル探索に利用

2. 出力ファイル名の生成

$timestamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'
$outputFile = Join-Path $scriptPath "SearchResult_$timestamp.txt"
$errorLogFile = Join-Path $scriptPath "ErrorLog_$timestamp.txt"
$normalLogFile = Join-Path $scriptPath "ProcessLog_$timestamp.txt"
  • 検索結果・エラー・通常ログを別々に保存
  • ファイル名に タイムスタンプ付与 → 実行ごとに一意化

3. 設定ファイル読み込み

$config = Get-Content $configFile -Raw | ConvertFrom-Json
$keywords = (Import-Csv $keywordsFile).Keyword -split ',' | ForEach-Object { $_.Trim() }
  • config.json → 検索ロジック・拡張子・除外フォルダを設定
  • keywords.csv → キーワードを読み込み、カンマ区切り対応

例: config.json

{
  "search_logic": "OR",
  "search_mode": "literal",
  "case_sensitive": false,
  "include_extensions": [".txt", ".csv", ".docx", ".xlsx"],
  "exclude_folders": ["node_modules", ".git"]
}

4. 検索対象ファイルの抽出

$includePatterns = $config.include_extensions | ForEach-Object { $_.ToLower() }
$targetFiles = Get-ChildItem -LiteralPath $searchPath -Recurse -File | 
               Where-Object { $includePatterns -contains $_.Extension.ToLower() }
  • 最初から拡張子でフィルタすることで進捗表示の実態と一致
  • 除外フォルダは正規表現で除去

5. キーワード一致関数

function Check-Keywords {
    param($text, $keywords, $config)

    foreach ($kw in $keywords) {
        $pattern = if ($config.search_mode -eq 'regex') { $kw } else { [regex]::Escape($kw) }
        if ($text -match $pattern) { return $true }
    }
    return $false
}
  • 検索モードに応じて正規表現/リテラル検索を切替
  • AND / OR 条件は設定ファイルで指定可能

6. 進捗管理とログ出力

$startTime = Get-Date
$lastProgressTime = $startTime

foreach ($file in $targetFiles) {
    $processedCount++
    $now = Get-Date
    if (($now - $lastProgressTime).TotalMinutes -ge 10) {
        $elapsed = New-TimeSpan -Start $startTime -End $now
        Write-Host "[$now] 進捗: $processedCount / $totalFiles 件 (経過: $elapsed)"
        Write-LogInfo "進捗: $processedCount / $totalFiles 件 (経過: $elapsed)"
        $lastProgressTime = $now
    }
}
  • 10分ごとに進捗件数と経過時間を出力
  • 大規模処理でも進捗を定期的に把握可能

7. Word / Excel の扱い

$wordApp = New-Object -ComObject Word.Application
$excelApp = New-Object -ComObject Excel.Application
$excelApp.DisplayAlerts = $false
  • COM オブジェクトを利用して Word / Excel を直接開く
  • 長いパスは一時フォルダにコピーして処理

8. 終了処理

$endTime = Get-Date
$elapsed = New-TimeSpan -Start $startTime -End $endTime
Write-Host "処理終了: $endTime"
Write-Host "処理時間: $elapsed"
Write-LogInfo "処理終了: $endTime"
Write-LogInfo "処理時間: $elapsed"
  • 終了時刻と経過時間を表示・ログに記録
  • COMオブジェクト解放 & 一時フォルダ削除でリソースリーク防止

⚠️ 注意事項

  • Microsoft Office が必須(Word/Excel 検索用)
  • 数十万件以上のファイルを対象にすると時間がかかる場合あり
  • 大規模検索では進捗ログを参照して進行を把握する

✅ まとめ

このスクリプトは業務用に必要な機能を実装済みです。

  • 検索対象拡張子のフィルタリング
  • 進捗ログ(10分ごと)
  • 検索結果・通常ログ・エラーログの分離保存
  • Word/Excel も横断検索可能

👉 ファイル監査や内部調査、ナレッジ検索基盤として応用可能です。


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?