特定フォルダにファイルが増えたり書き換わったりしたとき、それを検知して何か処理を走らせたいと思ったことはありますよね。
ポーリング(一定間隔でフォルダをチェックし続ける)でも実現できますが、どうせやるならイベント駆動で効率よく動かしたいところです。
PowerShellには[System.IO.FileSystemWatcher]という.NETクラスが利用できて、ファイルの作成・変更・削除・リネームを通知ベースで検知で検知できます。
この記事ではその基本的な使い方から、実運用の参考になるスクリプト例まで解説します。
PowerShellの基本操作や実践コマンドをまとめて確認したい場合はこちらも参考にしてください。

この記事でわかること
- FileSystemWatcherの基本的な仕組み
- ファイルの作成・変更・削除・リネームを検知する方法
- イベント駆動(Register-ObjectEvent)で処理する方法
- 実用的なスクリプト例(ログ出力・ファイル自動コピー)
- 注意点とよくあるはまりどころ
FileSystemWatcherとは
System.IO.FileSystemWatcherは.NET Framework / .NETのクラスで、OSレベルのファイルシステム変更通知を受け取る仕組みです。
PowerShellは.NETクラスをそのまま利用できるので、追加インストール不要でスクリプト内から使えますよ。
FileSystemWatcher Class (System.IO) | Microsoft Learndocs
監視できるイベントは次の4種類です。
| イベント名 | 内容 |
|---|---|
| Created | ファイル・フォルダが作成された |
| Changed | ファイルの中身、属性、サイズ、最終書き込み時刻などが変更された ※Changedは環境や保存方法によって複数回発火しやすいようです。 |
| Deleted | ファイル・フォルダが削除された |
| Renamed | ファイル・フォルダ名が変更された |
サンプル
まずウォッチャー本体を作成して、監視対象のフォルダや条件を設定します。
$watcher = New-Object System.IO.FileSystemWatcher
# 監視するフォルダのパス
$watcher.Path = "C:\Logs"
# 監視対象のファイル名フィルター(例: *.log で log ファイルに限定)
$watcher.Filter = "*.*"
# サブフォルダも監視するか
$watcher.IncludeSubdirectories = $false
# 何の変化を検知するか(NotifyFilters)
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite `
-bor [System.IO.NotifyFilters]::FileName設定項目の解説です。
- Path
監視したいフォルダのパス。ファイル単体の指定はできないのでフォルダを指定します。 - Filter
監視対象のファイル名フィルター。*.logのように拡張子を絞り込むことも可能です。 - IncludeSubdirectories
$trueにするとサブフォルダ内の変更も検知します。デフォルトは$falseでサブフォルダは検知されません。 - NotifyFilter
LastWrite(最終書き込み時刻)、FileName(ファイル名)、DirectoryName(フォルダ名)、Sizeなど複数を-borで組み合わせられます。
ウォッチャーを作ったら、各イベントに処理(アクション)を登録します。Register-ObjectEventを使うと、イベントが発生したときにスクリプトブロックが非同期で実行されます。
# 変更検知時の処理(Created / Changed / Deleted 共通)
$action = {
$info = $Event.SourceEventArgs
$path = $info.FullPath
$type = $info.ChangeType
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Write-Host "[$time] $type : $path"
}
Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action -SourceIdentifier "Watcher_Created"
Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action -SourceIdentifier "Watcher_Changed"
Register-ObjectEvent -InputObject $watcher -EventName "Deleted" -Action $action -SourceIdentifier "Watcher_Deleted"
# リネームは専用のイベント引数を使う
$renameAction = {
$info = $Event.SourceEventArgs
$oldPath = $info.OldFullPath
$newPath = $info.FullPath
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Write-Host "[$time] Renamed : $oldPath -> $newPath"
}
Register-ObjectEvent -InputObject $watcher -EventName "Renamed" -Action $renameAction -SourceIdentifier "Watcher_Renamed"
# 監視を開始
$watcher.EnableRaisingEvents = $true
Write-Host "モニタリング開始:$watcher.Path"
Write-Host "Ctrl+C で停止できます。停止時はイベント解除と Dispose() が実行されます。"
# スクリプトを終了させないように待機
while ($true) { Start-Sleep -Seconds 1 }Register-ObjectEventで各イベントに処理を登録します。RenamedはOldFullPathとFullPathを使って変更前後のパスを取得できます。EnableRaisingEvents = $trueにするとウォッチャーがイベント発火を開始します。while ($true) { Start-Sleep -Seconds 1 }の箇所は次のステップで少し改良します。
スクリプトを止めるときはイベントの登録を解除しておくとリソースが解放されてきれいです。
try {
# スクリプトを終了させないように待機
while ($true) { Start-Sleep -Seconds 1 }
}
finally {
# イベントの登録を解除
Unregister-Event -SourceIdentifier "Watcher_Created" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Changed" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Deleted" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Renamed" -ErrorAction SilentlyContinue
# 監視を停止してからオブジェクトを破棄
$watcher.EnableRaisingEvents = $false
$watcher.Dispose()
Write-Host "モニタリング終了"
}サンプル(分割なしフルコード)
$watcher = New-Object System.IO.FileSystemWatcher
# 監視するフォルダのパス(記事の C:\Logs をそのまま使用)
$watcher.Path = "C:\Logs"
# 監視対象のファイル名フィルター(*.*で全ファイル)
$watcher.Filter = "*.*"
# サブフォルダも監視するか
$watcher.IncludeSubdirectories = $false
# 何の変化を検知するか(NotifyFilters)
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite `
-bor [System.IO.NotifyFilters]::FileName
# 変更検知時の処理(Created / Changed / Deleted 共通)
$action = {
$info = $Event.SourceEventArgs
$path = $info.FullPath
$type = $info.ChangeType
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Write-Host "[$time] $type : $path"
}
Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action -SourceIdentifier "Watcher_Created"
Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action -SourceIdentifier "Watcher_Changed"
Register-ObjectEvent -InputObject $watcher -EventName "Deleted" -Action $action -SourceIdentifier "Watcher_Deleted"
# リネームは SourceEventArgs が異なる(OldFullPath と FullPath を使用する)
$renameAction = {
$info = $Event.SourceEventArgs
$oldPath = $info.OldFullPath
$newPath = $info.FullPath
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Write-Host "[$time] Renamed : $oldPath -> $newPath"
}
Register-ObjectEvent -InputObject $watcher -EventName "Renamed" -Action $renameAction -SourceIdentifier "Watcher_Renamed"
# 監視を開始する(EnableRaisingEvents = $true にしないとイベントが発火しない)
$watcher.EnableRaisingEvents = $true
Write-Host "モニタリング開始:$watcher.Path"
Write-Host "Ctrl+C で停止できます。停止時はイベント解除と Dispose() が実行されます。"
try {
# スクリプトを終了させないように待機
while ($true) { Start-Sleep -Seconds 1 }
}
finally {
# イベントの登録を解除
Unregister-Event -SourceIdentifier "Watcher_Created" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Changed" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Deleted" -ErrorAction SilentlyContinue
Unregister-Event -SourceIdentifier "Watcher_Renamed" -ErrorAction SilentlyContinue
# 監視を停止してからオブジェクトを破棄
$watcher.EnableRaisingEvents = $false
$watcher.Dispose()
Write-Host "モニタリング終了"
}サンプルの実行結果
上記のサンプルのPowerShellを実行してからC:\Logsディレクトリで下記を行った時の出力です。
- エクスプローラー上で「右クリック > 新規作成 > テキストドキュメント」を選択。
→新規 テキスト ドキュメント.txtが出来上がってリネームで新規 テキスト ドキュメントが選択されている状態。 - そのままtestと入力して
test.txtでファイル名を確定。 test.txtを開いて「Test Write」と入力して保存。- 保存後
test.txtを削除。
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Watcher_Created NotStarted False …
2 Watcher_Changed NotStarted False …
3 Watcher_Deleted NotStarted False …
4 Watcher_Renamed NotStarted False …
モニタリング開始:System.IO.FileSystemWatcher.Path
Ctrl+C で停止できます。停止時はイベント解除と Dispose() が実行されます。
[2026-05-16 16:39:00] Created : C:\Logs\新規 テキスト ドキュメント.txt
[2026-05-16 16:39:03] Renamed : C:\Logs\新規 テキスト ドキュメント.txt -> D:\Logs\test.txt
[2026-05-16 16:39:14] Changed : C:\Logs\test.txt
[2026-05-16 16:40:05] Deleted : C:\Logs\test.txt
モニタリング終了ps1ファイルとして保存して実行できない場合は、PowerShellの実行ポリシーも確認してみてください。

実用サンプル①: 変更ログをファイルに記録する
コンソールに出力するだけでなく、ログファイルに追記する例です。
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Logs"
$watcher.Filter = "*.log"
$watcher.IncludeSubdirectories = $false
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite `
-bor [System.IO.NotifyFilters]::FileName
$logFile = "C:\Temp\watch_log.txt"
$action = {
$info = $event.SourceEventArgs
$path = $info.FullPath
$type = $info.ChangeType
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$entry = "[$time] $type : $path"
Add-Content -Path $using:logFile -Value $entry
Write-Host $entry
}
Register-ObjectEvent $watcher "Created" -Action $action -SourceIdentifier "WL_Created"
Register-ObjectEvent $watcher "Changed" -Action $action -SourceIdentifier "WL_Changed"
Register-ObjectEvent $watcher "Deleted" -Action $action -SourceIdentifier "WL_Deleted"
$watcher.EnableRaisingEvents = $true
Write-Host "監視中... ログ出力先: $logFile"
while ($true) { Start-Sleep -Seconds 1 }スクリプトブロック内で外側の変数を参照するときは$using:変数名を使います。これを忘れると変数が$nullになってはまりがちです。
実用サンプル②: ファイル作成を検知して自動コピーする
特定フォルダに新規ファイルが置かれたら別フォルダへ自動コピーする例です。
取り込みフォルダの監視などで使えます。
$srcFolder = "C:\Drop"
$dstFolder = "C:\Processed"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $srcFolder
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $false
$watcher.NotifyFilter = [System.IO.NotifyFilters]::FileName
$action = {
$info = $event.SourceEventArgs
$srcPath = $info.FullPath
$dst = $using:dstFolder
$name = $info.Name
# ファイルが書き込み完了するまで少し待つ
Start-Sleep -Milliseconds 500
try {
Copy-Item -Path $srcPath -Destination "$dst\$name" -Force
Write-Host "コピー完了: $name"
} catch {
Write-Host "コピー失敗: $name - $_"
}
}
Register-ObjectEvent $watcher "Created" -Action $action -SourceIdentifier "AC_Created"
$watcher.EnableRaisingEvents = $true
Write-Host "自動コピー監視中... $srcFolder -> $dstFolder"
while ($true) { Start-Sleep -Seconds 1 }ファイル作成直後はまだ書き込み中のことがあります。500ミリ秒など短時間待ってから処理すると失敗を減らせますが、ファイルサイズによっては待ち時間の調整が必要です。
注意点
⚠️ 多数の変更が短時間に発生すると、内部バッファがあふれてイベントを取りこぼすことがあります。
FileSystemWatcherには内部バッファがあり、イベントが大量発生するとあふれてErrorイベントが発火します。ハンドリングの例です。PowerShellRegister-ObjectEvent $watcher Error -Action { Write-Warning "監視エラーが発生しました" }
$watcher.InternalBufferSize = 65536のようにバッファを大きくすることで多少は緩和できます(デフォルトは 8192)。
その他のはまりポイントをまとめておきます。
EnableRaisingEvents = $trueを忘れるとイベントが一切発火しない。Changedイベントは1回の保存で複数回発火することがある。(エディタが一時ファイルを経由して保存するため)- スクリプト終了時に
Dispose()しないと、プロセスが残りフォルダのロックが続く場合がある。 - スクリプトブロック内で外部変数を使うときは
$using:変数名。
Q&A
まとめ
- PowerShellは.NETの
FileSystemWatcherクラスをそのまま使えるよ。 Register-ObjectEventでイベント駆動の非同期処理が書けるよ。EnableRaisingEvents = $trueを忘れると何も起きないよ。- スクリプトブロック内の外部変数は
$using:変数で参照してね。 - 終了時は
Unregister-EventとDispose()で後片付けしておくのが安全だよ。
ポーリングより負荷が低く、リアルタイム性もそれなりに高いのがFileSystemWatcherの便利なところですね!
業務の自動化スクリプトにぜひ組み込んでみてください。
ログファイルの追記を確認したいだけなら、Get-Content -Waitを使う方法もあります。







