PowerShell : Scripting sous Windows

 

Introduction

Peut-être en avez-vous entendu parler, Microsoft a récemment annoncé que bash, le fameux shell Unix, serait bientôt supporté par son système d’exploitation. Mais en attendant, Windows a déjà son propre shell – et non, je ne parle pas de MS-DOS !

PowerShell est un langage de script atypique bien intégré dans l’écosystème Windows, offrant de nombreuses possibilités. Il va de pair avec l’éditeur ISE qui, outre la coloration syntaxique, offre de l’auto-complétion, une invite de commande intégrée et la possibilité d’installer des plugins.

Dans cet article, nous allons aborder plusieurs exemples démontrant l’utilisation de PowerShell plus spécifiquement dans un contexte DevOps tels que celui dans lequel nous travaillons à Criteo où la facilité de déploiement et les capacités du langage font des merveilles pour améliorer des processus quotidiens parfois répétitifs, longs, ou comportant un risque d’erreurs humaines.

 

La théorie

S’il fait tout ce qu’un bon shell permet de faire, qu’il s’agisse de naviguer dans un système de fichiers ou lancer des programmes en ligne de commande, PowerShell est surtout d’un langage complet, disposant d’une syntaxe complète (foreach et consorts) et conçu spécifiquement pour rendre simples des actions complexes d’administration d’un parc de machines, de manipulation de données, ou d’interactions avec des produits Microsoft tels que la suite Office.

Pour parachever l’édifice, PowerShell permet également d’importer des dlls de façon dynamique, donnant ainsi accès à tout le framework .NET, mais aussi si besoin à toute codebase existante au même titre que n’importe quel langage de la CLR.

En PowerShell, le nom des commandes natives est composé de deux parties : verbe-nom ; une notation d’usage auto-descriptive qui favorise la recherche et la complétion. Par exemple, pour autoriser l’exécution de scripts non-signés sur une machine (par défaut interdite par sécurité), la commande sera :

Set-ExecutionPolicy Unrestricted

Mais passons à des cas concrets.

 

La pratique

La plus évidente des tâches à scripter dans un contexte DevOps est la livraison de fichiers. Le gain que permet un tel script est évident, aussi bien en termes de simplicité que de traçabilité des actions réalisées. Notre premier exemple illustre ce cas simple :

# Records all console input/output to a log file

Start-Transcript -Path log.txt

try {

    # Checks the package is available

    if (Test-Path -Path ./Package) {

        throw "Delivery package not found."

    }

    # Recursively copies folder to AppData dir, prompt before actions

    Copy-Item -Path ./Package -Destination $env:APPDATA -Recurse -Force -Confirm -Verbose

    Write-Output "Operation successful."

}

catch {

    Write-Error "Operation failed with exception: '$($_.Message)'."

}

Stop-Transcript

Ce script basique copie un package de livraison dans le dossier AppData de l’environnement de la machine cible. Outre la syntaxe et les commandes de base, il illustre quelques points du langage :

  • L’existence d’une gestion des exceptions et la variable implicite $_ permettant d’y accéder.
  • La retranscription des entrées/sorties de la console dans un fichier de log afin de tracer la session d’exécution courante.
  • L’accès aux variables d’environnement de la machine via la syntaxe « $env: ».
  • La mise en forme de chaînes avec inclusion directe de variables.

Mais PowerShell permet de réaliser des actions plus puissantes. Parmi celles-ci, se trouve la possibilité depuis Windows 8/Server 2012 d’interagir avec le planificateur de tâches :

$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File $path"

$trigger = New-ScheduledTaskTrigger -Daily -At 9am

 

Register-ScheduledTask -Action $action `

                       -Trigger $trigger `

                       -TaskName "RecurrentMonotoring" `

                       -Description "Recurrent task to monitor the server"

 

Write-Debug "Task created at $([DateTime]::Now)."

Ce script crée une tâche journalière qui se lancera chaque jour à 9h. La tâche lancera un autre script en lançant l’exécutable de PowerShell avec comme argument un chemin contenu dans la variable $path.

Nous noterons dans ce script :

  • L’utilisation de la syntaxe [Type], permettant de faire référence à des types définis dans une dll, ici issue du framework .NET accessible par défaut.
  • L’opérateur « :: » permettant d’accéder aux membres statiques de ce type.
  • L’utilisation du symbole ` (backquote), pour écrire une commande multi-ligne.

Créer une tâche de cette façon évite les erreurs manuelles et supprime le besoin d’éditer un guide d’installation à destination des Ops, ce qui est un gain en charge administrative.

Mais que cet exemple démontre la possibilité de planifier l’exécution de script n’est pas un hasard ! A Criteo, en l’attente d’outils dédiés au monitoring, nous avons mis en place une brique simple qui permet d’accéder à des logs IIS et d’en extraire les codes http 500 pour les faire parvenir à notre équipe, afin de décharger l’équipe Ops de cette tâche et de disposer de détails sur les erreurs rencontrées par nos utilisateurs de façon proactive.

L’exemple qui suit est une version simplifiée de ce script. Il récupère les logs d’erreurs depuis la veille (ici, dans le log d’événements Windows par commodité) et les envoie par mail après les avoir réarrangés sous forme de CSV :

# Gets all errors and warning in the event log

# Then, selects only fields we want to output in the CSV

# Lastly, exports the selected items as CSV

Get-EventLog -LogName application -After (Get-Date).AddDays(-1) -EntryType Error,Warning `

    | Select-Object -Property EventId,EntryType,Message,Source,TimeGenerated `

    | Export-Csv -Delimiter ';' -Path 'logs.csv'

 

# Create a dynamic PowerShell object from a JSON config file

$config = "$(Get-Content $configFile)" | ConvertFrom-Json

 

# Sends the CSV by mail using the provided config

Send-MailMessage                          `

    $config.MailRecipients                `

    -Subject "$(Get-Date) - server log"   `

    -BodyAsHtml "<b>See attachment.</b>"  `

    -From $config.MailSender              `

    -Attachments "logs.csv"               `

    -SmtpServer $config.smtpServer

 

Ce petit outil est accompagné d’un fichier JSON simple contenant la configuration nécessaire à l’envoi du mail, comme la plupart de nos outils. L’utilisation du JSON en PowerShell est extrêmement simple et offre de la flexibilité en termes d’externalisation de configuration ou de données.

Cet exemple illustre :

  • La simplicité de manipulation de divers formats de données via PowerShell, aussi bien en entrée qu’en sortie (CSV, JSON, mais ç’aurait put être du XML).
  • La possibilité qu’offre le langage d’utiliser des « pipes », un sucre syntaxique visant à relier la sortie d’une commande à l’entrée d’une autre afin d’enchaîner les traitements, exactement comme les shells Unix, permettant de mettre en place des « flux » de traitements poussés de données, éventuellement à la volée depuis une console.
  • La commande Select-Object, qui équivaut au Select de LINQ.

 

 

Plus loin

A propos de ce dernier point, il existe en PowerShell des équivalents à la plupart des fonctions LINQ pour la manipulation de données. Par exemple, la commande Where-Object permet de trier des objets selon un critère contenu dans une fonction anonyme :

Import-Csv log.csv -Delimiter ";" `

    | Where-Object { $_.Message -like "*error*" } `

    | Export-Csv filtered.csv -Delimiter ","

En une ligne, nous avons importé un CSV, sélectionné uniquement les lignes dont la colonne « Message » contient le mot « error », et réexporté le fichier en changeant le délimiteur. Il va de soi que maîtriser ce type de commandes représente une valeur ajoutée dans de nombreux contextes pour produire des résultats à partir de données n’étant pas exploitables humainement.

Dans cette même optique de concision et d’expressivité, la ligne précédente illustre aussi l’existence en PowerShell d’opérateurs pratiques tels que –like directement dans le langage. Il existe à ce même titre les opérateurs -contains, -join, -split, -replace…

Tout cela permet de manipuler des données complexes de façon concise, les convertir, filtrer, trier, et mettre en forme afin de les exploiter.

 

Conclusion

PowerShell porte bien son nom, car il est puissant de par les possibilités qu’il propose. Investir dans cet outil permet de gagner en productivité sur tout un éventail de tâches où l’utilisation d’un exécutable compilé est trop contraignante, où il est important d’interagir avec la personne exécutant l’outil sans développer une UI coûteuse, de pouvoir modifier ou déboguer le code facilement, de manipuler des flux de données avec une charge de développement minimale, etc.

Note : PowerShell reste un langage peu structurant (non-compilé, typé dynamiquement, environnement d’exécution dépendant de la console, de la machine…), il faut donc garder à l’esprit les questions de maintenabilité, de complexité et de performance lors du choix de la technologie malgré tout.

Dans un contexte DevOps comme celui de Criteo, automatiser les déploiements du CRM nous a initialement permis de passer d’un processus où nous rédigions des guides d’installation manuellement, avec des captures d’écran, des descriptions d’actions détaillées, etc. ; à un script utilisant des dlls développées par nos soins pour interagir directement avec le CRM, et de simples boîtes de dialogues natives permettant à l’équipe des Ops de saisir les informations leur incombant. Quelques lignes de code permettent ainsi de remplacer plusieurs pages de texte et d’images, tout en assurant un risque d’erreur quasi-nul et des temps d’exécution vingt fois meilleurs.

Partant de là, PowerShell est devenu la solution utilisée pour toutes nos livraisons, mais aussi pour développer des outils rapides (voir l’exemple du parseur de logs IIS) et des interfaces pour faire transiter des données depuis des sources aussi diverses que des APIs REST ou des feuilles Excel…

Tout ceci pourrait faire l’objet d’autre(s) article(s)… Mais pour cela, il faut que vous cliquiez sur le petit bouton bleu avec marqué « je vote », ci-dessous ! ;)

 

Références

  • Les prérequis PowerShell :
    • Au développement : ici
    • A l’exécution (commande Set-ExecutionPolicy) : ici
  • Une liste de plugins pour l’éditeur ISE : ici
  • Bonnes pratiques du développement PowerShell : ici
  • Paramétrer son shell (pour les afficionados !) :
    • Cas général : ici
    • Pour ISE : ici
    • Modifier son prompt : ici

Grégoire B

Commentaires

Xavier
Merci Grégoire pour cet article Je suis intéressé pour échanger avec toi prochainement sur la démarche DevOps chez Critéo
Grégoire B.
Avec plaisir !

Ajouter un commentaire