Jacques's profilejanelPhotosBlogLists Tools Help
    April 13

    Gadget PowerShell pour Windows Vista

    Si vous utilisez Windows PowerShell ET Windows Vista, vous devez absolument installer ce Gadget :
     
     
    Attention, il y a une petite astuce à connaître pour arriver à installer le Gadget correctement. Lorsque vous êtes sur la page principale, cliquez sur le lien intitulé PowerShellGadget.Gadget, choisissez « Enregistrer » (ou « Save » si comme moi vous avez un OS anglais), et lorsque la boîte de dialogue vous demande de préciser où vous voulez enregistrer, profitez-en pour modifier le nom et le type du fichier :
     
    • Remplacez .zip par .gadget à la fin du nom de fichier
    • Choisissez « Tous types » (« All types ») comme type de fichier
    Enregistrez où bon vous semble. Pour finir, naviguez jusqu’à ce dossier, double-cliquez sur le fichier et confirmez que vous souhaitez installer le Gadget. Et voilà !
     
    Je vous laisse découvrir le reste. C’est du pur bonheur J.
     
    Bon vent,
    Janel
    April 12

    Modifiez la taille de vos images avec PowerShell

    Je passe beaucoup de mon temps libre à faire de la photo. Il m’arrive d’en publier sur des sites comme www.parlons-photo.com, ou sur les forums Usenet, ou encore sur mon blog personnel. Et à chaque fois je passe un temps significatif voire considérable à redimensionner mes images selon les contraintes du site ou du forum. Par exemple, www.parlons-photo.com impose un format où la plus grande dimension ne peut pas dépasser 700 pixels et où la taille du fichier doit être inférieure à 100 Ko.
     
    Après un nombre suffisant de manipulations laborieuses avec les différents logiciels de traitement d’image en ma possession, j’ai décidé d’étudier ce que Windows PowerShell pouvait faire pour moi. Et ô joie, PowerShell pouvait faire beaucoup, pour peu que je mette un peu d’énergie à écrire le bon script.
     
    A vrai dire, je ne partais pas d’une page blanche. L’année dernière j’avais écrit un script out-album.ps1 générant un album photo en HTML très rudimentaire. A partir d’une liste d’images, le script générait des vues réduites et enregistrait dans une page HTML un lien hypertexte vers ces vues réduites. Le script acceptait plusieurs types de réduction. Pour plus d’informations sur ce script, voyez mon billet de l’époque.
     
    J’ai donc repris ce script, en supprimant la partie création d’une page HTML et en étoffant la gestion du redimensionnement. Aux critères existants de largeur (Width), hauteur (Height) et ratio (Ratio), j’ai ajouté les options suivantes :
     
    Dim
    Permet de spécifier une taille à appliquer à la plus grande dimension. Par exemple, si vous voulez qu’une série de photos soient redimensionnées de manière à ce que la plus grande dimension soit toujours de 700 pixels, qu’il s’agisse de la largeur ou de la hauteur, spécifiez -dim 700 et le tour est joué. L’autre dimension est alors calculée en proportion.
     
    MaxSize
    Permet de spécifier une taille maximale pour le fichier résultant du redimensionnement. Par exemple si vous devez publier des photos dont la taille de fichier doit être inférieure à 100 Ko, spécifiez -maxsize 99KB et le tour est joué. Notez au passage que toutes les formes de notation possibles dans PowerShell sont acceptées y compris les suffixes KB, MB et GB.
    La taille souhaitée est obtenue en jouant sur la qualité (c’est-à-dire le taux de compression) de l’image. On part d’une qualité 100 (optimale) et on réduit jusqu’à obtenir la bonne taille de fichier. Si vous épluchez le script, vous verrez que la taille est calculée en sauvegardant temporairement dans un flux en mémoire (System.IO.MemoryStream) pour éviter de devoir écrire un fichier sur disque et en lire la taille à chaque itération de la boucle.
     
    Quality
    Permet de spécifier une qualité d’image, c’est-à-dire un taux de compression. Les valeurs possibles vont de 0 à 100, 0 pour une qualité minimale et 100 pour une qualité optimale.
     
    Copy
    Ce paramètre permet de préciser qu’on veut que l’image générée n’écrase pas l’image originale. La copie générée reprend le nom de l’image originale en y ajoutant des informations sur les modifications apportées.
     
    En l’état actuel, ce script a quelques limitations qu’il faut connaître :
     
    • Toutes les images générées sont au format JPEG. La contrainte n’existe en théorie que si l’on utilise une des options MaxSize ou Quality, mais la façon dont j’ai écrit mon script (un peu vite J) fait qu’il génère une image JPEG quelles que soient les options utilisées. Je vous conseille donc de ne pas l’utiliser sur des images utilisant un autre format, ou alors d’utiliser le paramètre –copy et de procéder à un premier test avant d’appliquer le script « en production ».
    • Les vérifications en cours de script sont réduites au strict minimum. Notamment, les valeurs maximales et minimales autorisées par les différents paramètres ne sont pas vérifiées. Il est donc conseillé de toujours faire un premier test avec les valeurs envisagées avant d’appliquer le script « en production ».
    • D’une manière générale, je ne saurais être tenu pour responsable du résultat, positif ou négatif, obtenu par l’usage de script. Je vous encourage à travailler sur des copies de vos images plutôt que sur les versions originales.
     
    Mais assez blablaté, voici le script :
     
    # set-imagesize.ps1
    #
    # Modifie la taille des images fournies en entrée
    #
    # Usage: ... | set-imagesize [-dim d | -ratio r | -width x -height y] [-size s | -quality q] [-copy]
    #
    # -dim indique la taille à appliquer à la plus grande dimension
    # -ratio indique le ratio (1/r) à appliquer aux deux dimensions
    # -width indique la largeur en pixels à appliquer
    # -height indique la hauteur en pixels à appliquer
    # -maxsize indique la taille maximum du fichier modifié
    # -quality indique le taux de compression JPEG à utiliser
    # -copy fait créer une copie au lieu de remplacer l'image originale
    #
    # Les images manipulées proviennent du pipeline. Le script agit comme un filtre et
    # traite une à une les images transmises.
    #
    # La copie porte le nom de l'image originale suivi des informations sur
    # le redimensionnement effectué. Par exemple, si un ratio de 1/10 est
    # appliqué à portrait.jpg, la copie s'appellera portrait_r10.jpg.
    #
    # Si dim est spécifiée, un ratio correspondant est calculé et appliqué.
    # Si ratio est spécifié, width et height sont ignorées.
    # Si une seule des deux dimensions est spécifiée, l'autre est calculée en proportion.
    #
    # Si maxsize est spécifiée, le taux de compression optimal est calculé pour l'approcher.
    # Dans ce cas, si un taux de compression est spécifié (quality), il est ignoré.
    #
    # Les images produites sont automatiquement converties au format JPEG.
    #
    # Exemple:
    #
    # dir *.jpg | set-imagesize -copy -dim 700 -max 98KB
    #
     
    param (
      $dim = 0,
      $ratio = 0,
      $width = 0,
      $height = 0,
      $maxsize = 0,
      [int64]$quality = 100,
      [switch]$copy
    )
     
    [reflection.assembly]::LoadWithPartialName("system.drawing")>$null
     
    if (!$ratio -and !$width -and !$height -and !$dim) {
      $ratio = 1
    }
     
    $input | foreach {
     
      $oldname = $_.name
      $oldbasename = [IO.Path]::GetFileNameWithoutExtension($_)
      $oldimage = new-object system.drawing.bitmap($_.fullname)
      $newbasename = "$($oldbasename)_"
     
      if ($dim) {
        if ($oldimage.width -ge $oldimage.height) {
          $ratio = $oldimage.width/$dim
        }
        else {
          $ratio = $oldimage.height/$dim
        }
      }
     
      if ($ratio) {
        $width = $oldimage.width/$ratio
        $height = $oldimage.height/$ratio
        $newbasename += "R$([math]::round($ratio,1) -replace ',','.')"
      }
      else {
        if ($width -or $height) {
          if (!$width) {
            $width = $oldimage.width/($oldimage.height/$height)
          }
          else {
            $newbasename += "L$([math]::round($width))"
          }
     
          if (!$height) {
            $height = $oldimage.height/($oldimage.width/$width)
          }
          else {
            $newbasename += "H$([math]::round($height))"
          }
        }
      }
     
      $newimage = $oldimage.GetThumbnailImage($width, $height, $null, 0)
      $oldimage.Dispose()
     
      $newname = $oldname
      $copy -and ($newname = $newbasename + $_.extension)>$null
      $newpath = join-path (pwd).path $newname
     
      $info = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()
      $params = new System.Drawing.Imaging.EncoderParameters(1)
      $params.Param[0] = new System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, $quality)
     
      if ($maxsize) {
        do {
          $stream = new System.IO.MemoryStream
          $newimage.Save($stream, $info[1], $params)
          if ($stream.length -gt $maxsize) {
            $quality--
            $params.Param[0] = new System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, $quality)
          }
        } while ($stream.length -gt $maxsize)
      }
     
      $newimage.Save($newpath, $info[1], $params)
      $newimage.Dispose()
    }
     
    J’espère que ce script pourra vous être utile comme il l’est pour moi. Au passage, vous pourrez peut-être y trouver quelques astuces d’écriture de script mais ce serait tout à fait fortuit car ce n’est pas du tout l’objet de cet exercice.
     
    Bon vent,
    Janel
    April 05

    Création d'un index avec PowerShell

    En répondant à un problème posé sur le forum dédié à PowerShell, je me suis retrouvé avec une solution très simple et très rapide pour constituer un index à partir d’un texte. Le terme d’index n’est peut-être pas totalement approprié d’ailleurs. Ce que fait ma petite formule que je vais détailler dans un instant, c’est compter le nombre d’occurrences de chaque mot d’un texte. Si vous savez comment on appelle ça, n’hésitez pas à partager l’information en laissant un commentaire à ce billet.
     
    Donc, voilà le script complet pour arriver au résultat décrit ci-dessus. Pour l’exemple, nous considérerons que le texte est le roman Alice’s Adventures in Wonderland, disponible sur http://www.gutenberg.org/etext/11 notamment sous la forme d’un fichier texte nommé alice30.txt :
     
    PS> $alice = ([regex]"\w+").matches((type alice30.txt))
    PS> $index = $alice | %{$_.groups[0].value} | group
     
    Et voilà. Je vous l'avais dit: simple et rapide. La variable $index contient maintenant une entrée par mot, chaque entrée ayant trois propriétés :
     
    Count – représente le nombre d’occurrences du mot
    Name – le mot lui-même
    Group – un tableau qui reprend toutes les occurrences du mot (pas très utile en tant que tel, sauf à vouloir vérifier les variations majuscules/minuscules par exemple).
     
    On pourra donc manipuler $index, par exemple pour connaître les dix mots les plus fréquents dans ce texte :
     
    PS> $index | sort count -desc | select -first 10
     
    Count Name                      Group
    ----- ----                      -----
     1712 the                       {the, The, the, the...}
      906 and                       {and, and, and, and...}
      783 to                        {to, To, to, to...}
      658 a                         {a, a, a, A...}
      612 it                        {it, it, it, It...}
      564 of                        {of, Of, of, of...}
      553 she                       {she, she, she, she...}
      543 I                         {I, I, I, I...}
      465 you                       {you, you, you, you...}
      462 said                      {said, said, said, said...}
     
    Evidemment, sans surprise on retrouve principalement des articles, des conjonctions de coordination, des pronoms, et tutti quanti. Plus intéressant, j’aimerais savoir combien de fois le nom d’Alice est utilisé :
     
    PS> $index | ?{$_.name -eq "Alice"}
     
    Count Name                      Group
    ----- ----                      -----
      401 Alice                     {Alice, Alice, Alice, ALICE...}
     
    Et si je me souviens bien, Alice est accompagnée par un lapin. En anglais, lapin se dit rabbit. Vérifions donc la présence du rabbit dans notre texte :
     
    PS> $index | ?{$_.name -eq "rabbit"}
     
    Count Name                      Group
    ----- ----                      -----
       51 Rabbit                    {Rabbit, Rabbit, Rabbit, Rabbit...}
     
    51 fois seulement… On notera quand même qu’il semble toujours avoir une majuscule. Lewis Carroll ne parle donc pas d’un simple lapin mais bien de M. Lapin.
     
    Revenons un instant à notre première liste, notre « Top 10 ». Cette liste est décevante car elle ne nous donne pas les mots les plus significatifs. On aurait aimé voir quels noms communs, ou quels noms propres, ou quels verbes étaient utilisés en plus grand nombre. Il faudrait pouvoir filtrer l’index sur certains critères, comme par exemple la longueur des mots : si je regarde les mots constitués d’au moins trois lettres, j’élimine cinq des mots figurant dans ce Top 10, et sans doute un certain nombre de petits mots d’articulation figurant plus bas dans mon classement, et j’ai peu de risque d’éliminer des noms communs, des verbes ou des noms propres. Essayons :
     
    PS> $index | ?{$_.name.length -gt 2} | sort count -desc | select -first 10
     
    Count Name                      Group
    ----- ----                      -----
     1712 the                       {the, The, the, the...}
      906 and                       {and, and, and, and...}
      553 she                       {she, she, she, she...}
      465 you                       {you, you, you, you...}
      462 said                      {said, said, said, said...}
      401 Alice                     {Alice, Alice, Alice, ALICE...}
      357 was                       {was, was, was, was...}
      326 that                      {that, that, that, that...}
      248 her                       {her, her, her, her...}
      189 all                       {all, all, All, all...}
     
    Bon. Ce n’est pas très concluant, on retrouve notamment des articles et des pronoms. Si j’étends mon critère à au moins quatre lettres, je risque d’éliminer certains noms communs (comme tea, cat, sun, ou que sais-je encore). Non, une solution qui pourrait être satisfaisante consisterait à créer une « liste noire » qui contiendrait les mots que je veux exclure de mon index.
     
    Essayons. Je me constitue un autre fichier texte, blacklist.txt, qui contient tous les mots que je veux exclure, à raison d’un mot par ligne. Je commencerai par un fichier qui contient les mots the, and, she, you, was, that, her et all. Je prends ces mots-là parce qu’ils constituent le Top 10 actuel (avec Alice) et que je ne les considère pas comme significatifs (encore que j’aurais pu garder all, mais je pourrai toujours le réintégrer plus tard si je le souhaite). Ensuite, mon utilisation de cette liste noire donne ceci :
     
    PS> $blacklist = type blacklist.txt
    PS> $whitelist = $index | ? {$blacklist -notcontains $_.name -and $_.name.length -gt 2}
    PS> $whitelist | sort count -desc | select -first 10
     
    Count Name                      Group
    ----- ----                      -----
      462 said                      {said, said, said, said...}
      401 Alice                     {Alice, Alice, Alice, ALICE...}
      184 with                      {with, with, with, with...}
      178 for                       {for, for, for, for...}
      178 had                       {had, had, had, had...}
      176 but                       {but, but, But, BUT...}
      171 This                      {This, This, This, this...}
      155 not                       {not, NOT, not, not...}
      154 They                      {They, they, they, they...}
      144 very                      {very, very, VERY, VERY...}
     
    On voit que j’ai créé une “liste blanche” ($whitelist) qui est mon index auquel j’ai retiré les mots contenus dans $blacklist ainsi que les mots d’une longueur inférieure à trois lettres. Bon, au final le nouveau Top 10 n’est pas non plus très intéressant, si ce n’est qu’il confirme l’omniprésence de ces petits mots qui articulent le discours mais qui ne sont pas porteurs de sens (enfin, je me comprends, que les linguistes ne s’énervent pas sur mes résumés à la machette).
     
    Le procédé peut être raffiné. Peu importe d’ailleurs cet exemple, il me servait principalement à illustrer la facilité déconcertante avec laquelle on peut manipuler les textes dans PowerShell. Maintenant, à vous de jouer !
     
    Janel
    April 03

    Ajouter du texte dans un fichier sans aller à la ligne

    Depuis la nuit des prompts, tout le monde sait créer un fichier texte et ajouter du texte à un fichier existant. Sous sa forme la plus simple, avec COMMAND.COM, CMD.EXE ou PowerShell, on tapera quelque chose équivalent à :
     
    echo "Bienvenue à bord !" > message.txt
    echo "N’oubliez pas d’attacher vos ceintures." >> message.txt
    type message.txt
    Bienvenue à bord !
    N’oubliez pas d’attacher vos ceintures.
     
    N.B. Si vous faites l’essai depuis l’invite de commandes COMMAND.COM ou CMD.EXE, vous noterez que les guillemets ont été enregistrées avec le reste du texte.
     
    Bien. Mais comment faire si je veux ajouter du texte à la suite du texte existant, sans aller à la ligne ? Par exemple, je veux que mon fichier ait sur une même ligne le texte suivant :
     
    N’oubliez pas d’attacher vos ceintures. Eteignez vos téléphones portables et tous vos appareils électroniques.
     
    Si j’ajoute du texte avec la technique déjà utilisée, il ira sur une nouvelle ligne. Qu’est-ce que PowerShell peut faire pour moi ? Les fameuses commandelettes me seront-elles utiles ici ?
     
    D’abord, faisons un tour très rapide des commandelettes qui ajoutent du texte à un fichier. On en dénombre deux :
     
    add-content $fichier $texte
    $texte | out-file -append $fichier
     
    Vous l’aurez compris, $fichier est une variable qui représente le fichier auquel on ajoute le texte contenu dans la variable $texte.
     
    Bien. Malheureusement, aucune des deux commandelettes ne permet de s’affranchir du saut de ligne. Ce qui va nous sortir d’affaire ici, c’est la versatilité de PowerShell. On sait que PowerShell offre un accès complet à l’ensemble du Framework .NET. Ce qu’on sait moins, surtout lorsqu’on vient de l’administration système qui n’a pas eu affaire à .NET jusqu’à présent, c’est les trésors que renferme ce Framework. Et il se trouve que dans notre cas, la réponse y figure :
     
    PS> $fichier = [System.IO.File]::CreateText("C:\temp\message.txt")
    PS> $fichier.WriteLine("Bienvenue à bord !")
    PS> $fichier.Write("N’oubliez pas d’attacher vos ceintures.")
    PS> $fichier.Flush()
    PS> type message.txt
    Bienvenue à bord !
    N’oubliez pas d’attacher vos ceintures.
    PS> $fichier.Close()
    PS> $fichier = [System.IO.File]::AppendText((gci message.txt))
    PS> $fichier.Write(" Eteignez vos portables et tout le tralala.")
    PS> $fichier.Flush()
    PS> type message.txt
    Bienvenue à bord !
    N’oubliez pas d’attacher vos ceintures. Eteignez vos portables et tout le tralala.
    PS> $fichier.Close()
     
    Bon. Quelques commentaires s’imposent.
     
    On utilise la classe System.IO.File qui permet de manipuler des fichiers texte ou binaires. La première méthode statique à appeler permet soit de créer un fichier texte ([System.IO.File]::CreateText()) soit d’ouvrir un fichier texte existant pour y ajouter du contenu ([System.IO.File]::AppendText()). Dans les deux cas on passera en argument une référence au fichier, mais le type de référence peut varier :
     
    On peut passer le nom du fichier (attention à passer le nom avec son chemin complet, car les classes .NET ne reconnaissent pas le répertoire en cours de la session dans laquelle on est). C’est la technique qu’on utilisera si le fichier n’existe pas encore.
     
    Ou alors, on peut passer une référence au fichier sous la forme d’un objet récupéré par exemple par la commandelette get-childitem (plus connue sous ses alias dir ou gci).
     
    L’exemple ci-dessus utilise les deux techniques.
     
    Vous noterez ensuite deux méthodes distinctes pour écrire du texte, selon que l’on veut insérer une fin de ligne ou non :
     
    $fichier.WriteLine("Bienvenue à bord !")
    $fichier.Write("N’oubliez pas d’attacher vos ceintures.")
     
    Dans le premier cas, le texte sera terminé par une fin de ligne, pas dans le second cas. Tout texte ajouté à la suite de la deuxième ligne apparaîtra sur la même ligne.
     
    Une fois tout le texte ajouté, il ne vous reste plus qu’à forcer son écriture dans le fichier avec la méthode Flush() et à fermer le fichier avec la méthode Close(). J’ai utilisé ces méthodes deux fois dans l’exemple ci-dessus pour vous montrer la réouverture du fichier avec la méthode AppendText() mais j’aurais pu ne le faire qu’une fois, à la fin.
     
    Mais votre esprit perspicace aura déjà noté un petit souci. Comment fait-on si, après avoir ajouté du texte sans fin de ligne, on veut ajouter le texte suivant à la ligne ? Ainsi, dans l’exemple ci-dessus, j’ai ajouté la dernière phrase avec la méthode Write(), donc si j’ajoute encore du texte il apparaîtra sur la même ligne. Or, je veux aller à la ligne. Rien de plus simple :
     
    PS> $fichier = [System.IO.File]::AppendText((gci message.txt))
    PS> $fichier.Write("`n")
    PS> $fichier.Write("Merci.")
    PS> $fichier.Flush()
    PS> type message.txt
    Bienvenue à bord !
    N’oubliez pas d’attacher vos ceintures. Eteignez vos portables et tout le tralala.
    Merci.
    PS> $fichier.Close()
     
    Je rappelle aux étourdis que la séquence `n correspond à un saut à la ligne en PowerShell.
     
    Voilà. A vous de jouer.
     
    Janel