Смотреть файл для изменений и запускать команду с помощью powershell

Есть ли простой способ (например, script) смотреть файл в командной оболочке и запускать команды, если изменения файла. Я был googling, но не могу найти простое решение. В основном я запускаю script в powershell, и если изменения файла затем powershell запускают другие команды.

ИЗМЕНИТЬ

Хорошо, я думаю, что допустил ошибку. Мне не нужна script, функция необходимости, которую я могу включить в мой файл $PROFILE.ps1. Но все-таки я был Центровочный трудно и до сих пор я не могу писать, поэтому я дам награду. Он должен выглядеть следующим образом:

function watch($command, $file) {
  if($file #changed) {
    #run $command
  }
}

Существует модуль npm, который делает то, что я хочу, watch, но он наблюдает только за папками, а не файлами, и это не powershell xD.

Ответ 1

Вот пример, который я нашел в моих фрагментах. Надеюсь, это будет немного более полным.

Сначала вам нужно создать наблюдатель файловой системы, а затем вы подписываетесь на событие, которое генерирует наблюдатель. В этом примере прослушиваются события "Create", но его можно легко изменить, чтобы не пропустить "Change".

$folder = "C:\Users\LOCAL_~1\AppData\Local\Temp\3"
$filter = "*.LOG"
$Watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
    IncludeSubdirectories = $false
    NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onCreated = Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier FileCreated -Action {
   $path = $Event.SourceEventArgs.FullPath
   $name = $Event.SourceEventArgs.Name
   $changeType = $Event.SourceEventArgs.ChangeType
   $timeStamp = $Event.TimeGenerated
   Write-Host "The file '$name' was $changeType at $timeStamp"
   Write-Host $path
   #Move-Item $path -Destination $destination -Force -Verbose
}

Я постараюсь сузить это до ваших требований.

Если вы запускаете это как часть своего скрипта "profile.ps1", вам следует прочитать Сила профилей, которая объясняет различные доступные скрипты профиля и многое другое.

Кроме того, вы должны понимать, что ожидание изменения в папке не может быть выполнено как функция в сценарии. Сценарий профиля должен быть завершен, чтобы начать сеанс PowerShell. Однако вы можете использовать функцию для регистрации события.

Для этого нужно зарегистрировать фрагмент кода, который будет выполняться каждый раз, когда происходит событие. этот код будет выполняться в контексте вашего текущего хоста PowerShell (или оболочки), пока сеанс остается открытым. Он может взаимодействовать с сеансом хоста, но не знает исходного сценария, который зарегистрировал код. Оригинальный сценарий, вероятно, уже завершен к тому времени, когда ваш код будет запущен.

Вот код:

Function Register-Watcher {
    param ($folder)
    $filter = "*.*" #all files
    $watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
        IncludeSubdirectories = $false
        EnableRaisingEvents = $true
    }

    $changeAction = [scriptblock]::Create('
        # This is the code which will be executed every time a file change is detected
        $path = $Event.SourceEventArgs.FullPath
        $name = $Event.SourceEventArgs.Name
        $changeType = $Event.SourceEventArgs.ChangeType
        $timeStamp = $Event.TimeGenerated
        Write-Host "The file $name was $changeType at $timeStamp"
    ')

    Register-ObjectEvent $Watcher -EventName "Changed" -Action $changeAction
}

 Register-Watcher "c:\temp"

После запуска этого кода измените любой файл в каталоге "C:\temp" (или любом другом каталоге, который вы укажете). Вы увидите событие, запускающее выполнение вашего кода.

Также допустимыми событиями FileSystemWatcher, которые вы можете зарегистрировать, являются "Изменено", "Создано", "Удалено" и "Переименовано".

Ответ 2

Я добавлю еще один ответ, потому что мой предыдущий пропустил требования.

Требования

  • Напишите функцию WAIT для изменения определенного файла
  • Когда обнаружено изменение, функция выполнит предопределенную команду и вернется к главному script
  • Путь файла и команда передаются функции как параметры

Уже есть ответ с использованием хэшей файлов. Я хочу выполнить свой предыдущий ответ и показать, как это можно выполнить с помощью FileSystemWatcher.

$File = "C:\temp\log.txt"
$Action = 'Write-Output "The watched file was changed"'
$global:FileChanged = $false

function Wait-FileChange {
    param(
        [string]$File,
        [string]$Action
    )
    $FilePath = Split-Path $File -Parent
    $FileName = Split-Path $File -Leaf
    $ScriptBlock = [scriptblock]::Create($Action)

    $Watcher = New-Object IO.FileSystemWatcher $FilePath, $FileName -Property @{ 
        IncludeSubdirectories = $false
        EnableRaisingEvents = $true
    }
    $onChange = Register-ObjectEvent $Watcher Changed -Action {$global:FileChanged = $true}

    while ($global:FileChanged -eq $false){
        Start-Sleep -Milliseconds 100
    }

    & $ScriptBlock 
    Unregister-Event -SubscriptionId $onChange.Id
}

Wait-FileChange -File $File -Action $Action

Ответ 3

Вы можете использовать System.IO.FileSystemWatcher для мониторинга файла.

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $searchPath
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true

Смотрите также эту статью

Ответ 4

  • Вычислить хэш списка файлов
  • Сохранить его в словаре
  • Проверьте каждый хэш на интервал
  • Выполнить действие, когда хеш отличается

function watch($f, $command, $interval) {
    $sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
    $hashfunction = '[System.BitConverter]::ToString($sha1.ComputeHash([System.IO.File]::ReadAllBytes($file)))'
    $files = @{}
    foreach ($file in $f) {
        $hash = iex $hashfunction
        $files[$file.Name] = $hash
        echo "$hash`t$($file.FullName)"
    }
    while ($true) {
        sleep $interval
        foreach ($file in $f) {
            $hash = iex $hashfunction
            if ($files[$file.Name] -ne $hash) {
                iex $command
            }
        }
    }
}

Пример использования:

$c = 'send-mailmessage -to "[email protected]" -from "[email protected]" -subject "$($file.Name) has been altered!"'
$f = ls C:\MyFolder\aFile.jpg

watch $f $c 60

Ответ 5

Вот решение, на котором я закончил, основываясь на нескольких предыдущих ответах здесь. Я специально хотел:

  • Мой код - это код, а не строка
  • Мой код должен быть запущен в потоке ввода-вывода, поэтому я могу видеть вывод консоли
  • Мой код, который будет вызываться каждый раз, когда произошли изменения, а не один раз

Боковое примечание: я оставил в деталях то, что я хотел запустить из-за иронии использования глобальной переменной для связи между потоками, поэтому я могу скомпилировать код Erlang.

Function RunMyStuff {
    # this is the bit we want to happen when the file changes
    Clear-Host # remove previous console output
    & 'C:\Program Files\erl7.3\bin\erlc.exe' 'program.erl' # compile some erlang
    erl -noshell -s program start -s init stop # run the compiled erlang program:start()
}

Function Watch {    
    $global:FileChanged = $false # dirty... any better suggestions?
    $folder = "M:\dev\Erlang"
    $filter = "*.erl"
    $watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
        IncludeSubdirectories = $false 
        EnableRaisingEvents = $true
    }

    Register-ObjectEvent $Watcher "Changed" -Action {$global:FileChanged = $true} > $null

    while ($true){
        while ($global:FileChanged -eq $false){
            # We need this to block the IO thread until there is something to run 
            # so the script doesn't finish. If we call the action directly from 
            # the event it won't be able to write to the console
            Start-Sleep -Milliseconds 100
        }

        # a file has changed, run our stuff on the I/O thread so we can see the output
        RunMyStuff

        # reset and go again
        $global:FileChanged = $false
    }
}

RunMyStuff # run the action at the start so I can see the current output
Watch

Вы можете перейти в папку/фильтр/действие в часы, если хотите что-то более общее. Надеюсь, это полезная отправная точка для кого-то еще.

Ответ 6

У меня была аналогичная проблема. Сначала я хотел использовать события Windows и зарегистрироваться, но это было бы менее отказоустойчиво, как решение под ним. Моим решением был опрос script (интервалы в 3 секунды). script имеет минимальный след в системе и замечает изменения очень быстро. Во время цикла мой script может делать больше вещей (на самом деле я проверяю 3 разные папки).

Мой опрос script запускается через диспетчер задач. Расписание запускается каждые 5 минут с остановкой флага, когда он уже работает. Таким образом, он перезапустится после перезагрузки или после сбоя.
Использование диспетчера задач для опроса каждые 3 секунды является слишком частым для диспетчера задач. Когда вы добавляете задачу в планировщик, убедитесь, что вы не используете сетевые диски (которые требуют дополнительных настроек) и предоставляют пользовательские пакетные привилегии.

Я даю мой script чистый старт, закрыв его за несколько минут до полуночи. Менеджер задач запускает script каждое утро (функция init моего script выйдет 1 минуту около полуночи).

Ответ 7

Вот еще один вариант.

Мне просто нужно было написать свой собственный, чтобы смотреть и запускать тесты в контейнере Docker. Решение Jan намного более элегантно, но FileSystemWatcher в настоящее время разбит на контейнеры Docker. Мой подход похож на Василия, но гораздо ленивее, доверяя файловому архиву времени записи.

Здесь нужна функция, которая запускает командный блок при каждом изменении файла.

function watch($command, $file) {
    $this_time = (get-item $file).LastWriteTime
    $last_time = $this_time
    while($true) {
        if ($last_time -ne $this_time) {
            $last_time = $this_time
            invoke-command $command
        }
        sleep 1
        $this_time = (get-item $file).LastWriteTime
    }
}

Вот один, который ждет, пока файл не изменится, запустит блок и выйдет.

function waitfor($command, $file) {
    $this_time = (get-item $file).LastWriteTime
    $last_time = $this_time
    while($last_time -eq $this_time) {
        sleep 1
        $this_time = (get-item $file).LastWriteTime
    }
    invoke-command $command
}