it-swarm.com.de

Überprüfen Sie die Datei auf Änderungen und führen Sie den Befehl mit Powershell aus

Gibt es eine einfache Möglichkeit (z. B. Skript), um die Datei in Powershell zu überwachen und Befehle auszuführen, wenn sich die Datei ändert. Ich habe gegoogelt, aber keine einfache Lösung gefunden. Grundsätzlich führe ich ein Skript in Powershell aus und wenn Datei geändert wird, führt Powershell andere Befehle aus.

BEARBEITEN

Ok, ich glaube, ich habe einen Fehler gemacht. Ich brauche kein Skript, eine Bedarfsfunktion, die ich in meine $PROFILE.ps1-Datei einfügen kann. Trotzdem war ich hart und konnte immer noch nicht schreiben, also gebe ich Kopfgeld. Es muss so aussehen:

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

Es gibt ein npm-Modul, das tut was ich will, watch, aber es überwacht nur Ordner, keine Dateien, und es ist keine Powershell-xD. 

13
IGRACH

Hier ist ein Beispiel, das ich in meinen Ausschnitten gefunden habe. Hoffentlich ist es etwas umfassender.

Zuerst müssen Sie einen Dateisystem-Watcher erstellen. Anschließend abonnieren Sie ein Ereignis, das der Watcher generiert. Dieses Beispiel wartet auf "Erstellen" -Ereignisse, kann jedoch leicht geändert werden, um auf "Ändern" zu achten.

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

Ich werde versuchen, dies auf Ihre Anforderungen einzugrenzen. 

Wenn Sie dies als Teil Ihres "profile.ps1" -Skripts ausführen, sollten Sie The Power of Profiles lesen, in dem die verschiedenen verfügbaren Profilskripts erläutert werden. 

Sie sollten auch verstehen, dass das Warten auf eine Änderung in einem Ordner nicht als Funktion im Skript ausgeführt werden kann. Das Profilskript muss abgeschlossen sein, damit Ihre PowerShell-Sitzung gestartet werden kann. Sie können jedoch eine Funktion verwenden, um ein Ereignis zu registrieren. 

Was dies tut, ist das Registrieren eines Codeabschnitts, der jedes Mal ausgeführt wird, wenn ein Ereignis ausgelöst wird. Dieser Code wird im Kontext Ihres aktuellen PowerShell-Hosts (oder der aktuellen Shell) ausgeführt, während die Sitzung geöffnet bleibt. Es kann mit der Host-Sitzung interagieren, hat jedoch keine Kenntnis des ursprünglichen Skripts, das den Code registriert hat. Das Originalskript ist wahrscheinlich bereits fertig, wenn Ihr Code ausgelöst wird.

Hier ist der 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"

Ändern Sie nach dem Ausführen dieses Codes alle Dateien im Verzeichnis "C:\temp" (oder in einem anderen von Ihnen angegebenen Verzeichnis). Sie sehen ein Ereignis, das die Ausführung Ihres Codes auslöst.

Gültige FileSystemWatcher-Ereignisse, die Sie registrieren können, sind "Geändert", "Erstellt", "Gelöscht" und "Umbenannt".

25
Jan Chrbolka

Ich werde noch eine Antwort hinzufügen, da meine vorherige die Anforderungen nicht erfüllt hat.

Bedarf  

  • Schreiben Sie eine Funktion anWAITfür eine Änderung in einer bestimmten Datei
  • Wenn eine Änderung erkannt wird, führt die Funktion einen vordefinierten Befehl aus und kehrt zum Hauptskript zurück
  • Dateipfad und Befehl werden als Parameter an die Funktion übergeben

Es gibt bereits eine Antwort mit Dateihashes. Ich möchte meiner vorherigen Antwort folgen und Ihnen zeigen, wie dies mit FileSystemWatcher erreicht werden kann.

$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
6
Jan Chrbolka

Sie können den System.IO.FileSystemWatcher verwenden, um eine Datei zu überwachen.

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

Siehe auch diesen Artikel

4
bytecode77

Hier ist die Lösung, die ich mit einigen der vorherigen Antworten gefunden habe. Ich wollte speziell:

  1. Mein Code soll Code sein, keine Zeichenfolge
  2. Mein Code muss auf dem E/A-Thread ausgeführt werden, damit ich die Konsolenausgabe sehen kann
  3. Mein Code muss bei jeder Änderung aufgerufen werden, nicht nur einmal

Randbemerkung: Ich habe in den Details dessen, was ich ausführen wollte, aufgrund der Ironie der Verwendung einer globalen Variablen für die Kommunikation zwischen Threads übrig gelassen, damit ich Erlang-Code kompilieren kann.

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

Sie können Ordner/Filter/Aktion in watch übergeben, wenn Sie etwas mehr generisches wollen. Hoffentlich ist dies ein hilfreicher Ausgangspunkt für jemanden.

3
Ed'
  1. Berechnen Sie den Hash einer Liste von Dateien
  2. Speichern Sie es in einem Wörterbuch
  3. Überprüfen Sie jeden Hash auf ein Intervall
  4. Aktion ausführen, wenn Hash anders ist

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

Verwendungsbeispiel:

$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

Ich hatte ein ähnliches Problem. Ich wollte zuerst Windows-Ereignisse verwenden und registrieren, dies wäre jedoch weniger fehlertolerant als die Lösung darunter.
Meine Lösung war ein Abfrageskript (Intervalle von 3 Sekunden). Das Skript hat einen minimalen Footprint im System und bemerkt Änderungen sehr schnell. Während der Schleife kann mein Skript mehr tun (eigentlich überprüfe ich 3 verschiedene Ordner). 

Mein Abfrageskript wird über den Task-Manager gestartet. Der Zeitplan wird alle 5 Minuten mit der Markierung angehalten, wenn bereits läuft. Auf diese Weise wird es nach einem Neustart oder nach einem Absturz neu gestartet.
Die Verwendung des Task-Managers zum Abfragen alle 3 Sekunden ist für den Task-Manager zu häufig. Wenn Sie dem Scheduler eine Aufgabe hinzufügen, stellen Sie sicher, dass Sie keine Netzlaufwerke verwenden (für die zusätzliche Einstellungen erforderlich sind), und geben Sie Ihren Task an Benutzerbatch-Berechtigungen.

Ich gebe meinem Skript einen sauberen Start, indem ich es einige Minuten vor Mitternacht herunterfahre. Der Task-Manager startet das Skript jeden Morgen (die Init-Funktion meines Skripts wird etwa eine Minute um Mitternacht beendet).

2
Walter A

Hier ist eine weitere Option.

Ich musste nur meine eigenen schreiben, um Tests in einem Docker-Container zu sehen und auszuführen. Die Lösung von Jan ist viel eleganter, aber FileSystemWatcher ist derzeit in Docker-Containern defekt. Mein Ansatz ist dem von Vasili ähnlich, aber er ist viel fauler und vertraut der Schreibzeit des Dateisystems.

Hier ist die Funktion, die ich brauchte, die den Befehlsblock jedes Mal ausführt, wenn sich die Datei ändert.

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

Hier ist einer, der wartet, bis sich die Datei ändert, den Block ausführt und dann beendet wird.

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