PowerShell et les boucles ForEach

I. Présentation

Dans la continuité de mon article précédent sur les boucles For en PowerShell, je trouvais intéressant d'en proposer un second sur les boucles ForEach car elles sont vraiment indispensables. C'est le type de boucle que j'utilise le plus dans mes scripts notamment lorsqu'il y a besoin de manipuler une collection de données. L'avantage c'est que la boucle ForEach va automatiquement traiter toutes les lignes de notre collection, il n'y a pas besoin de connaître à l'avance le nombre qu'il y en a, comme c'est le cas avec une boucle For.

Pratique et simple d'apparence, elle peut s'utiliser de différentes façons avec, à chaque fois, les spécificités qui vont avec. L'utilisation est possible directement dans la ligne de commande au sein d'une console PowerShell, mais aussi dans un script avec une syntaxe adaptée. Nous allons voir ensemble les boucles ForEach afin qu'elles n'aient plus de secret pour vous !

Retrouvez cet article au format vidéo également :

II. ForEach - Syntaxe n°1

Dans un script, une boucle Foreach se déclare de façon relativement simple et classique pour du PowerShell. La syntaxe d'une boucle ForEach est la suivante :

Foreach(<élément> in <collection>

   # bloc d'instructions / traitement
}

La collection correspond à notre ensemble de valeurs, représenté par une variable. Il peut s'agir du contenu d'un fichier CSV, de la liste des processus en cours d'exécution sur votre PC, du résultat du recherche d'utilisateurs dans l'Active Directory, etc... Tout ce qui permet de récupérer un ensemble de valeurs.

L'élément correspond à une variable qui va prendre pour valeur chaque objet à traiter de la collection de données. Par exemple, au premier tour de boucle, l'élément sera notre première ligne du fichier CSV, au second tour de boucle, ce sera la deuxième, etc... jusqu'à arriver à la fin du fichier. Cette variable est valide uniquement au sein de la boucle ForEach pour le traitement interne de la boucle.

Le bloc d'instructions correspond aux actions à réaliser à chaque tour de boucle sur l'objet en cours de traitement (variable de l'élément). Par exemple, si l'on utilise un fichier CSV qui contient une liste de nom et prénom, on peut imaginer que l'on va vouloir créer un utilisateur dans l'Active Directory pour chaque ligne du CSV.

Prenons un exemple : nous allons récupérer la liste des services sur notre PC (Get-Service) que l'on va stocker dans la variable $collection, et afficher l'état du service, à savoir s'il est démarré ou arrêté, avec une phrase personnalisée.

$collection = Get-Service

Foreach($element in $collection) 
{ 
   "$($element.Name) dans l'état : $($element.Status) ($($element.StartType))" 
}

J'ai utilisé les noms $element et $collection pour mes variables, mais vous pouvez utiliser d'autres noms... Le résultat contiendra le nom du service, son état actuel et son type de démarrage (manuel ou automatique).

wscsvc dans l'état : Running (Automatic)
WSearch dans l'état : Running (Automatic)
wuauserv dans l'état : Running (Manual)
WwanSvc dans l'état : Stopped (Manual)
XblAuthManager dans l'état : Stopped (Manual)
XblGameSave dans l'état : Stopped (Manual)
XboxGipSvc dans l'état : Stopped (Manual)
XboxNetApiSvc dans l'état : Stopped (Manual)

Nous aurions pu faire la même chose avec cette syntaxe :

Foreach($element in Get-Service) 
{ 
   "$($element.Name) dans l'état : $($element.Status) ($($element.StartType))" 
}

A chaque itération de la boucle ForEach, la variable $element va prendre un nouvel objet de notre collection $collection, à savoir un nouveau service. A chaque fois, il est possible d'accéder aux propriétés de l'objet et à ses méthodes. Ainsi, nous avons pu facilement récupérer le nom du service via $element.Name, ainsi que son état avec $element.Status et son type de démarrage avec $element.StartType.

Cette syntaxe est très intéressante et performante puisqu'elle permet de réaliser un ensemble d'actions sur une collection complète de données chargée au préalable dans une variable. Attention à la consommation de mémoire sur votre PC puisque l'on va charger l'intégralité de la collection en mémoire avant traitement.

III. ForEach - Syntaxe n°2

Une autre façon d'utiliser une boucle ForEach plutôt que dans un bloc dans un script, c'est directement de passer la collection d'objets à traiter à la boucle ForEach au travers d'un pipeline. Dans ce cas, la boucle ne s'appelle plus ForEach mais Foreach-Object bien que le mot clé "ForEach" fonctionne toujours, la différence est à signaler.

Pour que ce soit plus simple pour vous de comprendre la différence au niveau de la syntaxe, reprenons l'exemple précédent. Si l'on s'appuie sur une boucle Foreach-Object, et que l'on veut reproduire exactement le même résultat que précédemment, la syntaxe est la suivante :

Get-Service | Foreach-Object { "$($_.Name) dans l'état : $($_.Status) ($($_.StartType))" }

Cette syntaxe vu qu'elle tient sur une seule ligne et parfaitement adaptée à la console PowerShell. La commande Get-Service va envoyer toute sa collection d'objets à la boucle Foreach-Object qui va traiter chaque objet.

Avec cette syntaxe, nous laissons tomber notre variable $element puisque l'on peut récupérer directement l'objet en cours avec l'appel de la variable automatique : $_

Bon à savoir : au niveau de la consommation mémoire, l'impact est plus faible qu'avec la méthode précédente car on traite les objets au fur et à mesure, on ne stocke pas toute la collection d'objets dans une variable avant de la traiter. Néanmoins, cette méthode est moins efficace d'un point de vue des performances.

Enfin, il faut savoir qu'à l'instar de la commande Where-Object, la commande Foreach-Object propose elle aussi une syntaxe simplifiée. Si l'on souhaite afficher seulement un champ, avec la syntaxe complète on doit écrire :

Get-Service | Foreach-Object { $_.Name }

Avec la syntaxe simplifiée (qui limite les possibilités), cela donne :

Get-Service | Foreach Name

Vous avez désormais connaissance des deux syntaxes possibles pour une boucle ForEach en PowerShell, avec les avantages et inconvénients de chaque méthode. Si vous souhaitez calculer les différences au niveau des performances, vous pouvez vous amuser avec la commande Measure-Command ?

Si vous avez des questions ou une remarque, pensez à laisser un commentaire sur cet article.

Partagez cet article Partager sur Twitter Partager sur Facebook Partager sur Linkedin Partager sur Google+ Envoyer par mail

Florian BURNEL

Ingénieur système et réseau, cofondateur d'IT-Connect et Microsoft MVP "Cloud and Datacenter Management". Je souhaite partager mon expérience et mes découvertes au travers de mes articles. Généraliste avec une attirance particulière pour les solutions Microsoft et le scripting. Bonne lecture.

Nombre de posts de cet auteur : 5501.Voir tous les posts

4 thoughts on “PowerShell et les boucles ForEach

  • Bonjour,

    Je me suis basée sur ce tutoriel pour construire une boucle Foreach me permettant de retrouver le SamAccountName de 200 utilisateurs.
    Import d’un fichier CSV pour la collection, du type :
    Name
    « Prenom NOM »

    Voici ma requête :

    $collection = Import-Csv -Path « C:\Temp\Liste_Users_PTZ.csv » |

    ForEach ($element in $collection)
    {
    Get-ADUser -Filter {Name -eq « ‘$($_.Name)' »} -Properties SamAccountName |
    Select SamAccountName
    } |

    Export-Csv -Path « C:\Temp\Out.csv »

    J’obtiens ce message d’erreur :

    Au caractère Ligne:3 : 19
    + ForEach ($element in $collection)
    + ~~
    Jeton inattendu « in » dans l’expression ou l’instruction.
    Au caractère Ligne:3 : 18
    + ForEach ($element in $collection)
    + ~
    Parenthèse fermante « ) » manquante dans l’expression.
    Au caractère Ligne:3 : 33
    + ForEach ($element in $collection)
    + ~
    Jeton inattendu « ) » dans l’expression ou l’instruction.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
    ception
    + FullyQualifiedErrorId : UnexpectedToken*

    Pouvez-vous m’aider s’il vous plait ?

    Bien à vous.

    Répondre
    • Bonjour Sindy,

      Après lecture du code, j’ai quelques remarques pour apporter des corrections :
      1 – « $collection = Import-Csv -Path « C:\Temp\Liste_Users_PTZ.csv » | » -> supprimer le « | » à la fin de la ligne (puisque vous stockez les valeurs dans $collection)
      2 – Dans la boucle Foreach, puisque vous faites « Foreach($element in $collection) », pour appeler la valeur dans la boucle vous ne devez pas utiliser « $_.Name » mais « $element.Name »

      En fait, vous avez mélangé les deux façons d’écrire une boucle Foreach.

      Restant disponible
      Florian

      Répondre
  • Bonjour Florian,

    je suis IT Tech et je débute sous Powershell au vu des impératifs de service et de ma volonté d’évoluer.

    Voici le problème, nous souhaiterions déployer un script via SCCM pour « pousser » la réinstallation d’une imprimante réseau suite à la disparition de celle-ci (dû au update windows).

    Dans l’absolu j’ai commencé un script mais, SCCM poussant l’install en admin l’imprimante ne s’installe pas pour le compte de l’utilisateur local. Mon idée a donc été de sortir le nom de l’utilisateur local dans une variable et faire un petit foreach de cette variable qui permettrait l’install. (J’espère avoir été clair). Ensuite le script check si la printer n’est plus là, puis dans l’affirmative l’installe et enfin la passe en défaut pour le HKCU

    Voici le script:

    $name=Get-CimInstance -ClassName Win32_ComputerSystem -Property UserName
    Foreach ($element in $name)
    {
    $checkPrinterExists = Get-Printer -Name « SafeQ – Follow Me » -ErrorAction SilentlyContinue
    if (-not $checkPrinterExists) {
    Add-Printer -ConnectionName « \\path_server\Nom Imprimante »
    Set-ItemProperty -Path « HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows » -Name « SafeQ – Follow Me » -Value 1 –Force
    }
    }

    Aurais-tu une idée?

    Cordialement

    Répondre
  • Pour info « Foreach » peut aussi être remplacé par « % ».

    Exemple :
    @(Get-Item C:\Windows\Temp\*).FullName | ForEach-Object {Test-Path $_}
    est la même chose que :
    @(Get-Item C:\Windows\Temp\*).FullName | % {Test-Path $_}

    Répondre

Répondre à Romgab94 Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.