Aller au contenu principal

Fonctions et scripts fiables

Objectifs de la séance

  • Découper un script en fonctions réutilisables, nommées selon la convention Verbe-Nom
  • Comprendre comment une fonction PowerShell renvoie une valeur, et pourquoi ce n'est pas comme en Bash
  • Documenter un script avec l'aide commentée, pour le rendre exploitable par Get-Help
  • Rendre un script robuste : Set-StrictMode, try/catch, -ErrorAction Stop, -WhatIf
  • Découvrir PSScriptAnalyzer, l'équivalent PowerShell de shellcheck
  • Faire le bilan des 6 séances précédentes de PowerShell niveau 1

Notions théoriques

Déclarer une fonction

function Get-InfoDisque {
param(
[string]$Lettre = "C:"
)
# ...
}

Comme en Bash, une fonction doit être définie avant d'être appelée dans le fichier.

La convention Verbe-Nom s'applique aussi à vos fonctions

Vos propres fonctions doivent suivre la même convention que les cmdlets natives : Get-, Set-, New-, Remove-, Test- (pour une fonction qui renvoie un booléen), Write-. Get-Verb liste les verbes approuvés par Microsoft ; un verbe non approuvé déclenche un avertissement lorsque la fonction est chargée depuis un module. Le nom reste toujours au singulier.

astuce

En entreprise, il est courant de préfixer ses fonctions maison d'un trigramme, par exemple Get-SioInfoDisque, pour les distinguer visuellement des cmdlets natives.

Appeler une fonction

Get-InfoDisque -Lettre "D:"
attention

N'écrivez jamais Get-InfoDisque("D:"). Les parenthèses et les virgules ne sont pas la syntaxe d'appel de PowerShell — ce code passe en réalité un tableau à un seul élément comme unique argument positionnel, ce qui produit un résultat inattendu. C'est le piège n°1 pour qui vient de PHP, Java ou C#.

Ce qu'une fonction renvoie

Voici la vraie différence avec Bash. return $x existe bien en PowerShell, mais une fonction renvoie en réalité tout ce qu'elle n'a pas explicitement absorbé, pas seulement la valeur du return. Exemple :

function Get-Total {
$a = 5
Write-Output "Calcul en cours" # ceci part AUSSI dans le résultat de la fonction !
return $a * 2
}

Ici, Get-Total ne renvoie pas seulement 10 : elle renvoie deux valeurs, la chaîne "Calcul en cours" puis 10. Solutions :

  • utiliser Write-Host (qui n'écrit jamais dans le pipeline) pour les messages destinés à l'utilisateur ;
  • ou absorber une sortie parasite avec | Out-Null ou $null = ....
info

En Bash, return ne renvoie qu'un code de retour numérique (0-255) ; pour renvoyer une donnée, il faut l'afficher avec echo et la capturer avec $(...). En PowerShell, une fonction peut directement renvoyer un objet complet — un tableau, un [PSCustomObject], etc.

La portée des variables

Par défaut, une fonction lit les variables déjà définies dans le script, mais toute affectation à l'intérieur de la fonction crée une variable locale à cette fonction. Les préfixes $script: et $global: permettent d'élargir explicitement la portée si nécessaire.

astuce

Préférez toujours faire transiter les données par des paramètres en entrée et un résultat en sortie, plutôt que de faire dépendre une fonction de variables globales : c'est plus lisible et plus facile à tester isolément.

L'aide commentée

L'équivalent PowerShell du usage() de Bash, en beaucoup plus puissant :

<#
.SYNOPSIS
Génère un inventaire du poste Windows.
.DESCRIPTION
Collecte les informations disques, processus et services, puis les exporte.
.PARAMETER Chemin
Dossier de destination du rapport.
.EXAMPLE
.\Get-InfosSysteme.ps1 -Chemin "C:\Rapports"
#>

Placé au-dessus d'une fonction ou en tête d'un script, ce bloc rend Get-Help .\Get-InfosSysteme.ps1 -Full immédiatement utilisable, exactement comme pour une cmdlet native.

Rendre le script strict

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
  • Set-StrictMode -Version Latest interdit l'usage d'une variable non définie — le cousin PowerShell du set -u de Bash.
  • $ErrorActionPreference = "Stop" et -ErrorAction Stop transforment une erreur en erreur bloquante — le cousin du set -e de Bash.
try {
Get-Service -Name "ServiceInexistant" -ErrorAction Stop
} catch {
Write-Host "Erreur : $($_.Exception.Message)" -ForegroundColor Red
} finally {
Write-Host "Traitement terminé"
}
attention

Par défaut, une erreur PowerShell est non bloquante : le script continue son exécution après l'avoir affichée. C'est le contraire de l'intuition qu'on peut avoir venant de Bash — pensez toujours à -ErrorAction Stop si vous voulez qu'un try/catch se déclenche réellement.

Filet de sécurité sur les actions destructrices

De nombreuses cmdlets qui modifient ou suppriment quelque chose supportent nativement -WhatIf et -Confirm :

Remove-Item "C:\Temp\*.tmp" -WhatIf
astuce

Tester une commande avec -WhatIf avant son exécution réelle est un réflexe qui évite bien des catastrophes : la commande affiche ce qu'elle aurait fait, sans rien modifier.

PSScriptAnalyzer

Install-Module -Name PSScriptAnalyzer -Scope CurrentUser
Invoke-ScriptAnalyzer -Path .\Get-InfosSysteme.ps1

PSScriptAnalyzer relit votre script et signale les alias utilisés, les verbes non approuvés, les mots de passe en clair, les paramètres inutilisés — le parallèle direct avec shellcheck en Bash.

Sécurité et lisibilité

  • Jamais de mot de passe en clair dans un script — Get-Credential et SecureString (rappel de la séance précédente).
  • Pas d'alias (ls, %, ?) dans un script destiné à être maintenu.
  • Indentation à 4 espaces, une action par ligne, commenter le pourquoi d'un choix plutôt que reformuler ce que fait déjà la ligne.
  • Encodage UTF-8 (avec BOM pour Windows PowerShell 5.1) pour que les accents s'affichent correctement.
  • #Requires -Version 5.1 en tête de script pour documenter la version minimale attendue.
Et en Bash ?
BashPowerShell
nom() { ... }, local, return = code numériquefunction Verbe-Nom { param(...) }, return = valeur, portée locale par défaut
set -euSet-StrictMode -Version Latest + $ErrorActionPreference = "Stop"
shellcheckInvoke-ScriptAnalyzer (module PSScriptAnalyzer)
echo "Usage : $0 <dossier>"Aide commentée <# .SYNOPSIS ... #> + Get-Help

Exemple pratique

Version « brouillon », sans aucune bonne pratique :

$c = "C:\Rapports"
if (!(dir $c)) { md $c }
$f = "$c\rapport.txt"
"ok" > $f

Version corrigée, structurée en fonctions :

<#
.SYNOPSIS
Génère un inventaire système et l'écrit dans un fichier.
.PARAMETER Chemin
Dossier de destination du rapport.
.EXAMPLE
.\Get-InfosSysteme.ps1 -Chemin "C:\Rapports"
#>
param(
[string]$Chemin = "C:\Rapports"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Test-DossierSortie {
param([string]$Dossier)

if (-not (Test-Path -Path $Dossier -PathType Container)) {
New-Item -Path $Dossier -ItemType Directory -Force | Out-Null
}
}

function Write-Journal {
param([string]$Message)
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $Message"
}

function Get-InfoDisque {
param([string]$Lettre = "C:")

$disque = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$Lettre'"
return [PSCustomObject]@{
Lettre = $Lettre
LibreGo = [math]::Round($disque.FreeSpace / 1GB, 1)
}
}

# --- Programme principal ---
try {
Test-DossierSortie -Dossier $Chemin
Write-Journal "Dossier de sortie prêt : $Chemin"

$info = Get-InfoDisque -Lettre "C:"
Write-Journal "Espace libre sur C: : $($info.LibreGo) Go"

$info | Export-Csv -Path (Join-Path $Chemin "disque.csv") -NoTypeInformation -Encoding UTF8
Write-Journal "Rapport généré avec succès"
}
catch {
Write-Host "Erreur : $($_.Exception.Message)" -ForegroundColor Red
exit 1
}

Test de mémorisation/compréhension


Comment appelle-t-on correctement la fonction Get-InfoDisque avec le paramètre Lettre valant D: ?


Que renvoie réellement une fonction PowerShell ?


Quel est l'équivalent PowerShell de shellcheck ?


Que fait le paramètre -WhatIf sur Remove-Item ?


Pourquoi ajouter Set-StrictMode -Version Latest en tête de script ?


Quel bloc de commentaires rend Get-Help .\script.ps1 utilisable ?


TP pour réfléchir et résoudre des problèmes

Un Get-InfosSysteme.ps1 volontairement bâclé vous est fourni ci-dessous : alias partout, aucune vérification, chemin codé en dur.

$c = "C:\Rapports"
if (!(dir $c)) { md $c }
"ok" > "$c\rapport.txt"

Étape 1 — Extraire la vérification du dossier dans une fonction


Bonne pratique - Le verbe Test- pour un résultat booléen

Le verbe Test- annonce, par convention, une fonction qui vérifie une condition plutôt qu'elle ne modifie quelque chose — même si, ici, elle agit aussi en créant le dossier au passage.

Étape 2 — Écrire Write-Journal pour horodater les messages


Bonne pratique - Centraliser l'affichage

En passant systématiquement par Write-Journal plutôt que par des Write-Host disséminés, vous pourrez plus tard rediriger tous les messages vers un fichier de log en modifiant une seule fonction.

Étape 3 — Protéger l'écriture du rapport par un try/catch


Bonne pratique - -ErrorAction Stop pour activer le catch

Une erreur PowerShell est non bloquante par défaut : sans -ErrorAction Stop sur la commande qui peut échouer, le bloc catch ne se déclencherait jamais.

Étape 4 — Passer le script à PSScriptAnalyzer


Bonne pratique - Un réflexe systématique

Faites de Invoke-ScriptAnalyzer une étape systématique avant de considérer un script terminé, exactement comme shellcheck pour un script Bash.

📌 Une solution

Ce qu'il faut retenir

NotionRésumé
Premier scriptFichier .ps1, Set-ExecutionPolicy RemoteSigned -Scope CurrentUser, lancement par .\script.ps1
CmdletsConvention Verbe-Nom, découverte par Get-Command, Get-Help, Get-Member
Variables$nom = "valeur" (espaces autorisés), typage .NET, "$($obj.Propriete)", tableaux @(...)
PipelineTransporte des objets, pas du texte : Where-Object, Select-Object, Sort-Object, Export-Csv
Entréesparam() en première instruction, paramètres nommés et typés, Read-Host
Conditionsif (...) { }, opérateurs -eq, -lt, -like, Test-Path, $null à gauche
Bouclesforeach, ForEach-Object, for, while
Fonctionsfunction Verbe-Nom { param(...) }, appel -Parametre valeur, tout ce qui sort est renvoyé
FiabilitéSet-StrictMode, try/catch, -WhatIf, aide commentée, Invoke-ScriptAnalyzer

Aperçu de la prochaine séance

Vous savez désormais écrire un script PowerShell complet, fiable et documenté : politique d'exécution, variables, pipeline d'objets, paramètres, conditions, boucles, fonctions et bonnes pratiques. La séance suivante, PowerShell niveau 2, ira plus loin en orientant ces bases vers l'administration Windows avancée : la gestion des utilisateurs et des groupes, l'exécution de commandes à distance sur plusieurs machines avec PowerShell Remoting, l'administration d'Active Directory, la gestion d'erreurs avec try/catch, les modules réutilisables, et la génération de rapports planifiés avec le Planificateur de tâches. Vous retrouverez aussi, côté Linux, un pendant à cette recherche de convivialité en ligne de commande dans le cours 50_InterfacesConviviales (dialog/whiptail).