# SVN作業コピー内ファイルの直接上書き検知スクリプト(複数ファイル対応) # 手順: 指定した複数のファイルを常駐監視し、作業フォルダ内でファイルを直接開いて編集中でない状態 # (Word/Excel等が作る "~$ファイル名" ロックファイルが無い状態)での変更(=外部からの直接上書きコピー)を # 検知したら警告ダイアログを表示する。 # 使い方: .\SVN-ProtectFile.ps1 -ListFile "C:\path\to\watchlist.txt" # ※ watchlist.txt は1行に1パス。空行や "#" で始まる行はコメントとして無視されます。 # このウィンドウを開いたまま(最小化可)にしておいてください。終了するには Ctrl+C を押してください。 # 注: 警告を表示するだけで、上書き自体は止めません。 param( [Parameter(Mandatory = $true)] [string]$ListFile ) Add-Type -AssemblyName System.Windows.Forms # --- 一覧ファイルからのパス読込 --- if (-not (Test-Path $ListFile)) { Write-Host "一覧ファイルが見つかりません: $ListFile" -ForegroundColor Red exit 1 } $allPaths = Get-Content $ListFile | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' -and -not $_.StartsWith('#') } if ($allPaths.Count -eq 0) { Write-Host "一覧ファイルに有効なパスが記載されていません: $ListFile" -ForegroundColor Red exit 1 } # --- 監視対象ファイルの検証・正規化 --- $targets = @() foreach ($p in $allPaths) { if (-not (Test-Path $p)) { Write-Host "監視対象のファイルが見つかりません: $p" -ForegroundColor Red continue } $full = (Resolve-Path $p).Path $dir = Split-Path $full -Parent $name = Split-Path $full -Leaf $targets += [PSCustomObject]@{ FilePath = $full Dir = $dir Name = $name OfficeLockPath = Join-Path $dir ('~$' + $name) } } if ($targets.Count -eq 0) { Write-Host "有効な監視対象がありません。終了します。" -ForegroundColor Red exit 1 } # 同一ファイルが重複指定された場合は除去 $targets = $targets | Sort-Object FilePath -Unique # デバウンス管理用(ファイルごと) $global:SvnProtectLastWarn = @{} foreach ($t in $targets) { $global:SvnProtectLastWarn[$t.FilePath] = (Get-Date).AddSeconds(-3600) } # フォルダごとにグループ化し、フォルダ単位で FileSystemWatcher を1つ作成 $watchers = @() $groups = $targets | Group-Object Dir foreach ($g in $groups) { $watcher = New-Object System.IO.FileSystemWatcher $watcher.Path = $g.Name $watcher.Filter = '*' $watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite -bor [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::Size $watcher.EnableRaisingEvents = $true # このフォルダ内で監視すべきファイル名 -> ターゲット情報 のマップ $fileMap = @{} foreach ($t in $g.Group) { $fileMap[$t.Name] = $t } $messageData = [PSCustomObject]@{ FileMap = $fileMap Debounce = 5 } $action = { $data = $Event.MessageData $changedName = $Event.SourceEventArgs.Name if (-not $data.FileMap.ContainsKey($changedName)) { # 監視対象外のファイルなので無視 return } $target = $data.FileMap[$changedName] if (Test-Path $target.OfficeLockPath) { # 作業フォルダ内でファイルが直接開かれて編集中(Office系ロックファイルあり)なので警告しない return } $now = Get-Date $last = $global:SvnProtectLastWarn[$target.FilePath] if (($now - $last).TotalSeconds -lt $data.Debounce) { return } $global:SvnProtectLastWarn[$target.FilePath] = $now [System.Windows.Forms.MessageBox]::Show( "このファイルはSVN作業コピー内のファイルです:`n$($target.FilePath)`n`n他の場所からの直接上書きコピーは行わず、作業フォルダ内でこのファイルを直接開いて編集・保存してください。", "SVN管理ファイルへの直接上書きを検知", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning ) | Out-Null } Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action -MessageData $messageData | Out-Null Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action -MessageData $messageData | Out-Null Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action -MessageData $messageData | Out-Null $watchers += $watcher } Write-Host "監視を開始しました:" -ForegroundColor Green foreach ($t in $targets) { Write-Host " - $($t.FilePath)" -ForegroundColor Green } Write-Host "このウィンドウを開いたままにしてください。終了するには Ctrl+C を押してください。" -ForegroundColor Yellow try { while ($true) { Wait-Event -Timeout 1 | Out-Null } } finally { foreach ($watcher in $watchers) { Get-EventSubscriber | Where-Object { $_.SourceObject -eq $watcher } | Unregister-Event $watcher.Dispose() } Write-Host "監視を終了しました。" -ForegroundColor Yellow }
# SVN-ProtectFile.ps1 の監視対象ファイル一覧 # 1行に1パスを記述してください。空行や # で始まる行は無視されます。 C:\path\to\file1.xlsx C:\path\to\file2.docx C:\other\dir\file3.xlsx