Passer d'un script à un "bon" script (PowerShell)

Originally published at: https://rdr-it.com/blog/passer-dun-script-a-un-bon-script-powershell/

Bonjour à tous, Dans cet article “un peu particulier” qui ne va pas être réellement un tutoriel, je vais essayer de vous pousser à la réflexion pour passer d’un script “simple” à un “bon” script. Avant de rentrer dans le sujet, je vais poser le contexte de cet article afin de comprendre pourquoi j’ai écrit…

Bonjour, comme toujours Post très intéressant. Ce que j’apprécie particulièrement, c’est la démarche didactique.
On passe d’un ensemble de cmdlets basiques, à l’optimisation de la requête, puis à la gestion des erreurs.
j’ai cependant quelques remarques que je veux constructive.

Si nous prenons ceci :

Get-ADGroupMember -Identity $NomGroupe -Recursive | Select name, objectClass | Where { $_.objectClass -eq "user" }

Nous pouvons voir 3 cmdlets qui sont chainées via le pipeline. La première ramasse une collection d’objects, la seconde filtre sur cette collection pour ne conserver que 2 propriétés, et enfin la 3ème, filtre une seconde fois pour ne conserver que les « Users ».
Ce n’est pas totalement optimisé en terme de requête. Il aurait fallut bâtir la requête ainsi :

Get-ADGroupMember -Identity $NomGroupe -Recursive |  Where { $_.objectClass -eq "user" | Select name, objectClass  }

Dans le cas présent (et en toute franchise, je n’ai pas vérifié via Measure-Command), mais il y a une grande règle sui s’exprime ainsi « Filter Left, Format right ». Il faut filtrer le plus possible à gauche, et la présentation (format) c’est à la fin. Tout ça pour passer une collection moins importante dans le pipeline pour les cmdlets suivantes.
La démarche est donc la suivante :

  • Est-ce que la 1ère cmdlet a un paramètre -Filter ? Si oui, on filtre sur celle-ci. Ce n’est pas le cas, dans l’exemple présenté

  • Filtrage sur la seconde cmdlet avec Where-Object

  • Et enfin format, avec Select-Object pour la 3ème.

  1. Il faut éviter d’utiliser des alias dans les scripts (dans un shell, on fait ce qu’on veut, mais dans les scripts, il faut éviter). Ca rend les scripts moins lisibles et compréhensibles. Des scripts blindés de %, ? et autres alias, deviennent juste du charabia pour ceux qui les lisent et essaient de les comprendre.

  2. Concernant le « Error Handling » (gestion des erreurs).
    Très bien l’utilisation du Try… Catch, … mais tu as oublié 2 points. le premier est primordial, le second plus « anecdotique ».

  • La (ou les) cmdlet qui est passée dans le Try doivent être suivi impérativement du paramètre -ErrroAction Stop, … sinon cela ne passera jamais au Catch.

  • Bien le message d’erreur dans le Catch, mais tu aurais pu également « trapper » l’erreur avec $_ ou $_.Exception.Message ou encore $Error[0] (la dernière des erreurs).

  1. Si on destine ce script à un usage interactif, le Write-Host (avec ou sans paramètre en sus comme -ForegroundColor xxxx) est OK. Cependant, si le script est destiné à un usage silencieux, il faut éviter le Write-Host et lui préférer des Write-Output, Write-Information, Write-Error, car il n’y a pas de host dans ce cas.
    Un moyen simple de s’apercevoir de tout ceci est d’utiliser le module PSScriptAnalyzar. Une seule cmdlet à connaitre Invoke-ScriptAnalyzer -Path \monscript.ps1 et en console, cela retourne les Avertissements et Erreurs par rapport au bonnes pratiques. Ce module est à ajouter dans PS ou ISE, mais est en standard avec VisualStudio Code.

Dans le cas d’un usage silencieux (tâche planifiée par ex.), on peut demander l’exécution de script.ps1 + passer les paramètres, ou directement script.ps1 pour peu que les valeurs des paramètres (section param) aient une valeur par défaut.

  1. Le Exit à la fin, … on peut s’en passer, C’est fini, cela stoppe tout seul :slight_smile:

  2. Tu as une seule variable collée en dur dans ton script (le nom du fichier de sortie). Tu aurais pu également la même en param() avec sa valeur par défaut (la convention de nommage que tu as défini). Ainsi, pas la peine de se « palucher » tout le code pour modifier le nom du fichier de sortie :slight_smile: … et cela aurait introduit la notion de « valeur par défaut » pour certains paramètres.

  3. J’allais oublier ce point également. Tu testes ta cmdlet Get-ADGroupMember mais pas le path de sortie… Tu pourrais soit créer le path (du répertoire) s’il n’existe pas, ou peut-être mieux t’affranchir de tout cela et utiliser la variable automatique $PSScriptRoot (path ou est le script), car ça on est certain que cela existe (voire même un sous répertoire que tu pourrais créer (ex. Sortie) dans $PSScriptRoot.

Passons sur ces critiques, que je veux constructive, et sur les points très positifs :

  • Il est (bien trop) rare de voir une section param dans les scripts (dans les fonctions, oui, mais dans les scripts cela reste rare), … et pourtant qu’est-ce que c’est bien d’avoir tous les paramètres en début de script présentés comme cela. Excellent donc :slight_smile:

  • Les variables que tu crées ont des noms représentatifs que ce qu’elles contiennent. Ca c’est un point notable, que l’on voit malheureusement trop peu fréquemment.

Olivier
P.S. : Et une tartine de plus :-), décidément on ne se refait pas.

Bonjour Olivier,

Merci pour ton commentaire constructif, j’espère qu’il sera lu avant autant d’attention que l’article, tu facilites le travail des étudiants s’ils souhaitent optimiser et corriger le script :wink:

Après le but de l’article n’était pas forcément de leur fournir le script « parfait », mais donner une méthodologie de réflexion…

On aurait aussi pu dire que le script manque de commentaire … (ce qui est volontaire dans le contexte global)

Romain