Powershellで非同期にファイル変更を監視する
なんかさ、便利なものを公開してくれる人もいるわけじゃないですか。
でもさ、ファイル修正してからコマンド叩くの、面倒じゃないですか。
忘れるじゃないですか。
勝手に作れよ、と。
だから考えてみた。
追記 2014/12/15
Start-Watcherのリターンを、Watcher停止のクロージャに変更。
stuncloudさんが便利なハックをきめてくれたので、反映!
変更監視スクリプト
とりあえず、ファイルの変更を監視してみる。
「Powershell ファイル変更監視 非同期」で検索してみたけど、お目当てのものが見つからないので作った。
function Start-Watcher() { Param( $ScriptBlock, [string]$Path, [string]$Filter = '*.*', [switch]$IncludeSubdirectories, [int]$InternalBufferSize = 8192, [System.IO.NotifyFilters]$NotifyFilter = [System.IO.NotifyFilters]::LastWrite -bor [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::DirectoryName, $MessageData = $null ) $watcher = New-Object System.IO.FileSystemWatcher $watcher.Path = $Path $watcher.Filter = $Filter $watcher.IncludeSubdirectories = $IncludeSubdirectories $watcher.InternalBufferSize = $InternalBufferSize $watcher.NotifyFilter = $NotifyFilter $watcher.EnableRaisingEvents = $true $ret = Register-ObjectEvent -InputObject $watcher -EventName Created -SourceIdentifier ('JunjunFileWatcher_Created_' + $Path + $Filter) -Action $ScriptBlock -MessageData $MessageData $ret = Register-ObjectEvent -InputObject $watcher -EventName Changed -SourceIdentifier ('JunjunFileWatcher_Changed_' + $Path + $Filter) -Action $ScriptBlock -MessageData $MessageData $ret = Register-ObjectEvent -InputObject $watcher -EventName Deleted -SourceIdentifier ('JunjunFileWatcher_Deleted_' + $Path + $Filter) -Action $ScriptBlock -MessageData $MessageData $ret = Register-ObjectEvent -InputObject $watcher -EventName Renamed -SourceIdentifier ('JunjunFileWatcher_Renamed_' + $Path + $Filter) -Action $ScriptBlock -MessageData $MessageData return { Remove-Watcher -Path $Path -Filter $Filter }.GetNewClosure() } function Remove-Watcher() { Param( [string]$Path, [string]$Filter = '*.*' ) Remove-Timer ('JunjunFileWatcher_Created_' + $Path + $Filter) Remove-Timer ('JunjunFileWatcher_Changed_' + $Path + $Filter) Remove-Timer ('JunjunFileWatcher_Deleted_' + $Path + $Filter) Remove-Timer ('JunjunFileWatcher_Renamed_' + $Path + $Filter) }
ま、.Netで書く処理をPowershellに直しただけです。
Remove-WatcherがRemove-Timerに依存してます。
以下から持ってくるか依存しないよう修正してください。
Powershellで非同期実行 - じゅんじゅんのきまぐれ
使用例
テスト。「C:\」に「.txt」ファイルが作成されたりしたらどう動くか。
Start-Watcher { Param($sender, $ev) if($ev.ChangeType -eq 'Renamed') { Write-Host $ev.ChangeType $ev.FullPath $ev.Name '<-' $ev.OldFullPath $ev.OldName } else { Write-Host $ev.ChangeType $ev.FullPath $ev.Name } } 'C:\' '*.txt'
更新は使うエディターによって、出方が違うので注意が必要です。
(もしファイル更新が旧削除・新作成方式だと、Changedはないとか)
実験が終わったら、解除。
Remove-Watcher 'C:\' '*.txt'
現在のディレクトリーでSave-SignedScriptって関数を叩きたい場合。
「〜.ps1」が変更されて同じディレクトリーに「〜.Signed.ps1」が存在するなら叩く、とか。
$removeWatcher = Start-Watcher { Param($sender, $ev) if($ev.ChangeType -ne 'Deleted') { $f = Get-Item $ev.FullPath $SignedFilePath = "$($f.BaseName).Signed$($f.Extension)" if((Test-Path $SignedFilePath) -and ($f.LastWriteTime -gt (Get-Item $SignedFilePath).LastWriteTime)) { Save-SignedScript -FileInfo $f } } } (Get-Location) '*.ps1'
何回か同時にイベントが発行することを想定して、LastWriteTimeもチェックしてます。
解除したくなったら、該当ディレクトリーで以下を
Remove-Watcher (Get-Location) '*.ps1'
また、Start-Watcherの戻りを受けていれば、以下のように解除可能。
&$removeWatcher
解説他
このWatcher処理は、処理中メインを占有するので、あまり重たい処理には向きません。
Start-Watcherの引数は、System.IO.FileSystemWatcherに渡すものです。
$ScriptBlockが遅くイベントが頻発する場合、内部バッファーが足りなくなってイベントを取りこぼすことがあります。
取りこぼしがまずいなら、$InternalBufferSizeを大きくしてください。(目安知らんけど)
C:\とか指定して$IncludeSubdirectories=$trueとかだと、悲惨なことが起きかねないので注意してください。
重めの処理をしたいなら、$ScriptBlock内でStart-Taskすれば良いと思いますよ。
Powershellで非同期実行 - じゅんじゅんのきまぐれ
なお、Start-Taskのスクリプトブロックに引数を渡せるようにしました。
また、Start-Watcherのスクリプトブロックにも$Event.MessageData経由で引数が渡せます。
Start-Watcher { Param($sender, $ev) Start-Task { $args[0].out += $args[1].name + $args[1].setting.startMsg Start-Sleep $args[1].setting.sleepTime $args[0].out += $args[1].name + $args[1].setting.endMsg } -MessageData @{name=$ev.Name; setting=$Event.MessageData} } (Get-Location) '*.ps1' -MessageData @{startMsg=' start'; sleepTime=2; endMsg=' end'}
Start-Watcherの-MessageDataが、スクリプトブロック内の$Event.MessageDataになり、スクリプトブロック内にあるStart-Taskの-MessageDataは、そのスクリプトブロック内の$args[1]となっています。