Watch file for changes and run command with powershell

前端 未结 7 816
终归单人心
终归单人心 2020-11-28 07:01

Is there any simple way(i.e., script) to watch file in Powershell and run commands if file changes. I have been googling but can\'t find simple solution. Basically I run scr

相关标签:
7条回答
  • 2020-11-28 07:37
    1. Calculate the hash of a list of files
    2. Store it in a dictionary
    3. Check each hash on an interval
    4. Perform action when hash is different

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

    Example usage:

    $c = 'send-mailmessage -to "admin@whatever.com" -from "watch@whatever.com" -subject "$($file.Name) has been altered!"'
    $f = ls C:\MyFolder\aFile.jpg
    
    watch $f $c 60
    
    0 讨论(0)
  • 2020-11-28 07:38

    Here is another option.

    I just needed to write my own to watch and run tests within a Docker container. Jan's solution is much more elegant, but FileSystemWatcher is broken within Docker containers presently. My approach is similar to Vasili's, but much lazier, trusting the file system's write time.

    Here's the function I needed, which runs the command block each time the file changes.

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

    Here is one that waits until the file changes, runs the block, then exits.

    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 讨论(0)
  • 2020-11-28 07:42

    I will add another answer, because my previous one did miss the requirements.

    Requirements

    • Write a function to WAIT for a change in a specific file
    • When a change is detected the function will execute a predefined command and return execution to the main script
    • File path and command are passed to the function as parameters

    There is already an answer using file hashes. I want to follow my previous answer and show you how this can be accomplish using 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
    
    0 讨论(0)
  • 2020-11-28 07:45

    I had a similar problem. I first wanted to use Windows events and register, but this would be less fault-tolerant as the solution beneath.
    My solution was a polling script (intervals of 3 seconds). The script has a minimal footprint on the system and notices changes very quickly. During the loop my script can do more things (actually I check 3 different folders).

    My polling script is started through the task manager. The schedule is start every 5 minutes with the flag stop-when-already-running. This way it will restart after a reboot or after a crash.
    Using the task manager for polling every 3 seconds is too frequent for the task manager. When you add a task to the scheduler make sure you do not use network drives (that would call for extra settings) and give your user batch privileges.

    I give my script a clean start by shutting it down a few minutes before midnight. The task manager starts the script every morning (the init function of my script will exit 1 minute around midnight).

    0 讨论(0)
  • 2020-11-28 07:48

    Here is the solution I ended up with based on several of the previous answers here. I specifically wanted:

    1. My code to be code, not a string
    2. My code to be run on the I/O thread so I can see the console output
    3. My code to be called every time there was a change, not once

    Side note: I've left in the details of what I wanted to run due to the irony of using a global variable to communicate between threads so I can compile Erlang code.

    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
    

    You could pass in folder/filter/action into watch if you want something more generic. Hopefully this is a helpful starting point for someone else.

    0 讨论(0)
  • 2020-11-28 07:51

    You can use the System.IO.FileSystemWatcher to monitor a file.

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

    See also this article

    0 讨论(0)
提交回复
热议问题