Powershell en 10 min: instructions dans des scripts Powershell (partie 3)

Cet article indique quelques instructions utilisables dans un script Powershell. La liste d’instructions n’est pas exhaustive toutefois elle devrait permettre d’écrire rapidement un script avec les principales instructions.

Quelques instructions courantes utilisables dans les scripts

Ces instructions peuvent être utiles pour implémenter des scripts.

Manipulation de variables

L’affectation de variable se fait en utilisant le caractère $, par exemple:

$var1 = "value 1"
$var2 = $var1

L’affectation de la variable nulle se fait en écrivant:

$var1 = $null

Effectuer des “casts”

On peut effectuer des casts en indiquant le type souhaité avec des crochets [ ]. L’indication du type peut se faire de 2 façons:

# Effectuer un "cast" vers DateTime 
[System.DateTime]$var1="2017-08-25"

# Effectuer un "cast" vers un XmlDocument
$var2 = [System.Xml.XmlDocument]"<xml><node>HERE</node></xml>"
“Cast” implicite

Dans certains cas, powershell effectue implicitement des casts, par exemple si on écrit:

$stringVar = "PowerShell"
$doubleVar = 2.0
$result = $stringVar + $doubleVar

Dans ce cas le cast de $doubleVar en string se fera implicitement.

En revanche, si on écrit:

$result = $doubleVar + $stringVar # Cette ligne occasionne une erreur

Il y aura une erreur car Powershell ne pourra pas convertir $doubleVar en string.

Une solution peut consister à utiliser l’opérateur -as:

$result = ($doubleVar -as [string]) + $stringVar

On peut aussi effectuer le cast explicitement:

$result = [string]$doubleVar + $stringVar

if…then…else

Pour implémenter une clause if...then...else, la syntaxe est semblable au C#:
Par exemple:

$var = 2
if ($var -eq 1)
{ 
   # ... 
}
else
{
   # ... 
}
Pas de elseif

Il n’y a pas de elseif, il faut utiliser des if ... then ... else imbriqués.

Operateurs de comparaison

D’autres opérateurs de comparaison sont disponibles:

  • -eq: comparateur d’égalité
  • -ne: “not equal to”, la valeur est True si les opérandes ne sont pas égales.
  • -ge: “greater than or equal”, la valeur est True si l’opérande de gauche est supérieure ou égale à l’opérande de droite.
  • -gt: “greater than”, la valeur est True si l’opérande de gauche est strictement supérieure à l’opérande de droite.
  • -le: “less than or equal”, la valeur est True si l’opérande de gauche est inférieure ou égale à l’opérande de droite.
  • -lt: “less than”, la valeur est True si l’opérande de gauche est strictement inférieure à l’opérande de droite.
  • -like et -notlike: permet d’effectuer des comparaisons de chaines de caractères en utilisant des wildcards:
    • ? pour désigner un seul caractère non spécifié
    • * pour désigner un ou plusieurs caractères non spécifiés

    Par exemple: $var -like "*uary"

  • -match et -notmatch: permet de vérifier si une chaine de caractères respecte une expression régulière, par exemple: $string -match "\w" (se reporter à Regular Expression Language – Quick Reference pour plus de précisions sur les expressions régulières).
  • -contains et -notcontains: permet de tester si une valeur se trouve dans une liste. Par exemple:
    $names = "val1", "val2", "val3"
    $names -Contains "val2" 
  • -is et -isnot: permet de tester le type d’une variable .NET (même opérateur qu’en C#). Par exemple:
    $stringToTest = "chaine de caracteres"
    ($stringToTest -is [System.String])

Opérateurs logiques

Ces opérateurs permettent de tester plusieurs expressions logiques:

  • -and: opérateur ET
  • -or: opérateur OU
  • -xor: opérateur OU exclusif (retourne True si seulement une expression est vraie)
  • -not: opérateur NOT

Par exemple:

$var1="value1"
$var2="value2"
($var1 -eq "value1") -and ($var2 -eq "value2")

switch

Pour implémenter un switch...case, on utilise la syntaxe suivante:

switch ($var)
{ 
   21 { "value1"; break }
   22 { "value2" }  # pas d'arrêt dans ce cas car pas de break
   23 { "value3"; break }
   default { "default" }
}

On peut executer une liste de type:

switch (1, 2, 3, 0)

Le parcours se fait dans l’ordre des valeurs.

Par défaut la comparaison n’est pas sensible à la casse:

Par exemple:

switch ("AAA")
{
   "aAA" { "OK" }
   "aaA" { "OK" }
   "aaa" { "OK" }
}

Pour que la comparaison soit sensible à la casse, il faut ajouter -casesensitive, par exemple:

switch -casesensitive ("AAA")
{
   # ... 
}

Utilisation de “wildcards”

On peut utiliser des wildcards, par exemple:

switch -wildcard ("AAA")
{
   "AA*" {"OK"}
   "AA?" {"OK"}
   "A??" {"OK"}
}

Définition des listes d’éléments

Arrays

On peut définir des tableaux simples de la façon suivante:

$tab1=@(1, 2, 3, 4, 5, 6)
$tab2=1, 2, 3, 4, 5, 6

L’accès aux éléments du tableau se fait classiquement avec des crochets:

$tab1[3]
# On peut utiliser une variable contenant la valeur d'un index:
$i=2
$tab2[$i]

Listes .NET

On peut utiliser aussi des listes .NET de cette façon:

$dotnetList=New-Object Collections.Generic.List[string]

# Pour ajouter un élément:
$dotnetList.Add("first item") 

# Pour accéder à un élément à un index particulier:
$dotnetList.Item(0)

Table de hashage

Une table de hashage peut se définir de cette façon:

$hashTable= @{
    "key1" = "value1";
    "key2" = "value2";
    "key3" = "value3";
    "key4" = "value4"
}

L’accès à un élément se fait en écrivant: $hashTable["key3"].

Boucles

Boucle “while”

Une boucle while s’implémente de cette façon:

$i = 0
while ($i -le 5)
{
   $i = $i + 1
}

do…while

L’implémentation du do...while se fait de cette façon:

$i = 0
do
{ $i = $i + 1 }     # un équivalent à cette incrémentation est $i++
while ($i -le 5)

do…until

Pour coder une boucle do...until, on peut écrire:

$i = 0
do
{ $i++ }
until ($i -gt 5)

Boucle “for”

La boucle for s’implémente de façon classique:

for ($f = 0; $f -le 5; $f++)
{
   # ... 
}

Boucle “foreach”

De même la boucle foreach est proche de son équivalent en C#:

$array = 1, 2, 3, 4
foreach ($item in $array)
{
   "`$item = $item"
}

On peut utiliser une boucle foreach avec le résultat de cmdlets, par exemple:

Set-Location "<chemin sur le disque>"
foreach ($file in Get-ChildItem)
{
   $file.Name
}

“break” et “continue”

On peut utiliser break comme en C# pour stopper l’exécution d’une boucle:

foreach ($file in Get-ChildItem)
{
   break
}

De même qu’en C#, on peut aussi utiliser “continue” pour passer à l’itération suivante sans exécuter les instructions se trouvant après le continue.

Utilisation de “labels” avec “break” et “continue”

Les labels sont des espèces de “goto” pour indiquer à quel niveau d’une boucle, on souhaite que l’exécution se poursuive. Ces labels permettent d’effectuer des sauts vers une boucle particulière quand on se trouve dans des foreach imbriqués:

Dans l’exemple suivant, le label est outsideloop et break outsideloop permet de stopper de la première boucle foreach:

:outsideloop foreach( ... )
{
   foreach ( ...)
   { 
      break outsideloop
   }
}

L’exécution va donc s’arrêter au niveau de la 1ere boucle.

On peut aussi utiliser des “labels” avec continue:

:outsideloop foreach( ... )
{
   foreach ( ...)
   { 
      continue outsideloop
   }
}

Dans ce cas, l’exécution va continuer au niveau de la première boucle foreach.

Scripts blocks

Un script block est un bloc de code qu’on peut définir au préalable et qui se sera pas exécuté au moment de cette définition. On pourra exécuter ce bloc de code par la suite quand on le désire. L’intérêt des script blocks est de pouvoir définir et exécuter une “mini-fonction” dans le corps même d’une fonction.

Les blocs sont désignés avec des accolades { }.

Pour définir un script block, dans un premier temps on l’affecte à une variable:

$var = { Clear-Host; "Powershell" }

Ensuite dans un 2e temps, pour l’exécuter on peut écrire:

&$var 
# ou directement 
& { .... }

Pour utiliser le résultat de l’exécution d’un script block:

$value = (41 + 1)
1 + (& $value)

Remarque: il faut rejouter des parenthèses et écrire (& $value) pour que le corps du script block soit interprêté.

Dans le cas d’un script block, certains éléments sont consommés et d’autres ne le sont pas.

Par exemple, on écrit:

$value = { 42; Write-Host "Powershell" }

42 n’est pas consommé et il peut être utilisé comme valeur dans une instruction suivante.
En revanche Write-Host "Powershell" est consommé (car affiché au moment de l’exécution).

Si on exécute:

1 + (& $value)

On obtient 43.

De même si on exécute:

$value2 = & $value 

La variable $value2 contient 42.

On peut exécuter un script block avec l’instruction Invoke():

$var = { Clear-Host; "Powershell" }
$var.Invoke()

On peut aussi utiliser la cmdlet Invoke-Command:

$var = { Clear-Host; "Powershell" }
Invoke-Command -ScriptBlock $var

Creation d’un “script block” à partir d’une chaîne de caractères

On peut créer un script block à partir d’une chaîne de caractères en écrivant:

$newScript = [scriptblock]::Create("Get-ChildItem")
# ou
$newScript = [System.Management.Automation.ScriptBlock]::Create("Get-ChildItem")

“return”

En utilisant le mot clé return, on peut stopper l’exécution:

$value = { return 42; Write-Host ... }

Ainsi Write-Host ne sera pas exécuté.

Passage de paramètres à un “script block”

Pour passer des paramètres à un script block, il existe 2 méthodes:

Méthode 1: collection d’arguments

Les arguments sont passés dans une collection, dans l’exemple la collection est $arg:

$qa = { $question = $args[0] 
   $answer = $args[1]
}

Si on exécute l’instruction:

& $qa "value1" "value2"

alors arg[0] est égal à value1 et arg[1] est égal à value2.

Avec l’instruction Invoke() avec des arguments, la syntaxe est:

$qa.Invoke("value1", "value2")

Avec Invoke-Command:

Invoke-Command -ScriptBlock $qa -ArgumentList 'value1','value2'

Méthode 2: utiliser “param”

En utilisant le mot clé param on peut définir une liste d’arguments, par exemple:

$qa = { param( $question, $answer )
  Write-Host $question
  Write-Host $answer
}

A l’exécution, pour passer les paramètres la syntaxe est similaire à la méthode de la collection d’arguments:

& $qa "value1" "value2"

On peut aussi utiliser la syntaxe suivante:

& $qa -question "value1" -answer "value2"

On peut aussi utiliser seulement les initiales des paramètres:

& $qa -q "value1" -a "value2"

La syntaxe avec Invoke-Command est similaire à la méthode précédente:

Invoke-Command -ScriptBlock $qa -ArgumentList 'value1','value2'

Vérifier qu’un argument est manquant

Pour vérifier si un argument est manquant, on peut écrire:

$qa = { param($question, $answer )
   if (!$answer)  # si la variable n'est pas affectée
   {
     # ... 
   }
}

Valeur par défaut

Pour affecter une valeur par défaut à un argument, on peut écrire:

$qa = { param($question, $answer = "default value" )
   # ...
}

Forcer le type des paramètres:

$point = { param([int] $x, [int] $y)
   # ...
}

“script block” et “pipelines”

Il est possible d’utiliser des script blocks avec des pipelines. Toutefois les pipelines se définissent dans ce cas en utilisant des mot clés process, begin et end qui seront utilisés pour désigner des blocs de code à exécuter:

  • process: bloc de code qui constitue le corps du script block dans le pipeline.
  • begin: bloc de code qui sera exécuté avant d’exécuter le corps du script block dans le pipeline (i.e. ce bloc sera exécuté avant celui de process).
  • end: bloc de code qui sera exécuté après d’exécuter le corps du script block dans le pipeline (i.e. ce bloc sera exécuté après celui de process).

Par exemple:

$pipeline = { 
   process { 
      if ($_.Name -like "*.ps1" )
      { 
         return $_.Name 
      }
   }
}

Le mot clé process est utilisé pour indiquer les instructions qui seront exécutées à l’exécution du script block dans le pipeline.

Par exemple pour exécuter le script block définit dans la variable $pipeline dans un pipeline, on peut écrire:

Get-ChildItem | &$pipeline

En utilisant begin et end pour définir des instructions à exécuter avant et après l’exécution le script block dans un pipeline:

$pipeline = { 
   begin { $val = "value1" }
   process { 
      if ($_.Name -like "*.ps1" )
      { 
         return $_.Name 
      }
   }
   end { return $val }
}

Mot clé “param” avec les “script blocks”

On peut aussi utliser le mot clé param pour un script bloc utilisé dans un pipeline:

$pipeline = { 
   param ( $headerText )
   begin { ... }
   process { ... }
   end { ... }
}

Par exemple si on exécute le code suivant:

$pipeline = { 
   param ( $headerText )
   begin { Write-Host "Executé avant process: $headerText" }
   process { Write-Host "Execution de process: $headerText" }
   end { Write-Host "Executé après process: $headerText" }
}

& $pipeline "value1"

Le résultat sera:

Executé avant process: value1
Execution de process: value1
Executé après process: value1

Portée des variables dans un “script block”

Le terme employé dans la documentation pour qualifier la portée des variables est “scope”.

Quelques règles s’appliquent aux variables:

  • Les variables déclarées à l’extérieur du script block sont utilisables dans le script block.
  • Les variables modifiées dans le script block ont une portée limitée au script block. La valeur ne sera pas modifiée à l’extérieur du script block.

Portée des paramètres dans un “script block”

Le paramètre -scope utilisé dans un script block avec les cmdlets Get-Variable et Set-Variable permettent d’indiquer des perimètres de portée. Ainsi suivant le périmètre qu’on a définit, le comportement différe du comportement par défaut.

Par exemple, en utilisant -scope 1 on indique que l’on souhaite avoir la valeur du parent du script block:

$var = 42
& { $var = 33; 
     Write-Host "$var" 
     Write-Host "Parent: " (Get-Variable var -valueOnly -scope 1 )
}

Dans ce cas la valeur de "$var" sera 33 alors que la valeur de "Parent: " (Get-Variable var -valueOnly -scope 1 ) sera Parent: 42

De même avec Set-Variable, -scope 1 permet d’indiquer qu’on souhaite modifier la valeur de la variable au niveau du parent:

$var = 42
Write-Host "Valeur au niveau parent: $var"
& { Set-Variable var 33 -scope 1
    Write-Host "Valeur dans le script block: $var"
}

Write-Host "Valeur après le script block: $var"

Le résultat de cette exécution est:

Valeur au niveau parent: 42
Valeur dans le script block: 33
Valeur après le script block: 33

Utiliser -scope 1 n’est pas trés clair, il est préférable d’utiliser global ou private.

Utiliser “$global” pour modifier une variable de façon globale

Le mot clé $global permet de modifier la valeur d’une variable de façon globale. En reprenant l’exemple précédent:

$var = 42
Write-Host "Valeur au niveau parent: $var"
& { $global:var = 33
write-host "Valeur dans le script block: $var" }

Write-Host "Valeur après le script block: $var"

Le résultat de cette exécution est:

Valeur au niveau parent: 42
Valeur dans le script block: 33
Valeur après le script block: 33

$global: permet d’indiquer qu’on souhaite modifier la valeur du parent. Il s’utilise avec les deux points : et sans le $.

Utiliser “$private” pour modifier une variable de façon locale

Avec le mot clé $private la valeur d’une variable ne sera pas affectée dans le script block, par exemple:

$value = 42
Write-Host "Valeur au niveau parent: $value"
& { $private:value = 21
Write-Host "Valeur dans le script block: $value" }

Write-Host "Valeur après le script block: $value"

Le résultat est:

Valeur au niveau parent: 42
Valeur dans le script block: 21
Valeur après le script block: 42

Dans ce cas la valeur de $value n’est pas modifiée que dans le script block.

Fonctions

Les fonctions sont comme les script blocks sauf qu’elles sont définies de façon plus globale et non à l’intérieur d’une autre fonction.

Pour définir une fonction:

function NomFonction ( $value1, $value2)
{
  # ... 
  Write-Host $value1
  Write-Host $value2
}

Pour appeler la fonction:

NomFonction "val1" "val2"
# ou 
$par1 = "val1"
$par2 = "val2"
NomFonction $par1 $par2

On n’utilise pas de parenthèses pour indiquer les paramètres lors de l’appel, on indique directement les paramètres séparés d’un espace.

Passer des variables par référence

Pour qu’une variable soit passé en argument par référence, on utilise le mot clé [ref]. L’intérêt de passer une variable par référence est de pouvoir modifier sa valeur dans le corps de la fonction et de l’utiliser à l’extérieur de celle-ci.

Par exemple dans le code suivant, la valeur de la variable $par1 sera 33 à l’extérieur du corps de la fonction après exécution de cette fonction:

function NomFonction ([ref] $par1)
{
   $par1.Value = 33
   Write-Host "Valeur modifiée par la fonction: "$par1.Value
}

Si on exécute la fonction précédente de cette façon:

$value = 23
Write-Host "Valeur avant la fonction: $value"
NomFonction([ref] $value)
Write-Host "Valeur après la fonction: $value"

Le résultat est:

Valeur avant la fonction: 23
Valeur modifiée par la fonction: 33
Valeur après la fonction: 33

Il faut ajouter .value car avec [ref] powershell considère que la variable est de type object.

Pour appeler une fonction avec [ref], on écrit:

NomFonction([ref] $var)

Ainsi si on utilise [ref] et si la fonction change la valeur de la variable, la valeur sera modifiée à l’extérieur de la fonction.

Les “pipelines” dans une fonction

La syntaxe est la même que pour les script blocks. Il faut utiliser les mot clés process, begin et end pour désigner des blocs de code à exécuter:

  • process: bloc de code qui sera exécuté dans le pipeline.
  • begin: bloc de code qui sera exécuté avant le bloc process.
  • end: bloc de code qui sera exécuté après le bloc process.

Par exemple si on définit la fonction:

function NomFonction()
{
   begin { Write-Host "Exécuté avant process" }
   process { Write-Host "Exécution de process" }
   end { Write-Host "Exécuté après process" }
}

Et si on exécute la fonction avec:

NomFonction

Le résultat est:

Exécuté avant process
Exécution de process
Exécuté après process

Filtres

Les filtres sont des espèces de fonctions sans paramètres. L’intérêt des filtres est de les utiliser dans des pipelines.

Pour définir un filtre, on utilise le mot clé filter:

filter NomFiltre
{
   # ...
   Write-Host $_.Name
}

$_ permet d’utiliser la valeur d’un élément à une itération du pipeline.

On peut combiner des fonctions et des filtres dans un pipeline.

Par exemple, si on exécute la ligne suivante:

Get-ChildItem | NomFiltre

On obtient seulement le noms des fichiers se trouvant dans le répertoire courant.

Utiliser des “switch”

Les switch permettent d’indiquer des paramètres facultatifs dans une fonction sous la forme de booléen. Quand ces paramètres ne sont pas renseignés, ils n’ont pas de valeur. Les switch ne sont pas des paramètres facultatifs mais seulement des booléens qui peuvent être utilisés pour activer ou désactiver une fonctionnalité.

Par exemple, en utilisant le mot clé switch devant les paramètres $verbose et $debug on peut indiquer que ces paramètres sont des booléens facultatifs:

function NomFonction()
{
   param([switch] $verbose, [switch] $debug)
   if ($verbose.IsPresent)
   {
      Write-Host "Verbose est présent"
   }
   else
   {
      Write-Host "Verbose n'est pas présent"
   }
}

$verbose.IsPresent permet de tester si la variable $verbose a une valeur ou non.

Pour appeler une fonction qui utilise des switch, on peut écrire:

NomFonction -verbose -debug

Dans ce cas là, le résultat est:

Verbose est présent

Si on exécute la ligne suivante:

NomFonction -debug

Le résultat sera:

Verbose n'est pas présent

L’article suivant permet de détailler la syntaxe pour afficher des messages d’aide, de gérer des erreurs dans les fonctions et comporte des exemples pour manipuler des fichiers.

Les autres articles de cette série

Partie 1: exécuter Powershell

Partie 2: les cmdlets

Partie 3: instructions dans des scripts Powershell

Partie 4: aide, gestion d’erreurs et manipulation de fichiers

Références

Leave a Reply