Windows PowerShellLa puissance de filtrage

Don Jones

Dans l'article du mois dernier, j'ai abordé la puissance et la flexibilité du pipeline Windows PowerShell, qui vous permet de faire passer un ensemble de données ou, plus précisément, un flux d'objets, d'une cmdlet à une autre, en affinant ces données jusqu'à ce que vous obteniez exactement ce que vous voulez. Dans mon article, j'ai fait allusion au fait que vos propres scripts,

pas seulement les cmdlets, peuvent également bénéficier du pipeline. Ce mois-ci, je souhaite aborder ce sujet en détail.

L'une des opérations les plus fréquentes que j'effectue dans Windows PowerShell™ consiste à écrire des scripts qui agissent sur plusieurs ordinateurs distants, généralement via Windows® Management Instrumentation (WMI). Comme pour toutes les tâches impliquant des ordinateurs distants, il existe toujours l'éventualité qu'un ou plusieurs ordinateurs ne soient pas disponibles lors de l'exécution du script. Mes scripts doivent être conçus en conséquence.

Bien sûr, il existe plusieurs méthodes qui permettent à un script de gérer un délai d'expiration de connexion WMI, mais je n'aime pas trop cette approche car le délai d'expiration est très long, environ 30 secondes, par défaut. L'exécution de mon script serait très ralentie s'il devait attendre des délais d'expiration fréquents. Au lieu de cela, je souhaite que mon script vérifie rapidement si un ordinateur donné est réellement en ligne avant d'essayer de s'y connecter.

Le paradigme de Windows PowerShell

Dans d'autres langages de script, tels que VBScript, je traiterais normalement un ordinateur à la fois. C'est-à-dire que je récupèrerais un nom d'ordinateur, peut-être à partir d'une liste de noms enregistrés dans un fichier texte, et enverrais une requête ping au système pour voir s'il est disponible. S'il était disponible, je lancerais la connexion WMI et exécuterais toutes les autres tâches que je dois effectuer. C'est une approche de type script courante. En fait, j'écrirais probablement tout mon code dans une boucle, puis répèterais cette boucle une fois pour chaque ordinateur auquel je dois me connecter.

Cependant, Windows PowerShell, est mieux adapté aux opérations par lots grâce à sa base dans les objets et sa capacité de travailler directement avec les groupes ou collections d'objets. Le paradigme de Windows PowerShell est de ne pas travailler avec des objets ou éléments de données uniques, mais plutôt de travailler avec des groupes entiers, en affinant le groupe petit à petit jusqu'à ce qu'à atteindre le résultat souhaité. Par exemple, au lieu de récupérer les noms d'ordinateur un par un dans ma liste, je lirais toute la collection de noms immédiatement. Au lieu d'envoyer une requête un ping à chaque ordinateur en boucle, j'écrirais une seule routine qui accepte une collection de noms, j'enverrais une requête ping à ceux-ci, puis j'afficherais ceux qui ont pu être contactés. L'étape suivant de mon processus consisterait à lancer ma connexion WMI aux noms restants, c'est-à-dire ceux qui ont répondu à la requête ping.

Windows PowerShell utilise cette approche pour un certain nombre de tâches. Par exemple, pour obtenir une liste des services en cours, je peux utiliser une commande comme celle-ci :

Get-Service | Where-Object { $_.Status –eq "Running" }

La figure 1 illustre le résultat apparaissant sur mon ordinateur. Au lieu de vérifier chaque service un par un, j'ai récupéré tous les services à l'aide de Get-Service, j'ai dirigé ceux-ci vers Where-Object, puis j'ai éliminé tous ceux qui ne s'exécutaient pas. C'est plus ou moins ce que je veux faire avec mon script : obtenir une liste de noms d'ordinateur, éliminer tous ceux qui ne répondent pas aux requêtes ping et faire passer la liste d'ordinateurs qui ont répondu à l'étape suivante.

Figure 1 Obtention d'une liste d'ordinateurs répondant aux requêtes ping

Figure 1** Obtention d'une liste d'ordinateurs répondant aux requêtes ping **(Cliquer sur l'image pour l'agrandir)

Fonctions de filtrage

Bien que je puisse écrire ma propre cmdlet pour réaliser cette opération, je ne le souhaite pas. La création de cmdlets nécessite Visual Basic® ou C# et une bonne connaissance du développement Microsoft® .NET Framework. Plus important encore, elle nécessite plus d'implication que je ne souhaite en investir pour cette tâche. Heureusement, Windows PowerShell me permet d'écrire un type spécial de fonction, nommé filtre, parfaitement capable d'agir dans le pipeline. La forme de base d'une fonction de filtrage ressemble à ceci :

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

Comme vous pouvez le constater, cette fonction contient trois blocs de script indépendants, nommés BEGIN, PROCESS et END. Une fonction de filtrage, c'est à dire une fonction conçue pour agir dans le pipeline afin de filtrer les objets, peut être constituée de toutes les combinaisons de ces trois blocs de script possibles, en fonction de ce que vous souhaitez faire. Ils fonctionnent comme suit :

  • Le bloc BEGIN s'exécute une fois lorsque votre fonction est appelée pour la première fois. Vous pouvez utiliser ce bloc pour effectuer la tâche d'installation, si nécessaire.
  • Le bloc PROCESS s'exécute une fois pour chaque objet pipeline traité par la fonction. La variable $_ représente l'objet d'entrée pipeline actuel. Le bloc PROCESS est requis dans une fonction de filtrage.
  • Le bloc END s'exécute une fois après le traitement de tous les objets pipeline. Il peut être utilisé pour toutes les tâches de finalisation, si nécessaire.

Dans mon exemple, je souhaite créer une fonction de filtrage qui acceptera une collection de noms en tant qu'objets d'entrées, puis essayer d'exécuter la commande ping sur chacun d'eux. Ceux qui répondent à la requête ping seront dirigés vers le pipeline. Les systèmes qui ne répondent pas à la requête ping seront simplement éliminés. Comme la fonctionnalité ping ne nécessite pas d'installation ou de finalisation spéciale, j'utiliserai simplement le bloc de script PROCESS. Le code de la figure 2 fournit le script complet.

Figure 2 Ping-Address et Restart-Computer.

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

Notez que j'ai défini deux fonctions : Ping-Address et Restart-Computer. Dans Windows PowerShell, les fonctions doivent être définies avant de pouvoir être appelées. Par conséquent, la première ligne exécutable de mon script est la ligne 24, qui utilise la cmdlet Get-Content pour obtenir une liste de noms d'ordinateur à partir d'un fichier (un nom d'ordinateur par ligne). Cette liste, techniquement une collection d'objets String, est dirigée vers la fonction Ping-Address, qui filtre tous les ordinateurs qui ne répondent pas à la requête ping. Les résultats sont dirigés vers Restart-Computer, qui utilise WMI pour redémarrer à distance chaque ordinateur répondant à la requête ping.

La fonction Ping-Address implémente un bloc de script PROCESS, ce qui signifie que la fonction attend une collection d'entrée d'objets. Le bloc de script PROCESS traite cette entrée automatiquement. Je n'ai pas eu besoin de définir des arguments d'entrée pour contenir l'entrée. Je démarre le processus à la ligne 3 en définissant la variable $ping sur $false, qui est une variable Windows PowerShell intégrée représentant la valeur Booléenne False.

J'utilise ensuite la classe WMI locale Win32_PingStatus pour envoyer une requête ping à l'ordinateur spécifié. Notez à la ligne 5 que la variable $_, qui représente l'objet pipeline actuel, est intégrée dans la chaîne de requête WMI. Lorsqu'une chaîne est contenue dans des guillemets doubles, Windows PowerShell essaiera toujours de remplacer les variables telles que $_ par leur contenu, pour que vous n'ayez pas à vous soucier de la concaténation de chaîne. J'utilise cette fonctionnalité à la ligne 5.

La ligne 6 est une boucle qui vérifie le résultat de mes requêtes ping. Si l'un de ces résultats est positif (c'est à dire, avec un StatusCode de zéro), je définis $ping égal à $true pour indiquer le succès. À la ligne 11, je vérifie si $ping a été défini sur $true. Si c'est le cas, j'ajoute l'objet d'entrée original au flux de sortie par défaut. Windows Powershell gère le flux de sortie par défaut automatiquement. Si cette fonction se trouve à la fin du pipeline, le flux de sortie est transformé en représentation texte. S'il y a plus de commandes dans le pipeline, les objets du flux de sortie, en l'occurrence les objets de chaîne contenant des noms d'ordinateur, sont transmis à la commande pipeline suivante.

La fonction Restart-Computer est un peu plus simple, mais elle utilise également un bloc PROCESS pour pouvoir participer facilement au pipeline. À la ligne 19, la fonction se connecte à l'ordinateur nommé et récupère sa classe WMI Win32_OperatingSystem. La ligne 20 exécute la méthode Reboot de la classe pour redémarrer l'ordinateur distant.

Encore une fois, c'est à la ligne 24 que l'exécution s'effectue réellement. Bien sûr, vous devez être très prudent lorsque vous exécutez ce script. Il est conçu pour redémarrer chaque ordinateur nommé dans c:\computers.txt, ce qui pourrait certainement avoir des résultats désastreux si vous ne faites pas attention aux noms de ce fichier texte !

Étapes suivantes

Ce script n'est pas complètement infaillible. Je dois reconnaître également l'éventualité d'une erreur WMI non liée à la connectivité de base, par exemple un pare-feu bloquant les ports nécessaires sur l'ordinateur distant ou une erreur de sécurité WMI. Je peux contrôler ces problèmes en implémentant un gestionnaire d'interruption, lequel qui pourrait constituer un sujet parfait pour un futur article.

Ce script exécute aussi une opération assez importante : le redémarrage d'un ordinateur distant. Tous les scripts qui tentent cette action potentiellement dangereuse doivent implémenter deux paramètres Windows PowerShell courants, –Confirm et –WhatIf. L'explication de la procédure à suivre dépasse le cadre de cet article, mais constituerait elle aussi un bon sujet pour un futur article. En attendant, consultez le blog de l'équipe Windows PowerShell. L'architecte Jeffrey Snover traite ce sujet.

Même sans approfondir ces fonctionnalités, vous aurez une idée de ce que les fonctions de filtrage peuvent réaliser. J'affinerai cette technique dans de futurs articles, en montrant comment les fonctions peuvent permettent de modulariser le code réutilisable et d'améliorer la fonctionnalité de vos scripts en général.

Don Jones est le gourou des scripts chez SAPIEN Technologies et co-auteur de Windows PowerShell: TFM (SAPIEN Press, 2007). Vous pouvez le contacter sur le site www.ScriptingAnswers.com.

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