使用powershell监视文件以进行更改并运行命令

时间:2015-03-15 22:06:45

标签: powershell scripting cmd

是否有任何简单的方法(即脚本)在powershell中查看文件并在文件更改时运行命令。我一直在谷歌搜索,但找不到简单的解决方案。基本上我在powershell中运行脚本,如果文件发生了变化,那么powershell会运行其他命令。

  

修改

好吧我觉得我搞错了。我不需要脚本,一个我可以包含在$PROFILE.ps1文件中的需要函数。但是,我仍然很努力,我仍然无法写出来,所以我会给予赏金。它必须如下所示:

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

有一个npm模块正在做我想要的事情watch,但它只监视文件夹而不是文件,而不是PowerShell xD。

7 个答案:

答案 0 :(得分:25)

以下是我在代码段中找到的示例。希望它更全面一点。

首先,您需要创建一个文件系统观察程序,然后您订阅观察者正在生成的事件。此示例侦听“创建”事件,但可以轻松修改以注意“更改”。

$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 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主机(或shell)的上下文中执行。它可以与主机会话交互,但不知道注册代码的原始脚本。在您的代码被触发时,原始脚本可能已经完成。

以下是代码:

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 "Changed" -Action $changeAction
}

 Register-Watcher "c:\temp"

运行此代码后,更改" C:\ temp"中的任何文件。目录(或您指定的任何其他目录)。您将看到一个触发代码执行的事件。

此外,您可以注册的有效FileSystemWatcher事件是"已更改","已创建","已删除"和#34;重命名"。

答案 1 :(得分:6)

我会添加另一个答案,因为我之前的答案确实错过了要求。

要求

  • 将函数写入 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

答案 2 :(得分:4)

您可以使用System.IO.FileSystemWatcher来监控文件。

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

另见this article

答案 3 :(得分:3)

  1. 计算文件列表的哈希值
  2. 将其存储在字典中
  3. 检查间隔
  4. 上的每个哈希值
  5. 当哈希值不同时执行操作

  6. 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 "admin@whatever.com" -from "watch@whatever.com" -subject "$($file.Name) has been altered!"'
    $f = ls C:\MyFolder\aFile.jpg
    
    watch $f $c 60
    

答案 4 :(得分:3)

这是我最终得到的解决方案,基于以前的几个答案。我特别想要:

  1. 我的代码是代码,而不是字符串
  2. 我的代码将在I / O线程上运行,因此我可以看到控制台输出
  3. 每次有更改时都会调用我的代码,而不是一次
  4. 旁注:由于使用全局变量在线程之间进行通信具有讽刺意味,因此我可以编写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
    

    如果你想要更通用的东西,你可以将文件夹/过滤器/动作传递给观察。希望这对其他人来说是一个有用的起点。

答案 5 :(得分:2)

我有类似的问题。我首先想要使用Windows事件并进行注册,但这样下的解决方案的容错性会降低 我的解决方案是一个轮询脚本(间隔为3秒)。该脚本在系统上具有最小的占用空间,并且通知变化非常快。在循环中我的脚本可以做更多的事情(实际上我检查3个不同的文件夹)。

我的轮询脚本是通过任务管理器启动的。计划从每5分钟开始,标志停止 - 已经运行。这样它会在重启后或崩溃后重新启动 对于任务管理器来说,使用任务管理器每3秒进行一次轮询非常频繁。 向调度程序添加任务时,请确保不使用网络驱动器(这将需要额外的设置)并为您的用户授予批量权限。

我在午夜前几分钟关闭它,让我的脚本干净利落。任务管理器每天早上启动脚本(我的脚本的init函数将在午夜左右退出)。

答案 6 :(得分:0)

这是另一种选择。

我只需编写自己的内容即可在Docker容器中观看和运行测试。 Jan的解决方案更优雅,但FileSystemWatcher目前在Docker容器中被破坏。我的方法与Vasili相似,但更加懒惰,相信文件系统的写入时间。

这是我需要的功能,每次文件更改时都会运行命令块。

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
}