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

时间:2022-05-09 01:06:30

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 script in powershell and if file changes then powershell run other commands.

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

EDIT

编辑

Ok I think I made a mistake. I don't need script, a need function that I can include in my $PROFILE.ps1 file. But still, I was truing hard and still I'm unable to write it, so I will give bounty. It have to look like this:

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

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

There is a npm module that is doing what I want, watch , but it only watches for folders not files, and it's not powershell xD.

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

7 个解决方案

#1


23  

Here is an example I have found in my snippets. Hopefully it is a little bit more comprehensive.

这是我在我的代码片段中找到的一个例子。希望它更全面一点。

First you need to create a file system watcher and subsequently you subscribe to an event that the watcher is generating. This example listens for “Create” events, but could easily be modified to watch out for “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 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
}

I will try to narrow this down to your requirements.

我会尝试根据您的要求缩小范围。

If you run this as part of your "profile.ps1" script you should read The Power of Profiles which explains the different profile scripts available and more.

如果您将此作为“profile.ps1”脚本的一部分运行,则应阅读“配置文件的强大功能”,其中介绍了可用的不同配置文件脚本等。

Also, you should understand that waiting for a change in a folder can't be run as a function in the script. The profile script has to be finished, for your PowerShell session to start. You can, however use a function to register an event.

此外,您应该了解等待文件夹中的更改不能作为脚本中的函数运行。必须完成配置文件脚本才能启动PowerShell会话。但是,您可以使用函数注册事件。

What this does, is register a piece of code, to be executed every time an event is triggered. This code will be executed in the context of your current PowerShell host (or shell) while the session remains open. It can interact with the host session, but has no knowledge of the original script that registered the code. The original script has probably finished already, by the time your code is triggered.

这样做,就是注册一段代码,每次触发事件时都要执行。当会话保持打开时,此代码将在当前PowerShell主机(或shell)的上下文中执行。它可以与主机会话交互,但不知道注册代码的原始脚本。在您的代码被触发时,原始脚本可能已经完成。

Here is the code:

这是代码:

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"

After running this code, change any file in the "C:\temp" directory (or any other directory you specify). You will see an event triggering execution of your code.

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

Also, valid FileSystemWatcher events you can register are "Changed", "Created", "Deleted" and "Renamed".

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

#2


6  

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
  • 将函数写入WAIT以更改特定文件
  • 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.

已经有一个使用文件哈希的答案。我想按照我之前的回答向您展示如何使用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


4  

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

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

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

See also this article

另见本文

#4


3  

  1. Calculate the hash of a list of files
  2. 计算文件列表的哈希值
  3. Store it in a dictionary
  4. 将其存储在字典中
  5. Check each hash on an interval
  6. 检查间隔上的每个哈希值
  7. Perform action when hash is different
  8. 哈希值不同时执行操作

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

#5


3  

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. 我的代码是代码,而不是字符串
  3. My code to be run on the I/O thread so I can see the console output
  4. 我的代码在I / O线程上运行,所以我可以看到控制台输出
  5. My code to be called every time there was a change, not once
  6. 每次有变更时都要调用我的代码,而不是一次

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.

旁注:由于使用全局变量在线程之间进行通信具有讽刺意味,因此我可以编译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

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.

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

#6


2  

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).

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

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.

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

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).

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

#7


0  

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.

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

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
}

#1


23  

Here is an example I have found in my snippets. Hopefully it is a little bit more comprehensive.

这是我在我的代码片段中找到的一个例子。希望它更全面一点。

First you need to create a file system watcher and subsequently you subscribe to an event that the watcher is generating. This example listens for “Create” events, but could easily be modified to watch out for “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 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
}

I will try to narrow this down to your requirements.

我会尝试根据您的要求缩小范围。

If you run this as part of your "profile.ps1" script you should read The Power of Profiles which explains the different profile scripts available and more.

如果您将此作为“profile.ps1”脚本的一部分运行,则应阅读“配置文件的强大功能”,其中介绍了可用的不同配置文件脚本等。

Also, you should understand that waiting for a change in a folder can't be run as a function in the script. The profile script has to be finished, for your PowerShell session to start. You can, however use a function to register an event.

此外,您应该了解等待文件夹中的更改不能作为脚本中的函数运行。必须完成配置文件脚本才能启动PowerShell会话。但是,您可以使用函数注册事件。

What this does, is register a piece of code, to be executed every time an event is triggered. This code will be executed in the context of your current PowerShell host (or shell) while the session remains open. It can interact with the host session, but has no knowledge of the original script that registered the code. The original script has probably finished already, by the time your code is triggered.

这样做,就是注册一段代码,每次触发事件时都要执行。当会话保持打开时,此代码将在当前PowerShell主机(或shell)的上下文中执行。它可以与主机会话交互,但不知道注册代码的原始脚本。在您的代码被触发时,原始脚本可能已经完成。

Here is the code:

这是代码:

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"

After running this code, change any file in the "C:\temp" directory (or any other directory you specify). You will see an event triggering execution of your code.

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

Also, valid FileSystemWatcher events you can register are "Changed", "Created", "Deleted" and "Renamed".

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

#2


6  

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
  • 将函数写入WAIT以更改特定文件
  • 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.

已经有一个使用文件哈希的答案。我想按照我之前的回答向您展示如何使用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


4  

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

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

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

See also this article

另见本文

#4


3  

  1. Calculate the hash of a list of files
  2. 计算文件列表的哈希值
  3. Store it in a dictionary
  4. 将其存储在字典中
  5. Check each hash on an interval
  6. 检查间隔上的每个哈希值
  7. Perform action when hash is different
  8. 哈希值不同时执行操作

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

#5


3  

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. 我的代码是代码,而不是字符串
  3. My code to be run on the I/O thread so I can see the console output
  4. 我的代码在I / O线程上运行,所以我可以看到控制台输出
  5. My code to be called every time there was a change, not once
  6. 每次有变更时都要调用我的代码,而不是一次

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.

旁注:由于使用全局变量在线程之间进行通信具有讽刺意味,因此我可以编译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

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.

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

#6


2  

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).

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

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.

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

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).

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

#7


0  

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.

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

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
}