La boîte à Tutoriels de Christopher PECAUD

Créer un « watchdog » sur un dossier avec Powershell

Laisser un commentaire

SOMMAIRE


I Introduction
II Prérequis
III Ecriture du script
IV Conclusion

I Introduction

Dans ce tutoriel nous allons voir, à partir d’un script Powershell, comment créer un dossier watchdog, vous permettant de scruter celui-ci et d’effectuer une action sur un fichier nouvellement ajouté à l’intérieur de celui-ci. Pour l’exemple nous allons voir comment convertir un fichier vidéo en utilisant Ffmpeg

II Prérequis

Avant de commencer à écrire le code, il faut installer Ffmpeg sur votre ordinateur. Les binaires sont à télécharger à cette adresse : https://github.com/FFmpeg/FFmpeg, il suffit juste de télécharger le fichier compressé :

description image

Cliquez sur « Download ZIP ». Une fois téléchargé, décompressez le fichier le dossier inclus dans l’arborescence de votre disque dur où bon vous semble. Afin que l’exécutable ffmpeg soit reconnu directement il faut renseigner le chemin complet de l’exécutable ffmpeg.exe dans la variable d’environnement PATH :

description image

III Ecriture du script

Nous allons créer une fonctionnalité de dossier « chien de garde » dans C:\Temp. Nous déclencherons une action lorsqu’un nouveau fichier sera ajouté dans celui-ci. Cette action transformera le fichier vidéo wmv en fichier mp4. Nous utiliserons pour ce type de conversion la librairie de fonctions ffmpeg.

Au niveau du script lui-même pour implémenter un « watcher » il faut utiliser l’objet FileSystemWatcher. Plusieurs propriétés sont à définir en appelant le constructeur de l’objet :

  • Le chemin d’accès du dossier watchog (propriété Path) ;
  • Le filtre sur le type de fichier que l’on souhaite traiter (propriété Filter) ;
  • S’il faut inclure les sous-répertoires (propriété IncludeSubDirectories) ;
  • Et les attributs du fichier à vérifier (propriété NotifyFilter).

Nous devons gérer le cas où le watcher cesse de fonctionner, une méthode simple est d’utiliser une structure de gestion d’exception de type try … except :

try 

{

//création du watcher et définition des types d’événements à prendre en charge, ainsi que les actions associées

}
finally 
{

// Gestion du cas où le watcher cesse de fonctionner
}

Nous allons donc dans un premier temps voir le contenu de la structure « try ».

Voici un exemple d’appel de cet objet :

$watcher = New-Object -TypeName IO.FileSystemWatcher -ArgumentList $folder, $FileFilter -Property @{
        Path = $folder
        Filter = $FileFilter
        IncludeSubdirectories = $includeSubFolders
        NotifyFilter = $AttributeFilter
    }

Avec les variables suivantes :

$folder = 'C:\Temp\'
$FileFilter = '*'
$includeSubFolders = $false
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite

Nous allons créer un type de dossier watchdog dit asynchrone. Celui-ci ne bloque pas l’exécution du programme et fait appel à des événements.

L’étape suivante consiste à paramétrer les actions à accomplir en fonction de ces 4 types de changement :

  • Changed : Quand un changement sur un fichier ou un dossier a été effectué ;
  • Created : Quand un fichier ou un dossier vient d’être créé ;
  • Deleted : Quand un fichier ou un dossier a été supprimé ;
  • Renamed : Quand un changement de nom a été effectué sur un fichier ou un dossier existant dans le dossier.
  • Le code de l’action va être inclus dans une structure de ce type :
    
    $action = {
    # Ajouter le code qui permet de définir l’action à accomplir….
    
    }
    
    

    A l’intérieur on va commencer par définir la variable $details qui nous permet d’accéder aux événements :

    $details = $event.SourceEventArgs
    
    

    Puis on récupère le nom et le chemin d’accès complet du nouveau fichier créé dans l’arborescence :

    $Name = $details.Name
    $FullPath = $details.FullPath
    

    On récupère les types de changement possible dans la variable $changeType :

    $ChangeType = $details.ChangeType
    
    

    Et l’heure de la dernière modification dans l’arborescence, quelque soit le type de changement :

     $Timestamp = $event.TimeGenerated
    
    

    Vient ensuite le code qui sera exécuté en fonction du type de changement opéré sur un fichier. Voici la structure :

     switch ($ChangeType)
            {
    
              'Changed'  { "CHANGED" }
              'Created'  { "CREATED"
                        Invoke-Search_and_replace $FullPath, $Name
                        }
    
              'Deleted' { "DELETED"}
              'Renamed' { "RENAMED"}
    
              default   { Write-Host $_ -ForegroundColor Red -BackgroundColor White }
    
            }
    
    

    Comme vous pouvez le constater il n’est nécessaire de définir le code que pour les types de changement qui nous intéressent. Dans notre cas à la création du fichier dans le dossier nous appelons la fonction Invoke-Search_and_replace avec en arguments les variables $FullPath et $Name. On peut effectuer une action par défaut au cas où un type de changement ne correspondrait pas à ceux énoncés plus haut. Nous verrons plus loin le code de la fonction nous permettant de convertir le fichier créé.

    On crée ensuite les gestionnaires d’événements liés aux 4 types de changements sur le dossier « watchdog » comme ceci :

     $handlers = . {
            Register-ObjectEvent -InputObject $watcher -EventName Changed  -Action $action 
            Register-ObjectEvent -InputObject $watcher -EventName Created  -Action $action 
            Register-ObjectEvent -InputObject $watcher -EventName Deleted  -Action $action
            Register-ObjectEvent -InputObject $watcher -EventName Renamed  -Action $action
          }
    
    
    
    

    On lance ensuite le monitoring sur le watchfolder avec cette ligne de code :

    $watcher.EnableRaisingEvents = $true
    
    

    Le « watcher » est maintenant lancé et attend qu’un nouvel événement se produise dans le dossier.

    Il faut ensuite créer une boucle infinie avec ce type de structure :

    do
    
          {
            #code permettant de scruter les événements
             
        } while ($true)
    
    

    On va donc attendre qu’un événement se produise dans le dossier en question, pour ce faire on utilise la fonction « Wait-Event » comme ceci :

    Wait-Event -Timeout 1
    
    

    Le script scrute ainsi ce qu’il se passe dans le dossier toutes les secondes.

    Maintenant que le contenu de la structure try est terminé voyons maintenant celui de la structure finally.

    Ce code interviendra le plus souvent quand vous allez interrompre la boucle infinie par un CTRL + C par exemple.

    On commence alors par stopper le monitoring sur le watcher en assignant la valeur $false à la propriété EnableRaisingEvents :

    $watcher.EnableRaisingEvents = $true
    
    

    On supprime les gestionnaires d’événements associés :

    $handlers | ForEach-Object {
        Unregister-Event -SourceIdentifier $_.Name
    }
    
    

    On supprime de même les jobs associés :

    $handlers | Remove-Job
    
    

    On peut alors supprimer le watcher :

    $watcher.Dispose()
    
    

    L’implémentation de notre watcher est terminée, voyons maintenant la fonction qui permet de convertir notre fichier nouvellement créé dans le dossier.

    Cette fonction prend en paramètre le chemin d’accès complet au fichier.

    function convert_video_file
    {
        param (
    
            [Parameter(Mandatory)]
            $fullpath
        )
     
        # Code de la fonction	
    
    }
    
    

    Pour rappel, dans cet exemple, nous déposons un fichier .mp4 dans le dossier souhaité, ici D:\Temp\Conversion et le fichier converti sera stocké dans le sous-dossier « converted ». Celui-ci aura la même nom et seule l’extension sera modifiée. Nous devons donc récupérer le nom du fichier sans son extension. Nous utilisons donc la fonction IndexOf pour récupérer l’index qui correspond au point dans la chaîne de caractères :

    $index_point =  $fullpath[1].IndexOf(".")
    
    

    On appelle ensuite la fonction SubString pour récupérer le nom du fichier sans l’extension :

    $name_without_ext = $fullpath[1].SubString(0, $index_point)
    
    

    A noter que la fonction prend en paramètre l’index de début de chaîne et ensuite l’index de fin de chaîne, c’est-à-dire celui correspondant au point de la chaîne initiale.

    On crée alors une variable avec le nouveau chemin d’accès complet au nouveau fichier qui sera créé lors de la conversion.

    $newfullpath = $folder + "\converted\" + $name_without_ext + ".wmv" 
    
    

    On lance ensuite la conversion du fichier vidéo en .mp4 vers .wmv en utilisant ffmpeg :

    ffmpeg -y -i $fullpath $newfullpath -filter:v fps=25
    
    

    Ceci est un exemple, vous pouvez effectuer quelques ajustements.

    Ensuite une fois le fichier converti on supprime le fichier que l’on a déposé :

    Remove-Item -Path $fullpath[0] -Force
    
    

    Voici le code complet du script :

    $folder = 'D:\temp\Conversion'
    $FileFilter = '*.mp4'
    $includeSubFolders = $false
    $AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite
    
    
    <#
    .SYNOPSIS
    Fonction de conversion du fichier vidéo
    
    .DESCRIPTION
    Cette fonction va permettre de convertir une vidéo au format mp4 
    vers le format wmv.
    Elle utilise ffmpeg pour la conversion du fichier vidéo.
    
    .PARAMETER fullpath
    => chemin d'accès complet du fichier vidéo 
    
    .EXAMPLE
    convert_video_file $fullpath
    
    .NOTES
    None
    
    #>
    
    function convert_video_file
    {
        param (
            [Parameter(Mandatory)]
            $fullpath
        )
         
       
        # On récupère l'index du point dans la chaîne de caractère $fullpath
        $index_point =  $fullpath[1].IndexOf(".")
     
        # On récupère le nom du fichier sans l'extension
        $name_without_ext = $fullpath[1].SubString(0, $index_point)
    
        # Définition du chemin d'accès complet du nouveau fichier converti
        $newfullpath = $folder + "\converted\" + $name_without_ext + ".wmv"
     
        # Appel de ffmpeg pour exécuter la conversion
        ffmpeg -y -i $fullpath $newfullpath -filter:v fps=25
        
        # Suppression du fichier déposé
        Remove-Item -Path $fullpath[0] -Force
      
    }
    
    try 
    {
        Write-Warning "FileSystemWatcher est en train de scruté $folder"
    
        $watcher = New-Object -TypeName IO.FileSystemWatcher -ArgumentList $folder, $FileFilter -Property @{
            Path = $folder
            Filter = $FileFilter
            IncludeSubdirectories = $includeSubFolders
            NotifyFilter = $AttributeFilter
        }
    
        $action = {
       
          
            # modification des informations de type liées au fichier déposé
            $details = $event.SourceEventArgs
            $FullPath = $details.FullPath
    
      
            # On définit le type de changement sur le fichier
            $ChangeType = $details.ChangeType
    
          
            # On détermine l'heure du changement sur le fichier
            $Timestamp = $event.TimeGenerated
                        
    
            # On écrit un ùmessage dans la console pour prévenir d'un changement:
            $text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp
    
            Write-Host ""
            Write-Host $text -ForegroundColor DarkYellow
           
            # On défnit les actions à effectuer en fonction de la modification apportée au fichier
            switch ($ChangeType)
            {
              'Changed'  { "CHANGED" }
              'Created'  { "CREATED"
           
                        convert_video_file $FullPath
                        }
    
              'Deleted' { "DELETED"}
              'Renamed' { "RENAMED"}
             
              # Au cas où le type de modification ne serait pas pris en compte précédemment
              default   { Write-Host $_ -ForegroundColor Red -BackgroundColor White }
    
            }
    
        }
    
        # On définit les gestionnnaires d'événement
        $handlers = . {
    
            Register-ObjectEvent -InputObject $watcher -EventName Changed  -Action $action 
            Register-ObjectEvent -InputObject $watcher -EventName Created  -Action $action 
            Register-ObjectEvent -InputObject $watcher -EventName Deleted  -Action $action
            Register-ObjectEvent -InputObject $watcher -EventName Renamed  -Action $action
    
          }
    
        
    
          # La scrutation s'exécute à partir de maintenant
          $watcher.EnableRaisingEvents = $true
      
          Write-Host "Watching for changes to $folder"
       
          # Afin que l'exécution du watcher ne bloque pas le script
          # nous efeectuons une pause toutes les secondes pour que le script puisse
          # prendre en compte les différents événements. On utilise alors une boucle 
          # infinie.
         
          do
          {
            # Wait-Event effectue la pause toutes les secondes
            Wait-Event -Timeout 1
       
            # On affiche un point pour montrer que le script est en train de tourner et attend 
            # qu'un événement intervienne
            Write-Host "." -NoNewline
              
        } while ($true)
      
    }
    
    # Dans le cas où on interromp la boucle infinie par exemple avec un CTRL + C
    finally 
    {
      # On stoppe le monitoring
      $watcher.EnableRaisingEvents = $false
     
      # On supprime les gestionnaires d'événement
      $handlers | ForEach-Object {
        Unregister-Event -SourceIdentifier $_.Name
      }
     
      # On supprime les jobs associés:
      $handlers | Remove-Job
    
      # Et on supprime le watcher:
      $watcher.Dispose()
     
      Write-Warning "Event Handler disabled, monitoring ends."
    
    }
    
    

    IV Conclusion

    Ce document vous a montré comment paramétrer un monitoring sur un dossier bien défini et agir en fonction des modifications effectuées sur celui-ci. Il nous a permis de voir comment convertir un fichier vidéo en utilisant ffmpeg une fois celui-ci déposé dans le dossier en question.

    blog comments powered by Disqus
RapidWeaver Icon

Réalisé avec Rapidweaver