web-dev-qa-db-fra.com

Surveillez les modifications dans le fichier et lancez la commande avec powershell

Existe-t-il un moyen simple (script) de regarder un fichier dans powershell et d’exécuter des commandes si le fichier change. J'ai cherché sur Google mais je ne trouve pas de solution simple. Fondamentalement, j'exécute un script dans PowerShell et si les modifications de fichier, PowerShell exécute d'autres commandes.

MODIFIER

Ok je pense avoir commis une erreur. Je n'ai pas besoin de script, une fonction nécessaire que je peux inclure dans mon fichier $PROFILE.ps1. Mais malgré tout, je discutais avec acharnement et je ne parvenais toujours pas à l'écrire, alors je vais donner une prime. Cela doit ressembler à ceci:

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

Il existe un module npm qui fait ce que je veux, watch, mais il ne surveille que les dossiers, pas les fichiers, et ce n'est pas PowerShell xD. 

13
IGRACH

Voici un exemple que j'ai trouvé dans mes extraits. J'espère que c'est un peu plus complet.

Tout d'abord, vous devez créer un observateur de système de fichiers, puis vous vous abonnez à un événement généré par l'observateur. Cet exemple écoute les événements «Créer», mais peut facilement être modifié pour surveiller «Changer».

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

Je vais essayer de réduire cela à vos besoins. 

Si vous l'exécutez dans le cadre de votre script "profile.ps1", vous devriez lire La puissance des profils qui explique les différents scripts de profil disponibles, etc. 

De plus, vous devez comprendre que l'attente d'une modification dans un dossier ne peut pas être exécutée en tant que fonction dans le script. Le script de profil doit être terminé pour que votre session PowerShell puisse démarrer. Vous pouvez cependant utiliser une fonction pour enregistrer un événement. 

Cela permet d’enregistrer un morceau de code à exécuter chaque fois qu’un événement est déclenché. Ce code sera exécuté dans le contexte de votre hôte (ou de votre shell) PowerShell actuel tant que la session reste ouverte. Il peut interagir avec la session hôte, mais ne connaît pas le script d'origine qui a enregistré le code. Le script original a probablement déjà fini, au moment où votre code est déclenché.

Voici le 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"

Après avoir exécuté ce code, modifiez tous les fichiers du répertoire "C:\temp" (ou tout autre répertoire de votre choix). Vous verrez un événement déclencher l'exécution de votre code.

En outre, les événements FileSystemWatcher valides que vous pouvez enregistrer sont "Modifié", "Créé", "Supprimé" et "Renommé".

25
Jan Chrbolka

Je vais ajouter une autre réponse, car mon précédent manquait les exigences.

Exigences  

  • Ecrivez une fonction dansATTENDEZpour une modification dans un fichier spécifique
  • Lorsqu'un changement est détecté, la fonction exécute une commande prédéfinie et renvoie l'exécution au script principal.
  • Le chemin du fichier et la commande sont passés à la fonction en tant que paramètres

Il existe déjà une réponse à l'aide des hachages de fichiers. Je souhaite suivre ma réponse précédente et vous montrer comment cela peut être accompli à l'aide de 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
6
Jan Chrbolka

Vous pouvez utiliser le System.IO.FileSystemWatcher pour surveiller un fichier.

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

Voir aussi cet article

4
bytecode77

Voici la solution que j'ai trouvée avec plusieurs des réponses précédentes ici. Je voulais spécifiquement:

  1. Mon code doit être un code, pas une chaîne
  2. Mon code doit être exécuté sur le thread d'E/S afin que je puisse voir la sortie de la console
  3. Mon code doit être appelé à chaque changement, pas une fois.

Note latérale: j'ai laissé dans les détails ce que je voulais exécuter en raison de l'ironie d'utiliser une variable globale pour communiquer entre les threads afin de pouvoir compiler le 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

Vous pouvez passer dans folder/filter/action en watch si vous voulez quelque chose de plus générique. J'espère que c'est un point de départ utile pour quelqu'un d'autre.

3
Ed'
  1. Calculer le hash d'une liste de fichiers
  2. Stockez-le dans un dictionnaire
  3. Vérifier chaque hachage sur un intervalle
  4. Effectuer une action lorsque le hachage est différent

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

Exemple d'utilisation:

$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

J'avais un problème similaire. Je voulais d’abord utiliser les événements Windows et les enregistrer, mais la solution proposée serait moins tolérante aux pannes.
Ma solution était un script d'interrogation (intervalles de 3 secondes). Le script a une empreinte minimale sur le système et remarque les changements très rapidement. Pendant la boucle, mon script peut faire plus de choses (en fait, je vérifie 3 dossiers différents). 

Mon script d'interrogation est lancé via le gestionnaire de tâches. L'horaire commence toutes les 5 minutes avec l'indicateur stop-when-already-déjà en cours d'exécution. De cette façon, il redémarrera après un redémarrage ou après un crash.
Utiliser le gestionnaire de tâches pour une interrogation toutes les 3 secondes est trop fréquent pour le gestionnaire de tâches . Lorsque vous ajoutez une tâche au planificateur, assurez-vous de ne pas utiliser de lecteurs réseau privilèges de lot utilisateur.

Je donne à mon script un bon départ en l'éteignant quelques minutes avant minuit. Le gestionnaire de tâches lance le script tous les matins (la fonction init de mon script quitte 1 minute environ vers minuit).

2
Walter A

Voici une autre option.

Je devais juste écrire le mien pour pouvoir regarder et exécuter des tests dans un conteneur Docker. La solution de Jan est beaucoup plus élégante, mais FileSystemWatcher est actuellement cassé dans les conteneurs Docker. Mon approche est similaire à celle de Vasili, mais beaucoup plus paresseuse, faisant confiance au temps d'écriture du système de fichiers.

Voici la fonction dont j'avais besoin, qui exécute le bloc de commandes chaque fois que le fichier est modifié.

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

En voici une qui attend que le fichier soit modifié, exécute le bloc, puis se ferme.

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