Windows PowerShellÉcriture de scripts une ligne à la fois

Don Jones

Dans de précédents articles, j'ai insisté sur le fait que Windows PowerShell est un shell. Il est destiné à une utilisation interactive, un peu comme le shell cmd.exe (ou invite de commande) que vous connaissez sans doute déjà. Ceci dit, Windows PowerShell prend en charge un langage de script : ce langage est plus robuste que le langage de lot de cmd.exe. Il est

également tout aussi puissant, voire plus, que des langages comme VBScript. Cependant, le fait que Windows PowerShell™ est un shell interactif facilite considérablement l'apprentissage de l'écriture des scripts. En fait, vous pouvez développer des scripts de façon interactive à l'intérieur du shell, ce qui permet d'écrire des scripts une ligne à la fois et de voir immédiatement le résultat de vos efforts.

Cette technique d'écriture des scripts interactifs facilite également le débogage. Puisque vous voyez les résultats de votre script immédiatement, vous pouvez le réviser rapidement lorsque le résultat n'est pas celui attendu.

Dans cet article, je vous décrirai étape par étape un exemple d'écriture de script interactif dans Windows PowerShell. Je créerai un script lisant les noms de services à partir d'un fichier texte et définissant le mode de démarrage de chaque service sur Désactivé.

J'aimerais que vous reteniez de cette description le concept de création morceau par morceau de scripts Windows PowerShell, plutôt que d'essayer de s'attaquer à un script entier en une fois. Vous pouvez prendre n'importe quelle tâche administrative à accomplir et la décomposer pour comprendre comment faire fonctionner chaque composant de manière indépendante. Windows PowerShell fournit d'excellents moyens de relier ces morceaux. Vous découvrirez aussi qu'en travaillant morceau par morceau, vous aurez moins de mal à développer le script final.

Lecture de noms à partir d'un fichier

Il n'est pas toujours évident de comprendre comment lire des fichiers texte dans Windows PowerShell. Si j'exécute Help *file*, je n'obtiens que le cmdlet Out-File, qui envoie du texte à un fichier, plutôt que de lire dans le fichier. Pas très utile ! Cependant, les noms de cmdlets Windows PowerShell suivent une certaine logique que je peux utiliser à mon avantage. Lorsque Windows PowerShell récupère quelque chose, le nom de cmdlet commence généralement par Get. Donc j'exécute Help Get* pour afficher ces cmdlet, puis je fais défiler la liste jusqu'à Get-Content. Cela paraît prometteur ! J'exécute donc Help Get-Content pour en savoir plus (voir la figure 1) et on dirait que c'est ce qu'il me faut.

Figure 1 Exécution de Help Get-Content pour plus d'informations

Figure 1** Exécution de Help Get-Content pour plus d'informations **(Cliquer sur l'image pour l'agrandir)

Windows PowerShell traite presque tout comme un objet et c'est le cas notamment pour les fichiers texte. D'un point de vue technique, un fichier texte est un ensemble de lignes, chaque ligne du fichier jouant en quelque sorte le rôle d'un objet indépendant. Donc, si j'ai créé un fichier texte nommé C:\services.txt et que je l'ai rempli de noms de services (en plaçant chaque nom sur sa propre ligne à l'intérieur du fichier), Windows PowerShell peut lire les noms individuellement à l'aide du cmdlet Get-Content. Puisque l'idée de cette description est de montrer comment les scripts peuvent être développés de façon interactive, je commencerai par exécuter Get-Content, en lui donnant le nom de mon fichier texte, avant de voir ce qui se passe :

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

Comme prévu, Windows PowerShell lit le fichier et affiche le nom. Bien entendu, l'affichage des noms n'est pas exactement ce qui m'intéresse, mais maintenant je sais que Get-Content fonctionne comme je le veux.

Modification d'un service

À présent, je souhaite modifier le mode de démarrage d'un service. À nouveau, je commence par essayer de trouver le bon cmdlet. J'exécute donc Help *Service*. Ceci renvoie une liste courte et le cmdlet Set-Service a l'air d'être le seul adapté à mes besoins. Je veux tester ce cmdlet pour m'assurer que je comprends comment il marche avant d'essayer de l'incorporer dans un script. L'exécution de Help Set-Service me montre comment le cmdlet doit fonctionner et un test rapide le confirme :

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

Association des deux parties

Je dois maintenant associer la capacité de lire des noms de services à partir d'un fichier au cmdlet Set-Service et c'est là que les fonctionnalités de pipeline puissantes de Windows PowerShell entrent en jeu. Avec le pipeline, le résultat d'un cmdlet peut servir d'entrée pour le second cmdlet. Le pipeline fait passer des objets entiers. Au cas où un ensemble d'objets est placé dans le pipeline, chaque objet passe par le pipeline individuellement. Cela signifie que le résultat de Get-Content qui, souvenez-vous, est un ensemble d'objets, peut être transmis à Set-Service. Étant donné que Get-Content fait passer un ensemble, chaque objet ou ligne de texte de l'ensemble est transmis à Set-Service individuellement. Il en résulte que Set-Service est exécuté une fois pour chaque ligne dans mon fichier texte. Voici à quoi ressemble la commande :

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

Voici ce qui se produit :

  1. Le cmdlet Get-Content s'exécute, en lisant le fichier entier. Chaque ligne du fichier est traitée comme un objet unique et ensemble elles représentent un ensemble d'objets.
  2. L'ensemble d'objets est transmis à Set-Service.
  3. Le pipeline exécute le cmdlet Set-Service une fois pour chaque objet d'entrée. Pour chaque exécution, l'objet d'entrée est transféré à Set-Service comme premier paramètre du cmdlet, c'est-à-dire le nom de service.
  4. Set-Service s'exécute, en utilisant l'objet d'entrée pour son premier paramètre et les autres paramètres spécifiés, le paramètre -startuptype, dans ce cas.

Il est intéressant de souligner qu'en fait, à ce stade, j'ai accompli ma tâche sans avoir écrit un seul script. Il serait difficile de réaliser cette même action dans le shell Cmd.exe et cela nécessiterait une dizaine de lignes de code dans VBScript. Mais Windows PowerShell réalise tout cela en une ligne. Ceci dit, je n'ai pas complètement fini. Comme vous pouvez le voir, ma commande ne fournit pas beaucoup de sorties d'états ou de commentaires. En dehors du manque d'erreurs, il est difficile de voir si quelque chose s'est produit. À présent que je maîtrise la fonctionnalité nécessaire pour accomplir ma tâche, je peux commencer à la rendre plus attrayante en écrivant des scripts.

Invite de Windows PowerShell ici par Michael Murgolo

L'outil Ouvrir une fenêtre de commandes ici est l'un des PowerToys Microsoft® pour Windows® les plus populaires (et également l'un de mes préférés). Disponible dans le cadre des PowerToys Microsoft pour Windows XP ou dans les outils du kit de ressources Windows Server® 2003, Ouvrir une fenêtre de commandes ici vous permet de cliquer avec le bouton droit de la souris sur un dossier ou lecteur dans Windows Explorer pour ouvrir une fenêtre de commandes qui pointe vers le dossier sélectionné.

En découvrant Windows PowerShell, je me suis pris à souhaiter y voir la même fonctionnalité. Je me suis donc emparé du fichier .inf de configuration d'Ouvrir une fenêtre de commandes ici, cmdhere.inf, à partir des outils du kit de ressources Windows Server® 2003. Puis, je l'ai modifié pour créer un menu contextuel Invite de Windows PowerShell ici. Ce fichier .inf est inclus avec le message de blog original sur lequel se base cet encadré (disponible à l'endroit suivant : leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx). Pour installer l'outil, cliquez avec le bouton droit de la souris sur le fichier .inf et sélectionnez Installer.

En créant la version Windows PowerShell de cet outil, j'ai découvert que l'original avait un bogue : si Ouvrir une fenêtre de commandes ici était désinstallé, il resterait une entrée inutilisée dans le menu contextuel. Par conséquent, j'ai fourni une version mise à jour de cmdhere.inf, également disponible sur le message de blog original.

Ces deux PowerToys tirent parti du fait que ces entrées de menu contextuel sont configurées dans le registre sous des clés associées au Répertoire et aux types d'objets Lecteur. Ceci est semblable aux actions de menu contextuel qui sont associées à des types de fichiers. Par exemple, lorsque vous cliquez avec le bouton droit sur un fichier .txt dans Windows Explorer, plusieurs actions (telles que Ouvrir, Imprimer et Modifier) figurent en haut de la liste. Pour comprendre comment ces éléments sont configurés, examinons la ruche de registre HKEY_CLASSES_ROOT.

Si vous ouvrez l'Éditeur du Registre et que vous développez la branche HKEY_CLASSES_ROOT, vous verrez des clés nommées pour les types de fichiers tels que .doc, .txt et autres. Si vous cliquez sur la clé .txt, vous verrez que la valeur (par défaut) est txtfile (voir la figure A). Il s'agit du type d'objet associé à un fichier .txt. En faisant défiler vers le bas et en développant la clé txtfile, puis en développant la clé de shell correspondante, vous verrez des clés nommées pour certaines des entrées du menu contextuel pour un fichier .txt. (Vous ne les verrez pas toutes parce qu'il y a d'autres méthodes de création de menus contextuels). Sous chacune d'entre elles se trouve une clé de commande. La valeur (par défaut) se trouvant sous la clé de commande est la ligne de commande que Windows exécute lorsque vous sélectionnez cet élément du menu contextuel.

Les outils de l'invite cmd et de l'invite Windows PowerShell utilisent cette technique pour configurer les entrées de menu contextuel Ouvrir une fenêtre de commandes ici et Invite Windows PowerShell ici. Il n'y a pas de type de fichier associé aux lecteurs et aux répertoires, mais il y a des clés Lecteur et Répertoire sous HKEY_CLASSES_ROOT associées à ces objets.

Figure A Les entrées de menu contextuel sont configurées dans le Registre.

Figure A** Les entrées de menu contextuel sont configurées dans le Registre. **(Cliquer sur l'image pour l'agrandir)

Michael Murgolo est consultant en infrastructure senior pour Microsoft Consulting Services. Il est spécialisé dans les domaines des systèmes d'exploitation, du déploiement, des services réseau, d'Active Directory, de la gestion des systèmes, de l'automatisation et de la gestion des correctifs.

Écriture interactive de scripts

J'ai besoin d'avoir une exécution de plusieurs cmdlets pour chaque service répertorié dans C:\services.txt. De cette façon, je peux générer le nom de service et toutes les informations qui m'intéressent, afin de pouvoir suivre la progression du script.

Avant, j'utilisais le pipeline pour faire passer des objets d'un cmdlet à un autre. Cette fois, cependant, je vais utiliser une technique plus semblable au script appelée construction Foreach (que j'ai présentée dans l'article du mois dernier). La construction Foreach peut accepter un ensemble d'objets et elle exécutera plusieurs cmdlets pour chaque objet de l'ensemble. Je désigne une variable qui représente l'objet actuel à chaque fois qu'il passe par la boucle. Par exemple, la construction pourrait commencer de la façon suivante :

foreach ($service in get-content c:\services.txt)

J'exécute toujours le même cmdlet Get-Content pour récupérer le contenu du fichier texte. Cette fois, je demande à la construction Foreach de faire une boucle dans l'ensemble d'objets renvoyé par Get-Content. La boucle s'exécute une fois pour chaque objet et l'objet actuel est placé dans la variable $service. À présent, il faut que je spécifie le code que je souhaite exécuter dans la boucle. Je commencerai par essayer de dupliquer ma commande d'une ligne d'origine (ceci réduira la complexité et m'empêchera de perdre des fonctionnalités) :

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

Quelque chose d'intéressant se produit ici. Vous noterez que j'ai terminé la première ligne de la construction Foreach par une accolade ouvrante. Tout ce qui se trouve entre l'accolade d'ouverture et l'accolade de fermeture est considéré comme étant à l'intérieur de la boucle Foreach et sera exécuté une fois pour chaque objet de l'ensemble d'entrée. Notez qu'une fois que j'ai saisi { et appuyé sur la touche Entrée, l'invite Windows PowerShell est devenue >> (voir la figure 2). Ceci indique que le programme est conscient du fait que j'ai commencé une construction et qu'il attend que je la finisse. J'ai saisi ensuite mon cmdlet Set-Service. Cette fois, j'ai utilisé $service comme premier paramètre, puisque $service représente le nom de service actuel lu à partir de mon fichier texte. Sur la ligne suivante, je conclus la construction par une accolade de fermeture. J'appuie deux fois sur la touche Entrée et Windows PowerShell exécute immédiatement mon code.

Figure 2 Windows PowerShell sait qu'une construction a été lancée.

Figure 2** Windows PowerShell sait qu'une construction a été lancée. **(Cliquer sur l'image pour l'agrandir)

En un clin d'œil ! Ce que j'ai saisi ressemble à un script, mais Windows PowerShell l'exécute en fait en direct et il n'est stocké dans aucun fichier texte nulle part. À présent, je vais tout retaper, en ajoutant une ligne de code pour sortir le nom de service actuel :

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

Notez que je lui demande d'afficher quelque chose et que j'ai mis ce quelque chose entre des guillemets doubles. Les guillemets indiquent à Windows PowerShell qu'il s'agit d'une chaîne de texte, pas d'une autre commande. Cependant, lorsque vous utilisez des guillemets doubles plutôt que des guillemets simples, Windows PowerShell recherche d'éventuelles variables dans la chaîne de texte. S'il en trouve, il remplace la valeur réelle de la variable par son nom. Ainsi, lorsqu'il exécute ce code, vous pouvez voir que le nom de service actuel est affiché.

Ce n'est toujours pas un script !

Jusqu'à présent, j'ai utilisé Windows PowerShell de façon interactive, ce qui est une façon excellente de voir immédiatement les résultats de ce que j'écris. Cependant, recopier ces lignes de codes va devenir lassant. C'est pourquoi Windows PowerShell peut également exécuter des scripts. En fait, les scripts Windows PowerShell suivent la définition la plus littérale du mot script : Windows PowerShell se contente de lire dans le fichier texte de script et « saisit » chaque ligne trouvée, exactement comme si vous saisissiez les lignes manuellement. Cela signifie que tout ce que j'ai fait jusqu'à présent peut être recollé dans un fichier de script. Je vais donc utiliser le Bloc-notes pour créer un fichier appelé disableservices.ps1 et coller dedans les informations suivantes :

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

J'ai placé ce fichier dans un dossier nommé C:\test. Je vais maintenant essayer de l'exécuter depuis Windows PowerShell :

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

Mince ! Que s'est-il passé ? Windows PowerShell est concentré sur le dossier C:\test, mais il n'a pas trouvé mon script. Pourquoi ? En raison des contraintes de sécurité, Windows PowerShell est conçu pour ne pas exécuter de scripts du dossier actuel, ce qui empêche les scripts de détourner une commande du système d'exploitation. Par exemple, je ne peux pas créer un script appelé dir.ps1 et le faire remplacer la commande dir normale. Si j'ai besoin d'exécuter un script du dossier actuel, je dois spécifier un chemin relatif :

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

Et maintenant ? Ça ne marche toujours pas. J'ai indiqué le bon chemin, mais Windows PowerShell dit qu'il ne peut pas exécuter de scripts. C'est parce que, par défaut, Windows PowerShell ne peut pas exécuter de scripts. Ici encore, il s'agit d'une mesure de précaution pour éviter les problèmes que nous avons tous eus avec VBScript. Les scripts malveillants ne peuvent pas s'exécuter sous Windows PowerShell par défaut parce qu'aucun script ne peut s'exécuter par défaut ! Pour exécuter mon script, je dois modifier la stratégie d'exécution de manière explicite :

PS C:\test> set-executionpolicy remotesigned

La stratégie d'exécution RemoteSigned autorise l'exécution de scripts non signés à partir de l'ordinateur local. Les scripts téléchargés doivent tout de même être signés pour être exécutés. Allsigned représente une meilleure stratégie. Il exécute uniquement des scripts ayant été signés numériquement avec un certificat émis par un éditeur approuvé. Cependant, je n'ai pas de certificat sous la main, donc je ne peux pas signer mes scripts, ce qui fait que RemoteSigned est la meilleure solution dans le cas présent. À présent, je vais réessayer d'exécuter mon script :

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

Je tiens à signaler que la stratégie d'exécution RemoteSigned que nous utilisons n'est pas un très bon choix, mais elle fait l'affaire ici. Il existe toutefois une bien meilleure solution. Il serait beaucoup plus sûr d'obtenir un certificat de signature de code, d'utiliser le cmdlet Windows PowerShell Set-AuthenticodeSignature pour signer mes scripts et de définir la stratégie d'exécution sur la stratégie AllSigned, qui est beaucoup plus sûre.

Il existe également une autre stratégie, Unrestricted, qu'il vaut mieux éviter. Cette stratégie autorise l'exécution absolue de tous les scripts, même les scripts malveillants, d'emplacements distants sur votre ordinateur, ce qui peut être très dangereux. Par conséquent, je vous recommande d'éviter à tout prix d'utiliser la stratégie Unrestricted.

Résultats immédiats

Les capacités d'écriture de scripts interactive dans Windows PowerShell vous permettent de tester rapidement des scripts, voire simplement des petits morceaux de scripts. Vous obtenez des résultats immédiats et vous pouvez ainsi modifier vos scripts aisément pour obtenir les résultats souhaités. Lorsque vous avez terminé, vous pouvez transférer votre code dans un fichier .ps1, ce qui le rend permanent et facilement accessible à l'avenir. N'oubliez pas ! Idéalement, vous devez signer numériquement ces fichiers .ps1 pour laisser Windows PowerShell défini sur la stratégie d'exécution AllSigned, la stratégie la plus sûre pour l'exécution de scripts.

Don Jones est le gourou principal des scripts chez SAPIEN Technologies et co-auteur de Windows PowerShell : TFM (voir www.SAPIENPress.com). Contactez Don à l'adresse don@sapien.com.

© 2008 Microsoft Corporation et CMP Media, LLC. Tous droits réservés. Toute reproduction, totale ou partielle, est interdite sans autorisation préalable.