web-dev-qa-db-ja.com

ファイルの変更を監視し、powershellでコマンドを実行します

PowerShellでファイルを監視し、ファイルが変更された場合にコマンドを実行する簡単な方法(スクリプトなど)はありますか私はグーグルで検索してきましたが、簡単な解決策が見つかりません。基本的に私はpowershellでスクリプトを実行し、ファイルが変更された場合、powershellは他のコマンドを実行します。

編集

間違いだと思います。スクリプトは必要ありません。$PROFILE.ps1ファイルに含めることができる関数が必要です。しかし、それでも、私は懸命にツルーイングをしていましたが、それでも書くことができません。次のようになります。

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

watch、私がしたいことをしているnpmモジュールがありますが、それはファイルではなくフォルダを監視するだけで、PowerShell xDではありません。

17
IGRACH

これがスニペットで見つかった例です。少しでも包括的であることを願っています。

最初にファイルシステムウォッチャーを作成し、その後ウォッチャーが生成するイベントにサブスクライブする必要があります。この例は「作成」イベントをリッスンしますが、「変更」に注意するように簡単に変更できます。

$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」スクリプトの一部として実行する場合、 The Power of Profiles を読む必要があります。これは、使用可能なさまざまなプロファイルスクリプトなどを説明しています。

また、フォルダー内の変更を待機することは、スクリプト内の関数として実行できないことを理解する必要があります。 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イベントは、「変更」、「作成」、「削除」、および「名前変更」です。

30
Jan Chrbolka

以前の回答が要件を満たしていなかったため、別の回答を追加します。

要件

  • 特定のファイルの変更のために[〜#〜] wait [〜#〜]に関数を書く
  • 変更が検出されると、関数は定義済みのコマンドを実行し、実行をメインスクリプトに戻します
  • ファイルのパスとコマンドはパラメーターとして関数に渡されます

ファイルハッシュを使用した回答が既にあります。以前の回答に従い、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
7
Jan Chrbolka

System.IO.FileSystemWatcher ファイルを監視します。

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

この記事 も参照してください

5
bytecode77

ここに、以前のいくつかの回答に基づいて私が結んだ解決策があります。私は特に欲しかった:

  1. 私のコードは文字列ではなくコードになります
  2. I/Oスレッドで実行されるコードは、コンソール出力を見ることができます
  3. 一度ではなく変更があったときに毎回呼び出される私のコード

サイドノート:グローバル変数を使用してスレッド間で通信し、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

より一般的なものが必要な場合は、folder/filter/actionをwatchに渡すことができます。うまくいけば、これは他の誰かにとって役立つ出発点になるでしょう。

3
Ed'
  1. ファイルのリストのハッシュを計算する
  2. 辞書に保存する
  3. 間隔で各ハッシュを確認する
  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
3
Vasili Syrakis

同様の問題がありました。私は最初にWindowsイベントを使用して登録したかったのですが、これはその下のソリューションほどフォールトトレラントではありませんでした。
私の解決策は、ポーリングスクリプト(3秒間隔)でした。このスクリプトは、システム上のフットプリントが最小限であり、変更を非常にすばやく認識します。ループ中に、スクリプトはもっと多くのことができます(実際には3つの異なるフォルダーをチェックします)。

ポーリングスクリプトは、タスクマネージャーを介して開始されます。スケジュールは5分ごとに開始され、フラグはすでに実行されているときに停止します。これにより、再起動後またはクラッシュ後に再起動します。
3秒ごとにポーリングするためにタスクマネージャーを使用することは、タスクマネージャーにとってあまりにも頻繁です。スケジューラにタスクを追加するときは、ネットワークドライブ(追加の設定が必要)を使用しないようにし、ユーザーにバッチ特権を付与します。

真夜中の数分前にスクリプトをシャットダウンすることで、スクリプトをクリーンに開始します。タスクマネージャーはスクリプトを毎朝開始します(スクリプトのinit関数は真夜中の1分後に終了します)。

2
Walter A

別のオプションがあります。

Dockerコンテナー内でテストを監視および実行するために自分で作成する必要がありました。 Janのソリューションははるかにエレガントですが、FileSystemWatcherは現在Dockerコンテナー内で壊れています。私のアプローチはVasiliのアプローチに似ていますが、ファイルシステムの書き込み時間を信頼する、はるかに怠lazです。

ファイルが変更されるたびにコマンドブロックを実行する必要な関数を次に示します。

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
}
0
bjtucker