В сценариях, когда вам нужно отслеживать изменения в определенной папке и выполнять какое-то автоматические действие при обнаружении изменений в файле/директории (отправить уведомление, логировать событие в текстовый файл или журнал событий, запускать скрипт обработки и т.д.) вместо использования классического аудита событий доступа к папкам в Windows, можно использовать встроенный .Net класс FileSystemWatcher. Этот класс позволяет в реальном времени отслеживать события создания, удаления, переименования и редактирования файлов (и папок). В статье мы рассмотрим, как следить за изменениями объектов файловой системы в определенной директории через FileSystemWatcher из PowerShell.
Создадим объект класса FileSystemWatcher:
$watcher = New-Object System.IO.FileSystemWatcher
Хотите ли вы отслеживать события во вложенных директориях:
$watcher.IncludeSubdirectories = $true
Указать целевую директорию, которой будете мониторить:
$watcher.Path = "C:\Configs"
Включить генерацию событий при обнаружении изменений:
$watcher.EnableRaisingEvents = $true
Настраиваем действие, которое нужно выполнить, если обнаружены изменения в отслеживаемой директории.
$action = {
$changeType = $Event.SourceEventArgs.ChangeType
$path = $Event.SourceEventArgs.FullPath
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | $changeType | $path" | Out-File -FilePath "C:\logs\watch.log" -Append
}
Осталось подписаться на определенные типы событий, которые вы хотите отслеживать. Всего доступно четыре типа событий Created, Changed, Deleted, Renamed. Я хочу отслеживать все эти типы событий, поэтому зарегистрирую все четыре подписки:
Register-ObjectEvent $watcher Created -Action $action -SourceIdentifier FSCreate
Register-ObjectEvent $watcher Changed -Action $action -SourceIdentifier FSChange
Register-ObjectEvent $watcher Deleted -Action $action -SourceIdentifier FSDelete
Register-ObjectEvent $watcher Renamed -Action $action -SourceIdentifier FSRename

Мониторинг изменений в директории будет работать до тех пор, пока жив родительский процесс powershell.exe. Ядро Windows само отслеживает изменения в файловой системе и отправляет уведомления в ваши подписки. Cкрипт запускается один раз, поэтому не нужно добавлять в него циклы опроса. Но при запуске кода через PS1 скрипт, можно добавить в конец бесконечный цикл:
while ($true) {sleep 5}
Если запустить такой скрипт, то при любых изменениях в отслеживаемой папке, информация о каждом событии будет записываться в указанный лог файл. В отдельной PowerShell консоли я настроил вывод содержимого лог файла на экран с обновлением в реальном времени:
Get-Content -Path "C:\logs\watch.log" -wait

Чтобы остановить мониторинг, выполните:
Unregister-Event -SourceIdentifier FSCreate
Unregister-Event -SourceIdentifier FSChange
Unregister-Event -SourceIdentifier FSDelete
Unregister-Event -SourceIdentifier FSRename
Если нужно отслеживать изменения только в определенных типах файлах, в начале скрипта можно определить фильтр:
$filter = "*.cfg"
$watcher.Filter = $filter
Чтобы FileSystemWatcher реагировал только на определенные события, можно использовать NotifyFilter. Например, я хочу реагировать только если изменены NTFS права доступа, размер или имя файла:
$watcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::Size -bor [System.IO.NotifyFilters]::Security
Если нужно для определенного события сделать отдельный обработчик (действие), его можно указать сразу при подписке через параметр Action. В этом примере я хочу при переименовании файла, записывать в лог старое и новое имя файла.
Register-ObjectEvent $watcher Renamed -SourceIdentifier FSRename -Action {
$details = $Event.SourceEventArgs
$timeStamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$logMessage = "$timeStamp | Renamed | From: $($details.OldFullPath) To: $($details.FullPath)"
$logMessage | Out-File -FilePath "C:\logs\watch.log" -Append
}
При большом количестве событий (десятки/сотни в секунду), чтобы не терять события, можно увеличить размер внутреннего буфера до 64 кб:
$watcher.InternalBufferSize = 65536
или запускать каждой действие через отдельный фоновый процесс, добавив в Action вызов кода обработчика через
Start-Job
Итак, мы рассмотрели, как с помощью PowerShell реализовать простую систему отслеживания изменения, создания, удаления и переименования файлов в указанной папке и автоматической реакции на события.


Можно ли отслеживать, какой именно пользователь произвёл действия с файлами?
Нет, тут отслеживается сам факт изменения.
Если нужен пользователь — то придется настраивать классический аудит доступа _https://winitpro.ru/index.php/2024/02/21/audit-dostupa-k-fajlam-i-papkam-windows/
И? Назначил на отслеживание папку D:\TEMP
Все запустил в PS
И …
Id Name PSJobTypeName State HasMoreData Location Command-- ---- ------------- ----- ----------- -------- -------
1 FSCreate NotStarted False :
2 FSChange NotStarted False :
3 FSDelete NotStarted False :
4 FSRenam NotStarted False :
В папке C:\LOGS полная тишина, что бы не делал в папке D:\TEMP
Странно, обычно все стабильно работает. Проверяй пути к папкам, они должны существовать. консоль powershell не закрывай. если запускаешь через PS1 скрипт то, не забывай цикл в конце
while ($true) {sleep 5}У меня та же ерунда. И без сообщений об ошибках. Просто «NotStarted».
Если написать обработку ошибок:
# Настраиваем действие, которое нужно выполнить, если обнаружены изменения в отслеживаемой директории.
$action = {
try {
$changeType = $Event.SourceEventArgs.ChangeType
$path = $Event.SourceEventArgs.FullPath
«$(Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’) | $changeType | $path» |
Out-File -FilePath $MyLogPath -Append
} catch {
Write-Host «Ошибка при записи в лог: $_»
}
}
То вылезает ошибка: «Ошибка при записи в лог: Не удается привязать аргумент к параметру «FilePath», так как он имеет значение NULL.»
Возможно из-за того, что в скрипте используется фиксированный путь к папке с логом -FilePath «C:\logs\watch.log»
Каталог должен существовать. Можно поправить на
"$env:USERPROFILE\watch.log"Тогда лог пишется в текущий профиль.А так, все работает нормально (w11 и ws2025).
Вот теперь скрипт работает:
# Скрипт отслеживающий изменения файлов в папке с помощью PowerShell.
# При любых изменениях в отслеживаемой папке, информация о каждом событии будет записываться в указанный лог файл.
# События создания или копирования файлов отображаются сразу тремя строчками-событиями в логе: Created, Changed, Changed.
# Выход из скрипта по клавише «q», независимо от языка.
# В отдельной PowerShell консоли можно настроить вывод содержимого лог файла на экран с обновлением в реальном времени:
#Get-Content -Path «C:\tmp\logs\watch.log» -wait
# Проверим политику выполнения:
#Get-ExecutionPolicy
#Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# Указываем целевую директорию, которую будем мониторить:
$MyWatcherPath = «C:\temp»
# Путь к LOG:
$global:MyLogPath = «C:\tmp\logs\watch.log»
# begin
# Проверка существования каталога
if (Test-Path -Path $MyWatcherPath -PathType Container) {
} else {
Write-Host «Папка $MyWatcherPath не найдена — выход.» -ForegroundColor Red
exit 1
}
# Создадим объект класса FileSystemWatcher:
$watcher = New-Object System.IO.FileSystemWatcher
# Будем отслеживать события во вложенных директориях:
$watcher.IncludeSubdirectories = $true
$watcher.Path = $MyWatcherPath
«`nНачало лога $(Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’)» | Out-File -FilePath $MyLogPath -Append
«Отслеживание изменений в папке $($watcher.Path)» | Out-File -FilePath $MyLogPath -Append
#Включаем генерацию событий при обнаружении изменений:
$watcher.EnableRaisingEvents = $true
# При большом количестве событий (десятки/сотни в секунду), чтобы не терять события, можно увеличить размер внутреннего буфера до 64 кб:
$watcher.InternalBufferSize = 65536
# или запускать каждое действие через отдельный фоновый процесс, добавив в Action вызов кода обработчика через Start-Job
# Настраиваем действие, которое нужно выполнить, если обнаружены изменения в отслеживаемой директории.
#$action={Write-Host «Привет, мир!»}
$action = {
try {
$changeType = $Event.SourceEventArgs.ChangeType
$path = $Event.SourceEventArgs.FullPath
$timeStamp = Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’
«$timeStamp | $changeType | $path» | Write-Host
«$timeStamp | $changeType | $path» | Out-File -FilePath $global:MyLogPath -Append
} catch {
Write-Host «Ошибка при записи в лог: $_»
Write-Host «Тип изменения: $changeType»
Write-Host «Файл: $path»
}
}
# Если нужно отслеживать изменения только в определенных типах файлах, в начале скрипта можно определить фильтр:
#$filter = «*.cfg»
#$watcher.Filter = $filter
#Всего доступно четыре типа событий Created, Changed, Deleted, Renamed. Я хочу отслеживать все эти типы событий, поэтому зарегистрирую все четыре подписки:
Register-ObjectEvent $watcher Created -Action $action -SourceIdentifier FSCreate
Register-ObjectEvent $watcher Changed -Action $action -SourceIdentifier FSChange
Register-ObjectEvent $watcher Deleted -Action $action -SourceIdentifier FSDelete
#Register-ObjectEvent $watcher Renamed -Action $action -SourceIdentifier FSRename
# Если нужно для определенного события сделать отдельный обработчик (действие),
# его можно указать сразу при подписке через параметр Action.
# В этом примере при переименовании файла, записывается в лог старое и новое имя файла.
Register-ObjectEvent $watcher Renamed -SourceIdentifier FSRename -Action {
$details = $Event.SourceEventArgs
$timeStamp = Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’
$logMessage = «$timeStamp | Renamed | From: $($details.OldFullPath) To: $($details.FullPath)»
$logMessage | Write-Host
$logMessage | Out-File -FilePath $MyLogPath -Append
}
# При запуске кода через PS1 скрипт, нужно добавить в конец бесконечный цикл:
#while ($true) {sleep 5}
# Добавляем проверку состояния событий
#while ($true) {
# Start-Sleep -Seconds 5
# Get-EventSubscriber | Format-Table -AutoSize
#}
# Бесконечный цикл
Write-Host «Нажмите ‘q’ для выхода из цикла…» -ForegroundColor Yellow
while ($true) {
# Проверяем, была ли нажата клавиша
if ([Console]::KeyAvailable) {
$key = [Console]::ReadKey($true) # $true — не показывать нажатую клавишу
if ($key.Key -eq ‘Q’) {
Write-Host «Клавиша ‘q’ нажата, выходим из цикла…» -ForegroundColor Green
break
}
}
# Задержка
Start-Sleep -Seconds 2
# Get-EventSubscriber | Format-Table -AutoSize
}
Write-Host «Скрипт завершает работу» -ForegroundColor Cyan
# eof
«Конец лога $(Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’)» | Out-File -FilePath $MyLogPath -Append
#Чтобы остановить мониторинг, выполните:
Unregister-Event -SourceIdentifier FSCreate
Unregister-Event -SourceIdentifier FSChange
Unregister-Event -SourceIdentifier FSDelete
Unregister-Event -SourceIdentifier FSRename