PowerShell : le couteau suisse des administrateurs Windows ?
Contexte
On avait jusqu'alors pour habitude de séparer le monde des administrateurs système en deux : celui du clic à la souris et celui de la ligne de commande. Les mondes Microsoft© et UNIX© s'opposaient fondamentalement.
Les fondements de ces deux approches dans l'administration d'un serveur repose à la base sur une volonté de répondre à des objectifs différents :
clic : je veux une interface visuelle et intelligible pour toutes les opérations, surtout si je ne sais pas trop ce que je dois faire.
ligne de commande : je veux des choses très unitaires que je puisse combiner et ainsi automatiser dans tous les sens.
Malheur à l'administrateur UNIX© qui souhaitait des belles IHMs. À moins de développer des solutions Web ou de s'appuyer systématiquement sur des consoles éditeurs aussi gourmandes que surchargées de fonctions, il est difficile de proposer des écrans d'administration intuitif. En revanche, lorsqu'il s'agit d'automatiser des tâches, notre cher sysadmin (ou root dans le cas présent) dispose d'un large éventail de possibilités. Plusieurs décennies ont permis d'affiner des outils aux noms aussi légendaires que courts : cat, sed, awk, grep, cut, tr, ls, sh et bien entendu les deux solutions miracles à tous les problèmes que sont vim et Perl. L'usage du pipe «|» est un exercice de gymnastique classique lorsqu'il s'agit de chainer plusieurs commandes à la suite.
L'administrateur Windowsien est généralement plus malheureux lorsqu'il va chercher à trouver la même puissance et la même flexibilité. Il est soit contraint de se battre avec VBscript, soit obligé de se lancer dans un vrai développement .Net.
Il y a quelques années, une tentative de la part de Redmond est venue semer la zizanie dans cette guerre de tranchée depuis longtemps enlisée.
Son objectif est de proposer des serveurs plus sécurisés et qui s'administrent et s'utilisent un peu comme des serveurs UNIX. Deux briques sont ainsi présentées : un système d'exploitation sans interface graphique (Windows Server Core), et un shell avancé permettant de pratiquer toutes les opérations d'administration possibles et imaginables (PowerShell).
PowerShell se positionne donc comme une brique très stratégique et pleine de promesses. Voyons plus en détail ce que l'on trouve sous le capot de PowerShell et s'il est apte à tenir ces promesses.
Même si la version 2.0 est désormais disponible, je propose ici un retour d'expérience sur la version 1.0 sur laquelle nous disposons désormais de longs mois d'utilisation, de codage et de déploiement dans des conditions réelles de production.
Livré en standard dans les versions serveur du système d'exploitation aux fenêtres (il est un pré-requis à bon nombre de briques), vous avez également la possibilité de le déployer sur des postes clients équipés d'XP/Vista/7 sous forme d'un auto-installeur. Les sites de recueil de tutoriaux, d'exemples et autres bonnes astuces fleurissent sur la toile.
Description
Le langage
Pour décrire cet outil, disons simplement que c'est une volonté de reprendre ce qui se fait de mieux dans les langages de scripts actuels. On y retrouve donc des bribes de Perl, Python, sh, Ruby. La liste pourrait être longue.
Il implémente le sacrosaint pipe «|» dans le but de chainer des commandes. La subtilité réside ici dans le fait que ce sont bien des objets qui sont transmis au travers du tuyau et non de simples chaines de caractère comme dans un Shell ou une session DOS classique. Il est du coup possible de faire des manipulations relativement poussées, d'autant plus que beaucoup de commande acceptent des données soit au travers de paramètres, soit au travers de leur entrée (et donc via le pipe).
Le nom des commandes est généralement composé de deux termes liés par un tiret «-» : Action-Objet, ou Action désigne généralement un verbe et Objet définit le type d'objet sur lequel l'action va s'appliquer. On arrive rapidement à devoir taper des commandes relativement longues.
La mise en place d'alias permet d'utiliser des fonctions via des noms courts. Et là, que l'on ne s'y trompe pas, on obtient un mélange de DOS/sh des plus hétéroclites : cat, cls/clear, ls, ps, kill, rm/del, rmdir, sleep, sort, man complétés par un ensemble de nouvelles fonctions propres à PowerShell.
La plateforme
Comme la plupart des langages de script, on a affaire à un langage interprété, ce qui prémunie des compilations explicites et permet des interprétations à la volée de code généré dynamiquement.
Un des points notable est que le langage est interprété dans la CLR (moteur d'exécution .Net, qui pourrait être plus ou moins comparé à la JVM), ce qui donne accès, presque mécaniquement à tout le fond de développement du monde .Net. Il est donc aisé de s'appuyer sur des briques classiques comme Log4Net pour gérer ses journaux, ou même de développer ses propres fonctions dans des langages .Net natifs (C# par exemple).
Pour illustrer cet atout, il est par exemple possible de produire en C# une HashTable d'objets .Net et la récupérer directement dans du code PS pour poursuivre le traitement, sans devoir passer par la case conversion/transformation.
En terme d'accès natifs, citons bien sûr les systèmes de fichiers, mais également la base de registres, les annuaires LDAP, les requêtes WMI, les objets COM.
Pour démarrer un PowerShell, soit on ouvre directement une console PowerShell, soit on lance d'abord une console DOS (cmd) dans laquelle on empile l'interpréteur PowerShell, comme on le ferait avec des Shell UNIX.
Les bibliothèques
Comme dit précédemment, il est possible d'utiliser n'importe quelle bibliothèque .Net préalablement produite dans n'importe quel langage de la suite .Net. Mais c'est dans l'intention que se trouve la nouveauté dans les bibliothèques proposées.
Un effort a été massivement consenti puisque les grosses briques d'infrastructure de MS fournissent systématiquement des extensions (appelées Snapins) pour PowerShell : Exchange, ActiveDirectory (Quest fournit d'ailleurs une bibliothèque très pratique à utiliser), IIS, SQL Server. Désormais, pour tout ce qui est faisable par une interface Graphique, une CommandLet (cmdlet) existe pour faire la même chose en ligne de commande ou par script.
On peut même aller plus loin en précisant que certaines fonctions avancées ne sont disponibles qu'en ligne de commande. On se retrouve donc dans un modèle qui se rapproche étonnement de celui d'UNIX.
Grande lacune de la version 1.0, citons le manque de méthodes d'accès à des interpréteurs PS de manière distante. On a toujours la possibilité de venir encapsuler des PS au travers de SSH/Cygwin/bash, au risque de se retrouver assez vite face à une situation des plus scabreuses en terme de gestion des droits, des interpréteurs, des environnements.
Quelques exemples
Voici quelques exemples de choses faisables. Je me place ici dans la position d'un UNIXien qui cherche à retrouver ses petits.
Commençons par le fameux «RTFM». La commande Get-Help (ou get-help, car le langage n'est pas sensible à la casse) associée à n'importe quelle autre commande retournera la page d'aide. Get-Help Get-Help vous retournera un équivalent du man man. On retrouve dans les pages d'aide des sections comme NAME, SYNOPSIS, SYNTAX, DETAILED DESCRIPTION, RELATED LINKS, REMARKS.
Ensuite, on va pouvoir passer à la consultation des alias en place pour découvrir quel est le nom de la vraie commande qui se cache derrière un nom plus court ou plus UNIX-aware.
PS C:\Temp> alias kill
CommandType Name Definition ----------- ---- ---------- Alias kill Stop-Process
On le voit le formatage nous produit une sortie sous forme d'un tableau, ce qui laisse à penser que l'on a au moins 3 propriétés à l'objet retourné.
Passons à l'étape suivante avec l'utilisation du pipe qui va nous permettre d'appliquer ici un filtre.
PS C:\Temp> Get-Process | where { $_.ProcessName -eq "Powershell"}
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 530 44 80372 3884 633 47,19 7748 powershell 730 51 240740 255976 658 400,84 40756 powershell
Les puristes crieront que cette opération peut-être plus simplement écrite sous la forme ps -name PowerShell, mais l'objectif ici est d'introduire les notions de where et la variable automatique $_, qui ne surprendra aucun Perlist digne de ce nom.
Pour accéder au journal d'événements, là où un root ferait un tail -20 /var/log/syslog, un Administrator serait plutôt tenté de lancer la commande suivante :
Get-EventLog -logname application | select -last 20
Une fois le principe compris, nous allons pouvoir faire tourner des commandes directement utiles pour un administrateur.
get-MailboxStatistics | ` where {"IssueWarning","ProhibitSend","MailboxDisabled" -contains $_.StorageLimitStatus} | ` format-Table DisplayName,TotalItemSize
Dans cette commande, qui doit être exécutée sur un serveur Exchange, nous allons pouvoir collecter la liste des Boites aux lettres qui sont en dépassement de quota.
Voyons comment supprimer tous les contacts d'une organisation Exchange en une passe :
Get-MailContact -ResultSize Unlimited | Remove-MailContact -Confirm:$False
Ça y est, nous avons accès à des commandes dangereuses, puissantes, à donner des sueurs froides à tout responsable de production, le bonheur quoi.
Regardons plus en détail le langage en tant que tel. Rien de bien perturbant si ce n'est que l'on est souvent confronté à plusieurs approches pour effectuer une même opération. Exemple ici avec une simple somme des valeurs d'une liste.
$myList = $(8, 2, 5, 4, 10, 9, 34, 244) $sum = 0 for ($i = 0; $i -lt $myList.length; $i++) { $sum += $myList[$i] }
On préférera bien entendu une formule moins propice aux erreurs comme :
$sum = 0 foreach (****$i in $myList) { $sum += $i }
Et pour les plus violents (attention à la lisibilité) :
$sum = 0 $myList | % { $sum += $_ }
Dernier petit exemple pour montrer les notations des fonctions, listes, dictionnaires et quelques-unes de leurs manipulations, sans autre intérêt que celui de la démonstration.
function TweakMyData**([hashtable]$aDict**, [array]$aList, [int]$aValue) { $aList | % { if ($myDict.containsKey**($_))** { $myDict[$_] += $aValue } } }
$myDict = @{"un" = 1 ; "deux" = @("item1", "item2"); "trois" = "tagada"} $myList = @("un", "deux", 38**)**
write "avant:" $myDict | ft
TweakMyData $myDict $myList 3
write "après:" $myDict | ft
Et dans la vraie vie, qu'est ce que ça donne ?
Effectivement on peut à peu près tout faire
Dans le cadre de mise en place d'outils d'automatisation, d'alimentation automatique d'entrées comptes, de boites aux lettres de compte OCS, on arrive à devoir chainer un ensemble conséquent d'opérations techniques. À ce jour, il a toujours été possible de le faire en PowerShell. Parfois une commandlet fait tout en une passe, parfois il est nécessaire d'en chainer plusieurs, mais dans tous les cas on arrive à ses fins. On arrive assez vite à faire des scripts de plusieurs centaines de ligne de code pour traiter tous les cas de figure.
L'environnement naturellement distribué des infrastructures annuaire/messagerie conduit généralement à devoir gérer deux problématiques classiques : le manque d'atomicité des opérations globales (qui rend pénible les retours arrière) et les délais de réplication d'annuaire qui peuvent engendrer pas mal de surprises.
La gestion des erreurs est perfectible
En l'absence de structure de type try-catch-finalize, inutile de rêver, on s'arrache les cheveux avec des blocs trap.
Là où un shell UNIX n'offre que peu de solutions élégantes, on pouvait espérer d'un outil aussi récent une avancée plus impressionnante en termes de gestion des erreurs.
Si on le compare à d'autres langages de scripts/interprétés «modernes» (Python, Ruby), c'est selon moi un défaut majeur, qui semble-t-il a été comblé dans la version 2.0.
Le typage des objets est aléatoire
Comme la plupart des langages de scripts les variables ne sont pas vraiment typées et c'est dynamiquement que l'interpréteur essaye de comprendre les types d'objets à lire/produire. Là ou certains langages se sortent très bien de cette situation, PowerShell peine à rendre les choses simple. On arrive très fréquemment à devoir aider l'interpréteur à prendre les bonnes décisions à grands coups de [string].
Les performances sont mauvaises
L'utilisation systématique d'objets dans toutes les parties du code entraine une sanction sans appel. On en arrive du coup à devoir proposer un script épaulé par une bibliothèque dynamique écrite en C# qui vient implémenter des routines qui seraient bien trop lentes en PS.
L'effet est immédiat, car on parvient à rendre certaines portions de code plusieurs centaines de fois plus rapides.
L'impact est de devoir maitriser deux langages, dont l'un nécessite un environnement de développement complet pour être compilé, dommage.
La stabilité est discutable
Au moment où le comportement d'un script devient non reproductible, il est grand temps de fermer et relancer sa console PowerShell. Ce jugement, certes peu flatteur est malheureusement constaté soit directement dans une console, soit lorsque l'on développe dans l'éditeur multi-usage/débogueur qu'est PowerGUI (lui-même écrit en PS).
Fort heureusement, lorsqu'il s'agit de lancer des tâches planifiées en PS, on démarre un interpréteur tout neuf qui fait tourner notre script favori et finit par se terminer, on n'est donc généralement pas dans un cas d'utilisation problématique.
Conclusion
Je m'interroge sur l'objectif profond de la mise en place de PowerShell. Je suis partagé entre plusieurs sentiments.
Faut-il y voir une tentative d'attirer les UNIXiens vers la plateforme aux fenêtres ? Est-ce finalement un aveu de la suprématie indiscutable du modèle des scripts ? Un relent de réalisme face à la grogne des Administrators qui souffrent de ne pouvoir faire des traitements en masse ?
Quoi qu'il en soit, le fossé ne me semble que partiellement comblé. L'arrivée de PowerShell 2.0 apporte son lot d'améliorations, mais je me garderai bien des effets d'annonce sans avoir un réel retour d'expérience dans la durée, qui fera sans doute l'objet un nouveau billet.