Chapitre 316 minDébutant
Cmdlets, objets et pipeline PowerShell
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- trouver une cmdlet et lire son aide ;
- lire les propriétés d'un objet avec
Get-Member; - filtrer, trier, regrouper et mesurer des objets dans le pipeline ;
- appliquer la règle « filtrer à gauche, mettre en forme à droite ».
4 exercices corrigés1 schéma
Une cmdlet est une commande PowerShell de la forme Verbe-Nom, comme Get-Service ou New-ADUser, qui renvoie des objets et non du texte. Le pipeline | transmet ces objets d'une commande à la suivante, qui peut les filtrer, les trier, les regrouper ou les exporter en utilisant leurs propriétés par leur nom. Ce chapitre t'apprend à trouver la bonne commande, à lire un objet et à construire des pipelines qui répondent à une question d'administration en une ligne.
Anatomie d'une cmdlet
Get-Service -Name Spooler, W32Time -DependentServices
Le verbe dit l'action, le nom dit sur quoi elle porte, les paramètres commencent par un tiret. Les verbes sont normalisés : Get-Verb affiche la liste des verbes approuvés.
Dans Windows PowerShell 5.1, Get-Service accepte aussi -ComputerName srv-ad pour interroger un autre serveur ; ce paramètre n'existe plus dans PowerShell 7, où l'on passe par Invoke-Command (chapitre 11).
| Verbe | Action | Exemples |
|---|---|---|
Get | Lire, sans rien modifier | Get-Service, Get-ADUser, Get-ChildItem |
Set | Modifier un objet existant | Set-Service, Set-ADUser, Set-Content |
New | Créer | New-Item, New-ADUser, New-SmbShare |
Remove | Supprimer | Remove-Item, Remove-ADUser |
Add | Ajouter à un ensemble | Add-ADGroupMember, Add-Content |
Start, Stop, Restart | Démarrer, arrêter | Restart-Service, Stop-Computer |
Enable, Disable | Activer, désactiver | Disable-ADAccount, Enable-NetFirewallRule |
Test | Vérifier, renvoie souvent vrai ou faux | Test-Path, Test-Connection, Test-NetConnection |
Import, Export | Lire ou écrire un fichier structuré | Import-Csv, Export-Csv |
Les paramètres ont trois formes :
- nommé :
-Name Spooler; - positionnel :
Get-Service Spooler, le premier argument sans nom va au paramètre-Name; - commutateur (switch), sans valeur :
Get-ChildItem -Recurse,Remove-Item -WhatIf.
Toutes les cmdlets acceptent aussi des paramètres communs : -Verbose (affiche le détail), -ErrorAction (que faire en cas d'erreur, chapitre 7) et, pour celles qui modifient quelque chose, -WhatIf (montre ce qui serait fait sans le faire) et -Confirm (demande une confirmation).
Les alias. ls, dir et gci sont trois alias de Get-ChildItem ; cd de Set-Location, cat de Get-Content, % de ForEach-Object, ? de Where-Object. Ils font gagner du temps au clavier, mais dans un script, écris toujours le nom complet : le script se lit sans connaître les alias, et sous Linux, ls lance le vrai programme ls, pas Get-ChildItem.
Trouver la commande et lire son aide
Tu n'as pas à connaître les milliers de cmdlets par cœur. Trois commandes suffisent pour trouver et comprendre celle qu'il te faut.
Get-Command -Noun *DnsServerResourceRecord* # toutes les commandes sur les enregistrements DNS
Get-Command -Verb Get -Module SmbShare # les commandes de lecture du module des partages
Get-Help Get-ChildItem -Examples # des exemples prêts à adapter
Get-Help Get-ChildItem -Parameter Filter # le détail d'un paramètre
Get-Help about_Comparison_Operators # les rubriques « about_ » expliquent le langage
Update-Help -UICulture en-US # télécharger l'aide complète, en anglais
La touche Tab complète les noms de commandes, de paramètres et même de valeurs (Get-Service -Name Sp puis Tab). Ctrl+Espace affiche toutes les possibilités. Get-Help -Online ouvre la page de documentation dans le navigateur.
Un objet : des propriétés et des méthodes
Une cmdlet Get- renvoie des objets. Chacun a des propriétés (des valeurs : nom, état, taille) et des méthodes (des actions qu'on peut lui demander). Get-Member affiche les deux.
$spouleur = Get-Service -Name Spooler
$spouleur | Get-Member -MemberType Property
Le résultat commence par le type de l'objet, System.ServiceProcess.ServiceController, puis donne une ligne par propriété, avec son type : CanStop (bool), DependentServices (la liste des services qui en dépendent), DisplayName (string), ServiceName (string), StartType (le mode de démarrage), Status (l'état). PowerShell 7 en ajoute quelques-unes, comme Description ou UserName, le compte qui fait tourner le service. On lit une propriété avec un point :
$spouleur.Status # Running
$spouleur.StartType # Automatic
$spouleur.DisplayName # Spouleur d'impression
Ce que PowerShell affiche n'est qu'un résumé de l'objet, choisi par un fichier de format. Get-Service montre trois colonnes, mais chaque service en a bien plus : Select-Object * ou Format-List * les montre toutes.
Le pipeline : des objets d'une commande à l'autre
Le pipeline | envoie chaque objet produit par la commande de gauche à la commande de droite, un par un. Chaque commande du milieu reçoit des objets complets et en renvoie d'autres.
Les six cmdlets de pipeline à connaître :
| Cmdlet | Rôle | Exemple |
|---|---|---|
Where-Object | Garder les objets qui vérifient une condition | Where-Object Status -eq 'Stopped' |
Select-Object | Garder certaines propriétés, ou les premiers objets | Select-Object Name, Status -First 5 |
Sort-Object | Trier | Sort-Object Length -Descending |
Group-Object | Regrouper et compter | Group-Object Department |
Measure-Object | Compter, additionner, moyenne, minimum, maximum | Measure-Object Length -Sum |
ForEach-Object | Faire une action pour chaque objet | ForEach-Object { $_.Name.ToUpper() } |
Dans un bloc de script { … } du pipeline, $_ (ou $PSItem, son nom long) désigne l'objet en cours. Where-Object a deux syntaxes équivalentes :
# Syntaxe complète : un bloc de script, qui accepte plusieurs conditions
Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -eq 'Stopped' }
# Syntaxe simplifiée : une seule condition, sans $_ ni accolades
Get-Service | Where-Object Status -eq 'Stopped'
Un exemple pas à pas
Pour t'exercer sans dépendre de ton poste, crée une petite liste d'objets qui décrit l'espace disque des serveurs de Sorbier. [pscustomobject] fabrique un objet à partir d'une table de propriétés (chapitre 4) :
$disques = @(
[pscustomobject]@{ Serveur = 'srv-ad'; Lecteur = 'C'; TailleGo = 80; LibreGo = 31 }
[pscustomobject]@{ Serveur = 'srv-ad2'; Lecteur = 'C'; TailleGo = 80; LibreGo = 12 }
[pscustomobject]@{ Serveur = 'srv-fichiers'; Lecteur = 'C'; TailleGo = 120; LibreGo = 9 }
[pscustomobject]@{ Serveur = 'srv-fichiers'; Lecteur = 'D'; TailleGo = 2000; LibreGo = 410 }
[pscustomobject]@{ Serveur = 'srv-gestion'; Lecteur = 'C'; TailleGo = 100; LibreGo = 7 }
)
$disques
Serveur Lecteur TailleGo LibreGo
------- ------- -------- -------
srv-ad C 80 31
srv-ad2 C 80 12
srv-fichiers C 120 9
srv-fichiers D 2000 410
srv-gestion C 100 7
Les disques qui ont moins de 15 Go libres, du plus plein au moins plein :
$disques | Where-Object LibreGo -lt 15 | Sort-Object LibreGo | Select-Object Serveur, Lecteur, LibreGo
Serveur Lecteur LibreGo
------- ------- -------
srv-gestion C 7
srv-fichiers C 9
srv-ad2 C 12
Le total d'espace libre, et le nombre de disques par serveur :
($disques | Measure-Object -Property LibreGo -Sum).Sum
$disques | Group-Object Serveur | Select-Object Name, Count
469
Name Count
---- -----
srv-ad 1
srv-ad2 1
srv-fichiers 2
srv-gestion 1
Measure-Object renvoie un objet dont la propriété Sum contient le total : les parenthèses exécutent le pipeline d'abord, puis .Sum lit la propriété du résultat.
Les propriétés calculées
Select-Object peut créer une colonne qui n'existe pas, à partir des autres. On lui donne une table avec un nom (Name) et un bloc de calcul (Expression) :
$disques | Select-Object Serveur, Lecteur,
@{ Name = 'PctLibre'; Expression = { [math]::Round($_.LibreGo / $_.TailleGo * 100) } }
Serveur Lecteur PctLibre
------- ------- --------
srv-ad C 39
srv-ad2 C 15
srv-fichiers C 8
srv-fichiers D 20
srv-gestion C 7
Filtrer à gauche, mettre en forme à droite
Deux règles font la différence entre un pipeline rapide et juste, et un pipeline lent ou faux.
Filtrer le plus tôt possible. Quand la commande de départ sait filtrer elle-même, laisse-la faire : elle ne produit que les objets utiles, au lieu de tout produire pour que Where-Object jette le reste.
# Lent : parcourt tout D:\Partages, puis jette ce qui n'est pas un .log
Get-ChildItem -Path D:\Partages -Recurse | Where-Object Extension -eq '.log'
# Rapide : le système de fichiers ne renvoie que les .log
Get-ChildItem -Path D:\Partages -Recurse -Filter *.log
C'est encore plus vrai pour l'annuaire : Get-ADUser -Filter "Department -eq 'Comptabilité'" demande au contrôleur de domaine les seuls comptes voulus, alors que Get-ADUser -Filter * | Where-Object … rapatrie tout l'annuaire sur le réseau.
Mettre en forme en dernier. Format-Table et Format-List transforment les objets en instructions d'affichage. Après eux, il n'y a plus de propriétés à trier ou à exporter : ils se placent tout à la fin, et jamais avant Export-Csv.
# Faux : le CSV contient des lignes de mise en forme illisibles
Get-Service | Format-Table Name, Status | Export-Csv services.csv
# Juste : Select-Object garde des objets, Export-Csv les écrit
Get-Service | Select-Object Name, Status | Export-Csv services.csv -NoTypeInformation
Afficher ou renvoyer : Write-Output et Write-Host
Write-Output envoie des objets dans le pipeline : ils peuvent être stockés dans une variable, filtrés, exportés. Write-Host écrit directement à l'écran, en couleur si on veut, mais ce texte ne part pas dans le pipeline.
$a = Write-Output "rapport" # $a contient "rapport"
$b = Write-Host "rapport" # « rapport » s'affiche, $b est vide
Dans un script, renvoie les résultats avec Write-Output (ou en écrivant simplement la valeur), et réserve Write-Host aux messages destinés à la personne devant l'écran. Pour les messages de suivi, PowerShell a des flux séparés, que l'on peut rediriger chacun vers un fichier :
| Flux | Numéro | Cmdlet | Affiché par défaut |
|---|---|---|---|
| Succès (les résultats) | 1 | Write-Output | Oui |
| Erreur | 2 | Write-Error | Oui, en rouge |
| Avertissement | 3 | Write-Warning | Oui, en jaune |
| Détail | 4 | Write-Verbose | Non, sauf avec -Verbose |
| Débogage | 5 | Write-Debug | Non, sauf avec -Debug |
| Information | 6 | Write-Information, Write-Host | Oui pour Write-Host |
.\Rapport.ps1 2> erreurs.txt # les erreurs dans un fichier, le reste à l'écran
.\Rapport.ps1 *> tout.txt # tous les flux dans le même fichier
.\Rapport.ps1 | Out-File rapport.txt -Encoding utf8
Exercices
Exercice 1 sur 4 Trouver la commande Réussi À revoir
Sans chercher sur Internet, quelle commande tapes-tu pour :
- lister toutes les commandes qui contiennent « Firewall » dans leur nom ;
- voir des exemples d'utilisation de
Test-NetConnection; - connaître toutes les propriétés des objets renvoyés par
Get-ChildItemsur un fichier ; - savoir de quelle commande
gciest l'alias ?
Voir la réponseMasquer la réponse de l'exercice 1
Get-Command *Firewall*(ouGet-Command -Noun *Firewall*pour chercher dans la partie nom seulement).Get-Help Test-NetConnection -Examples.Get-ChildItem C:\Windows\notepad.exe | Get-Member -MemberType Property, ouGet-Item C:\Windows\notepad.exe | Select-Object *pour voir aussi les valeurs.Get-Alias gci, qui répondgci -> Get-ChildItem.
Tu avais trouvé ?
Exercice 2 sur 4 Écrire le pipeline Réussi À revoir
Avec la variable $disques de ce chapitre, écris le pipeline qui :
- affiche les disques
Cseulement, avec le serveur et l'espace libre ; - affiche le serveur qui a le moins d'espace libre, et seulement lui ;
- calcule l'espace libre total des disques
C; - affiche les disques avec une colonne
OccupeGo(taille moins espace libre), triés parOccupeGodécroissant.
Donne aussi le résultat des questions 2 et 3.
Voir la réponseMasquer la réponse de l'exercice 2
# 1.
$disques | Where-Object Lecteur -eq 'C' | Select-Object Serveur, LibreGo
# 2.
$disques | Sort-Object LibreGo | Select-Object -First 1
# 3.
($disques | Where-Object Lecteur -eq 'C' | Measure-Object -Property LibreGo -Sum).Sum
# 4.
$disques | Select-Object Serveur, Lecteur, @{ Name = 'OccupeGo'; Expression = { $_.TailleGo - $_.LibreGo } } |
Sort-Object OccupeGo -Descending
Pour la question 2, le résultat est le disque C de srv-gestion, avec 7 Go libres. Pour la question 3 : 31 + 12 + 9 + 7 = 59. Le disque D de srv-fichiers (410 Go) est exclu par le filtre. Pour la question 4, l'ordre obtenu est srv-fichiers D (1590), srv-fichiers C (111), srv-gestion (93), srv-ad2 (68), srv-ad (49).
Tu avais trouvé ?
Exercice 3 sur 4 Trouve l'erreur Réussi À revoir
Chacun de ces pipelines ne donne pas le résultat attendu. Explique pourquoi.
Get-Process | Format-Table Name, CPU | Sort-Object CPU -Descending$r = Get-Service | Where-Object Status -eq 'Stopped' | Write-Hostpuis$r.CountGet-ChildItem C:\Scripts | Where-Object { Extension -eq '.ps1' }
Voir la réponseMasquer la réponse de l'exercice 3
Format-Tableest placé avantSort-Object: il produit des instructions d'affichage, qui n'ont plus de propriétéCPU. Le tri ne fonctionne pas et l'affichage est incohérent. Il faut trier d'abord :Get-Process | Sort-Object CPU -Descending | Format-Table Name, CPU.Write-Hostaffiche les services à l'écran mais ne renvoie rien dans le pipeline :$rest vide et$r.Countvaut 0. Il faut supprimer| Write-Hostpour que$rreçoive les objets.- Dans un bloc de script, la propriété s'écrit
$_.Extension. Écrite seule,Extensionest prise pour une commande, qui n'existe pas : erreur « Le terme Extension n'est pas reconnu ». Correction :Where-Object { $_.Extension -eq '.ps1' }, ou la syntaxe simplifiéeWhere-Object Extension -eq '.ps1'. Mieux encore, filtrer à gauche :Get-ChildItem C:\Scripts -Filter *.ps1.
Tu avais trouvé ?
Exercice 4 sur 4 L'arrondi surprise Réussi À revoir
Un technicien calcule le pourcentage d'espace libre avec [math]::Round($_.LibreGo / $_.TailleGo * 100). Pour un disque de 2000 Go avec 410 Go libres, il obtient 20 et s'attendait à 21. Pour un disque de 120 Go avec 9 Go libres, il obtient 8. Explique ces deux résultats et corrige le calcul pour obtenir l'arrondi « scolaire ».
Voir la réponseMasquer la réponse de l'exercice 4
410 / 2000 × 100 = 20,5 et 9 / 120 × 100 = 7,5 : les deux valeurs sont exactement à mi-chemin. [math]::Round applique par défaut l'arrondi au pair le plus proche : 20,5 va vers 20 (pair) et 7,5 vers 8 (pair). Pour arrondir toujours vers le haut à mi-chemin :
[math]::Round($_.LibreGo / $_.TailleGo * 100, [MidpointRounding]::AwayFromZero)
qui donne 21 et 8. Pour un indicateur d'espace disque, l'écart d'un point n'a pas d'importance ; pour un montant ou une note, il en a.
Tu avais trouvé ?