Jacques's profilejanelPhotosBlogLists Tools Help
    October 19

    Comparer deux fichiers texte avec PowerShell

    Sur le forum de PowerShell-Scripting, quelqu’un demandait récemment comment faire pour comparer les lignes de deux fichiers texte et obtenir le nombre de caractères différents. Bien sûr, l’utilisation du terme « comparer » m’a sauté aux yeux comme une évidence : compare-object !

     

    Pour des raisons trop longues à expliquer ici (je viens de découvrir que Live.com impose une limite à la taille des billets, limite que j'ai apparemment franchie avec la première version de ce billet), la solution reposant sur compare-object ne marchait pas. Vous trouverez les explications sur le forum en question. Je suis donc arrivé au script suivant :

     

    # -- compare-textfile.ps1 --

     

    param (

        $reference,

        $difference

    )

     

    $file1 = [System.IO.File]::OpenText((dir $reference))

    $file2 = [System.IO.File]::OpenText((dir $difference))

     

    $line = 1

     

    while (!$file1.EndOfStream -or !$file2.EndOfStream)

    {

        $line1 = $line2 = ""

        ($file1.EndOfStream) -or ($line1 = $file1.ReadLine()) > $nul

        ($file2.EndOfStream) -or ($line2 = $file2.ReadLine()) > $nul

     

        $length = $line1.length

        if ($line2.length -gt $length) { $length = $line2.length }

     

        $matchline = New-Object PSObject

        $chararray = @()

        $diff = 0

     

        for ($i=0;$i -lt $length;$i++) {

            if ([char]$line1[$i] -ne [char]$line2[$i]) {

                $diffchars = New-Object PSObject

                $diffchars | Add-Member -MemberType NoteProperty Position $i

                $diffchars | add-member -MemberType NoteProperty Reference $line1[$i]

                $diffchars | add-member -MemberType NoteProperty Difference $line2[$i]

                $chararray += $diffchars

                $diff++

            }

        }

     

        $matchline | Add-Member -MemberType NoteProperty Line $line

        $matchline | Add-Member -MemberType NoteProperty Different $diff

        $matchline | Add-Member -MemberType NoteProperty Equal ($length - $diff)

        $matchline | Add-Member -MemberType NoteProperty Characters $chararray

        $matchline

     

        $line++

    }

     

    $file1.Close()

    $file2.Close()

     

    # -- fin de script --

     

    Imaginons deux fichiers texte simples :

     

    PS> type ref.txt

    HELLO WORLD

    HOULA HOUP

     

    PS> type dif.txt

    HELLO WORLD!

    HOULA, HOUPS...

     

    PS> compare-textfile ref.txt dif.txt

     

         Line           Different               Equal Characters

         ----           ---------               ----- ----------

            1                   1                  11 {@{Position=11; Reference=...

            2                  10                   5 {@{Position=5; Reference= ...

     

    Le résultat s’affiche ligne par ligne. On peut voir que sur la première ligne on a un caractère de différent et onze caractères égaux (Equal). Sur la deuxième ligne, j’ai dix caractères différents et cinq égaux. Mais quelles sont ces différences ? Et qu’est-ce que c’est que ce charabia à la fin de chaque ligne ?

     

    PS> $comp = compare-textfile ref.txt dif.txt

    PS> $comp[0].characters

     

         Position    Reference     Difference

         --------    ---------     ----------

               11                           !

     

    La propriété Characters d’une ligne (ici, la première) est en fait un tableau qui contient la position de chaque différence, et pour chaque position le caractère de référence (celui présent ou pas à la position donnée dans le premier fichier passé en paramètre) ainsi que le caractère de différence (celui présent ou pas à la même position donnée dans le deuxième fichier passé en paramètre).

     

    Attention : les numéros de ligne commencent à 1, mais les positions dans chaque ligne comment à 0. J’ai fait ce choix pour suivre les conventions les plus fréquemment rencontrées dans les numérotations de ligne : ouvrez un fichier dans un éditeur de texte évolué, vous verrez que la numérotation des lignes commence toujours à 1. A l’inverse, la numérotation des caractères dans une chaîne PowerShell commence toujours à 0.

     

    Mais revenons à notre exemple. Regardons maintenant la deuxième ligne :

     

    PS> $comp[1].characters

     

          Position    Reference     Difference

          --------    ---------     ----------

                 5                           ,

                 6            H

                 7            O              H

                 8            U              O

                 9            P              U

                10                           P

                11                           S

                12                           .

                13                           .

                14                           .

     

    L’insertion de la virgule en cinquième position a déplacé la fin du texte ce qui provoque une cascade de différences jusqu’aux points de suspension.

     

    Très bien, mais le problème initial consistait à obtenir le nombre total de différences entre les deux fichiers. Or, ici je n’affiche les différences que ligne par ligne. Bigre, comment faire ? Voyons voir :

     

    PS> compare-textfile ref.txt dif.txt | measure-object different –sum

     

    Janel

    October 11

    Faites votre BA du jour

    Vous n’avez pas encore fait votre bonne action aujourd’hui ? Voici l’occasion de vous racheter et de finir la journée en beauté. Votez pour la photo que j’ai publiée sur http://www.picture.com, vous me ferez peut-être gagner un iPod Shuffle :
     
     
    Et si je gagne l’iPod, je serai plus heureux, donc en meilleure forme, donc plus créatif sur mon blog. Donc un bénéfice également pour vous. Ca c’est du gagnant-gagnant ou je ne m’y connais pas !
     
    Merci d’avance,
    Janel
    October 09

    Un éditeur de scripts PowerShell gratuit et super efficace

    Voilà une bonne nouvelle qui fait plaisir ! La dernière version de PowerGUI, console d’administration en mode graphique basée sur PowerShell, inclut maintenant un éditeur de scripts à la fois simple et efficace, le PowerGUI Script Editor :
     
     
    Parmi les fonctionnalités les plus pratiques que j’ai pu relever après quelques minutes d’utilisation :
     
    • Mise en couleurs des éléments syntactiques du script
    • Fonctionnalité « Intellisense » proposant les noms de commandelettes et de paramètres au fur et à mesure de la frappe
    • Vue en arborescence du script permettant de suivre et de regrouper les blocs d’instructions très simplement
    • Numérotation des lignes pour faciliter le débogage en cas de besoin
    • Partage de la fenêtre en deux volets pour pouvoir, par exemple, comparer deux parties d’un même script
    • Possibilité d’exécuter le script directement dans une nouvelle fenêtre PowerShell
    • Etc.
     
    Pour compléter ce tableau, trois excellentes nouvelles :
     
    • Le PowerGUI Script Editor est gratuit
    • L’installation comprend également la console graphique PowerGUI, outil qui peut se révéler pratique pour exécuter rapidement certaines tâches d’audit sur votre poste ou sur d’autres postes du réseau – la console offre même une vision du code PowerShell exécuté
    • L’installation associe automatiquement les fichiers de type .ps1 à l’éditeur : un double-clic sur vos scripts les ouvrira directement dans votre nouvel outil préféré

     

    Alors, qu’attendez-vous ? J
     
    Janel

    October 03

    Je veux ajouter un bouton Imprimer à mon Windows Form créé à partir de PowerShell

    Il y a quelque temps sur le forum de PowerShell-Scripting un utilisateur a demandé comment il pouvait imprimer un Windows Form qu’il avait créé à partir de PowerShell. Le premier lot de réponses qu’il a reçues tournaient autour de la possibilité de faire une capture de la fenêtre dans le presse-papiers de Windows, et d’envoyer le contenu du presse-papiers vers l’imprimante. La solution est ingénieuse, mais elle repose sur plusieurs points difficiles à résoudre. En premier lieu, il faut pouvoir gérer le presse-papiers, ensuite il faut pouvoir interagir avec l’imprimante et notamment savoir lui transmettre l’image capturée dans un format qu’elle comprenne.
     
    Dans une telle situation, on peut aussi ajouter au formulaire un bouton "Imprimer" qui appellera les méthodes du Framework .NET qui vont bien. Il existe un exemple sur MSDN pour imprimer un Windows Form, mais le code fourni n’est évidemment pas en PowerShell mais en VB.Net et en C#. J'ai donc dû gratter un peu pour pouvoir le traduire en PowerShell. Voici ce que ça donne - le code qui suit suppose que le Windows Form s'appelle $form :
     
     
    # [void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    [void] [Reflection.Assembly]::LoadWithPartialName("System.Drawing")
     
    $printClick = {
      $myGraphics $form.CreateGraphics()
      $size $form.Size
      $script:memimage = new-object System.Drawing.Bitmap $form.Width$form.Height$myGraphics
      $memgraphics = [System.Drawing.Graphics]::FromImage($script:memimage)
      $memgraphics.CopyFromScreen($form.Location.X$form.Location.Y00$size)
      $printdoc.Print()
    }
     
    $PrintPage = {
     $_.Graphics.DrawImage($script:memimage00)
    }
     
    $printDoc = New-Object System.Drawing.Printing.PrintDocument
    $printDoc.Add_PrintPage($PrintPage)
     
    $printButton = New-Object System.Windows.Forms.Button
    $printButton.Text "Imprimer"
    $printButton.Add_Click($printClick)
     
    # $form = New-Object System.Windows.Forms.Form
    $form.Controls.Add($printButton)
    # $form.ShowDialog()
     
     
    Les lignes commentées sont simplement là pour rappeler ce qui doit être fait par ailleurs pour créer et afficher le Windows Form. Le script existant doit déjà contenir des lignes équivalentes.
     
    Au-delà du besoin spécifique de lancer une impression, cet exemple vous montrera également comment associer une action à un évènement qui survient sur un contrôle. Ici, nous avons l’action d’impression initiée par un clic sur le bouton $printButton et dans cette action d’impression la « sous-action » d’impression d’une page initiée par l’appel à la méthode Print() de l’objet $printDoc, respectivement associées aux blocs de commandes $printClick et $printPage.
     
    Il ne vous reste plus qu’à l’adapter à vos propres besoins et à laisser libre cours à votre fantaisie !
     
    Janel
    October 01

    Infos photos (et plus) avec PowerShell

    En parcourant la collection de scripts PowerShell qui commence à envahir mon disque dur, j’ai retrouvé ce script, get-filemetadata.ps1, qui récupère les méta-données d’un fichier. Ces méta-données sont des informations sur le contenu d’un fichier, généralement renseignées par l’application qui l’a créé.
     
    Ainsi, les méta-données d’un document Word incluent le titre (information à distinguer du nom du fichier), le nombre de mots, le nombre de pages ou encore le temps total passé à éditer le document (pas sûr que cette information soit totalement pertinente). Les méta-données d’une photo numérique incluent la résolution de l’image, le temps de pose, l’ouverture du diaphragme ou encore le matériel utilisé.
     
    Je ne suis pas l’auteur de ce script. Je me rappelle avoir vu ce sujet traité sur plusieurs blogs (celui de MOW notamment, ainsi que le blog d’un membre de l’équipe PowerShell) mais le script que j’ai n’étant pas signé il m’est impossible d’identifier à coup sûr son origine. Qui plus est, il est probable que je l’aie personnalisé après l’avoir téléchargé. Bref, la version que voici est – sauf preuve du contraire – le premier « script traditionnel » publié ici, comme on a des « chants traditionnels » dans notre patrimoine :
     
     
    # get-filemetadata.ps1
    #
    # Auteur: traditionnel, adapté par janel
    #
    begin {
     
        function emitMetaInfoObject($path) {
            [string]$path = (resolve-path $path).path
            [string]$dir  = split-path $path
            [string]$file = split-path $path -leaf
            $shellApp = new-object -com shell.application
            $myFolder = $shellApp.Namespace($dir)
            $fileobj = $myFolder.Items().Item($file)
     
            $metaInfoObj = new-object psobject
            $metaInfoObj.psobject.typenames[0] = "Custom.IO.File.Metadata"
     
            for ( $i=0 ; $i -lt 300; $i++) {
                $n = $myFolder.GetDetailsOf($null, $i)
     
                if ($n) {
                  $v = $myFolder.GetDetailsOf($fileobj,$i)
                  if ($v) {
                      $metaInfoObj | add-member noteproperty $n $v
                  }
                }
            }
            $metaInfoObj
        }
     
    }
     
    process {
        if ($_) {
            emitMetaInfoObject $_.Fullname
        }
     
    }
     
    end {
        if ($args) {
            $paths = @()
            foreach ($path in $args) {
                if (!(test-path $path)) {
                    write-error "$path is not a valid path"
                }
                $paths += resolve-path $path
            }
            foreach ($path in $paths) {
                emitMetaInfoObject $path
            }
        }
    }
    # end of script
     
     
    Voyons un peu ce que ça donne sur un album de Lloyd Cole :
     
    PS> dir | get-filemetadata | format-table artists,year,album,title,length -auto
     
    Artists                       Year Album       Title                    Length
    -------                       ---- -----       -----                    ------
    Lloyd Cole and the Commotions 1985 Easy Pieces Rich                     00:04:22
    Lloyd Cole and the Commotions 1985 Easy Pieces Why I Love Country Music 00:03:00
    Lloyd Cole and the Commotions 1985 Easy Pieces Pretty Gone              00:03:32
    Lloyd Cole and the Commotions 1985 Easy Pieces Grace                    00:04:05
    Lloyd Cole and the Commotions 1985 Easy Pieces Cut Me Down              00:04:29
    ...
     
    Sur des photos, ça donne les résultats suivants :
     
    PS> dir *.jpg | get-filemetadata | ft name, "date created", "camera model", "focal length", "f-stop", exposure* -a
     
    Name           Date created     Camera model Focal length F-stop Exposure bias Exposure time
    ----           ------------     ------------ ------------ ------ ------------- -------------
    001.JPG        13/01/2007 17:21 NIKON D50    ‎55 mm       f/5,6  ‎0 step       ‎1/60 sec.
    Aéroport 2.jpg 10/03/2007 09:37 NIKON D50    ‎40 mm       f/4,5  ‎0 step       ‎1/125 sec.
    Aéroport.jpg   10/03/2007 09:34 NIKON D50    ‎40 mm       f/4,5  ‎0 step       ‎1/125 sec.
    BW 1.jpg       28/02/2007 10:53 NIKON D50    ‎35 mm       f/2    ‎-0,3 step    ‎1/250 sec.
    BW 2.jpg       28/02/2007 11:00 NIKON D50    ‎35 mm       f/2    ‎-1 step      ‎1/250 sec.
    ...
     
    Maintenant, si vous voulez vraiment exploiter les données ci-dessus à des fins statistiques par exemple, comme calculer la durée totale de tous les morceaux de musique de votre compositeur préféré, ou encore estimer la longueur de focale moyenne de vos photos, vous devrez appliquer un petit traitement aux données retournées par le script. Rien de bien méchant, mais en gros il faudra enlever le texte superflu (comme les ‘mm’ de la longueur focale) ou encore convertir le format utilisé pour l’affichage en une valeur compréhensible par PowerShell (comme ’00:04:22’ pour la durée d’un morceau de musique).
     
    De plus, ce script est conçu pour traiter les méta-données de n’importe quel type de fichier. L’avantage est évident, mais si vous manipulez des photos couramment, vous devez savoir que les méta-données qu’une photo numérique peut stocker sont considérablement plus nombreuses. Ces données, qu’on appelle également données EXIF, peuvent être visualisées dans la plupart des logiciels de traitement d’images, ou encore en ligne de commande avec un outil gratuit fabuleux : ExifTool.
     
    ExifTool permet de très nombreuses choses qu’il serait difficile d’implémenter dans un script PowerShell sans avoir recours à une bibliothèque de fonctions externe. Je n’ai pas encore trouvé la bibliothèque en question, ni trouvé le courage ou le temps de m’y coller, alors voici une méthode à deux balles pour – au moins – exploiter les informations retournées par ExifTool depuis un script PowerShell :
     
     
    filter get-exifdata {
         $img = new-object PSObject
         exiftool $_.FullName | foreach {
              $n,$v = $_.split(":",2) | foreach {$_.trim()}
              $img | add-member NoteProperty $n $v -ea SilentlyContinue
         }
         $img
    }
     
     
    Pour que la fonction ci-dessus marche, il faut que vous ayez téléchargé et copié exiftool.exe dans un répertoire déclaré par votre PATH.
     
    Vous pouvez ensuite l’utiliser ainsi :
     
    PS> dir *.jpg | get-exifdata
     
    Tel quel, l’affichage risque d’être assez rébarbatif et finalement peu différent de ce que vous donnerait ExifTool directement. Mais comme pour get-filemetadata, on pourra utiliser les commandelettes standard pour filtrer, trier, mesurer, grouper et formater les données émises. Déjà, essayons cela :
     
    PS> dir *.nef | get-exifdata | ft "focal length",aperture,"focus distance",depth* -a
     
    Focal Length Aperture Focus Distance Depth of Field
    ------------ -------- -------------- --------------
    200.0mm      5.6      33.50 m        6.31 m (30.64 - 36.95)
    70.0mm       11.0     21.13 m        389.65 m (10.85 - 400.50)
    18.0mm       11.0     1.68 m         inf (0.79 m - inf)
    18.0mm       11.0     1.68 m         inf (0.79 m - inf)
    18.0mm       11.0     1.68 m         inf (0.79 m - inf)
    ...
     
    On constate qu’ExifTool obtient des informations sur des fichiers RAW (*.NEF est l’extension de fichier pour le format RAW de Nikon) que get-filemetadata ne sait pas obtenir – ici par exemple, « Focus Distance » qui indique la distance entre l’appareil photo et le point sur lequel la mise au point a été faite, et « Depth of Field » qui indique la profondeur de champ avec entre parenthèses le point le plus proche et celui le plus distant entre lesquels la netteté est considérée comme acceptable.
     
    En faisant une petite manipulation sur les valeurs de la propriété « Focus Distance », on pourra récupérer un nombre réutilisable pour nos propres calculs. Voici comme dernier exemple une ligne de commande qui va mesurer la distance de mise au point moyenne sur toutes les photos (au format RAW) dans le répertoire en cours :
     
    PS> dir *.nef | get-exifdata | select @{n="Distance";e={$_."Focus Distance".Split()[0]}} | measure-object distance –average –min -max
     
    Count    : 186
    Average  : 10,8857526881721
    Sum      :
    Maximum  : 33,5
    Minimum  : 0,5
    Property : Distance
     
    Voilà, maintenant je sais que sur les 186 photos de cette série, en moyenne j’ai mis au point à 10,88m de distance. Essentiel, non ?
     
    A vous de jouer!
     
    Janel