L’utilisation du pipe en Powershell

Loin de moi l’idée de vouloir dispenser des cours de Powershell, mais il y a certains concepts qui peuvent être déroutants lorsque l’on débute Powershell où que l’on a appris « sur le tas ». Parmi ces concepts, celui du pipe (ou pipeline), que l’on retrouve dans d’autres environnements comme Bash.

Ce caractère | est très utile en Powershell car il permet de passer plusieurs instructions en une seule commande, permettant au code de gagner en lisibilité et bien souvent de limiter le temps de traitement et l’occupation en mémoire.

Prenons un exemple simple : je veux afficher une liste des adresses mail pour les utilisateurs de mon AD.

Sans utiliser de pipe, je vais devoir faire un code comme ceci :

$users = Get-AdUser -filter * -Properties mail
 foreach($user in $users) { echo $user.mail }

Le moteur Powershell va donc dans un premier temps effectuer une requête dans l’AD, stocker les résultats dans une variable, puis ensuite parser cette variable pour afficher les adresses mail. En utilisant un pipe, tout va tenir sur une ligne et je n’aurai pas à occuper de la mémoire en mobilisant une variable :

Get-AdUser -filter * -Properties mail | select mail

Le pipe, comme son nom l’indique, permet d’agir comme un tuyau. Dans cet exemple, pour chaque utilisateur que Get-ADUser renvoie, on affiche son adresse mail. Le moteur Powershell va donc traiter de multiples instructions en un seul appel.

Powershell offre nativement des possibilités de filtre ou de traitement avec le pipe. Dans cet exemple, je veux afficher les noms de fichiers commençant par la lettre W dans C:\Windows. Sans utiliser de pipe, je devrais exécuter un script comme ceci :

$items = Get-Item -Path C:\Windows\*
 foreach($item in $items){
     if($item.Name -like "w*") { echo $item }
 }

En utilisant un pipeline, je peux tout passer en une seule ligne, grâce à Where-Object :

Get-Item C:\windows\* | where-object { $_.Name -like "w*" }

Ici, Powershell va filtrer dès l’exécution de Get-Item au lieu de stocker dans une variable et de la parser ensuite. Get-Item retourne un objet par élément présent dans le dossier. Le pipeline where-object insère une condition pour le retour de la commande Get-Item ; dans cet exemple la condition est que le nom de l’objet retourné commence par la lettre W. PS va donc vérifier chaque objet au fur et à mesure qu’il est retourné par Get-Item et ne le considérer que si il remplit la condition posée dans Where-Object. Dans un pipeline, l’objet actuellement traité est désigné par la variable $_ ; on peut donc accéder aux propriétés, qui ici sont entre autres Mode, LastWriteTime, Length et Name.

Je souhaite désormais afficher les informations de plusieurs comptes AD en même temps à partir de leur nom de compte. Je vais donc déclarer ma liste d’utilisateurs :

$users = @("mscott","dschrute","jhalpert")

Je peux procéder de cette manière dans un script :

foreach($user in $users){
     Get-AdUser $user
}

Mais cela peut tenir sur une simple ligne en utilisant le pipeline ForEach-Object :

$users | foreach-object { Get-AdUser $_ }

Comme pour Where-Object, PS va parcourir le contenu de la variable $users et exécuter la commande incluse pour chaque objet qui sera retourné par $users.

Un autre exemple pratique pour Foreach-Object : ce script va lister les contrôleurs de domaine et exécuter un ping sur chacun d’entre eux.

$domctl = Get-ADDomainController -filter *
 foreach ($dc in $domctl){
     ping $dc.Name
 }

Ce code peut se résumer en une simple instruction :

Get-ADDomainController -Filter * | foreach-object { ping $_.Name }

Il est possible d’utiliser de multiples pipelines en une seule instruction. En reprenant l’exemple avec mes trois utilisateurs, je veux afficher ceux dont l’identifiant comme par la lettre M.

foreach($user in $users){
     if($user -like "m*") { Get-AdUser $user }
 }

Je peux éviter cette double boucle en utilisant deux pipelines comme ceci :

$users | where-object { $_ -like "m*" } | foreach-object { Get-AdUser $_ }

Dans un premier temps, PS va traiter la condition du Where-Object, et si celle-ci est validée, alors l’objet va passer dans le pipe du Foreach-Object. Tout objet qui ne commence pas par la lettre M ne passe pas dans le deuxième pipeline.

D’autres usages plus courants du pipe comprennent les commandes Format-List ( | fl ) ou Format-Table ( | ft ). Le premier permet d’afficher toutes les données retournées par une commande sous forme de liste. Des captures d’écran valent mieux qu’un long discours :

Get-Item C:\Windows\*
Get-Item C:\Windows\* | fl

Format-List nous permet d’obtenir bien plus d’informations que dans le retour de base de la commande Get-Item. Format-Table, quant à lui, permet de sélectionner ce que l’on souhaite afficher, sous forme de tableau.

Get-Item C:\Windows\* | ft Name,CreationTime,LastWriteTime

Les pipelines remplissent toujours le même rôle : exécuter une instruction supplémentaire pour chaque objet retourné par l’instruction précédant le pipe.