## Intro

PowerShell excelle dans le travail de liaison, l'automatisation système et la mise en forme des données, mais les performances varient largement selon la façon dont vous composez les commandes et structurez les pipelines. Quelques modifications ciblées réduisent souvent le temps d'exécution, l'utilisation de la mémoire et la latence sans sacrifier la lisibilité. Ce guide se concentre sur des étapes sûres et mesurables que vous pouvez appliquer à vos scripts :

- Inventaire des versions et des ressources de l'hôte

- Identification des goulets d'étranglement de latence et de débit

- Application de techniques d'optimisation pratiques

- Vérification des améliorations par des contrôles observables

- Gestion des modes de défaillance et retour arrière propre

- Exploitation d'une liste de contrôle reproductible pour l'optimisation continue

L'objectif est de créer un pilote étroit et mesurable, de l'améliorer localement, puis de déployer les changements en toute confiance.

## Inventaire de la version et de l'environnement

Avant d'optimiser, capturez les détails de l'environnement. De nombreux problèmes remontent au runtime et aux contraintes de ressources.

- Enregistrer les informations PowerShell et hôte

$PSVersionTable
Get-Host | Select-Object Name, Version
[Environment]::Is64BitProcess
[Environment]::ProcessorCount
Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize, FreePhysicalMemory
Get-Process -Id $PID | Select-Object Id, ProcessName, WS, PM, CPU 

- Identifier l'impact des modules et des chemins (le chargement automatique des modules peut ajouter un coût au démarrage)

 $env:PSModulePath -split ';'
Get-Module -ListAvailable | Sort-Object Version -Descending | Select-Object -First 10 Name, Version 

- Enregistrer la topologie du script

- Local uniquement vs appels distants (Invoke-Command, REST, base de données)

- Tailles des fichiers et répertoires à scanner

- Concurrence attendue (mono-thread vs fan-out)

 Cet inventaire ancre les lignes de base, rend les résultats comparables et clarifie s'il faut optimiser le script, l'hôte ou une dépendance externe.

### Triage rapide des goulets d'étranglement

- Le script est lent dès la première ligne → Cause probable : chargement automatique de module ou surcharge du profil. Vérification : Measure-Command { pwsh -NoProfile -File .\script.ps1 }

- Lent sur de gros volumes → Cause probable : surcoût par objet dans le pipeline. Vérification : comparer foreach vs ForEach-Object avec Measure-Command

- Lent sur les appels réseau → Cause probable : latence distante ou DNS. Vérification : Measure-Command { Invoke-RestMethod ... } et Test-NetConnection

- Mémoire élevée, puis pauses GC → Cause probable : pattern « tout charger en mémoire ». Vérification : flux avec Get-Content -ReadCount ou [System.IO.File]::ReadLines

## Chemin de configuration sûr

Un chemin sûr évite les changements risqués et garde le retour arrière simple.

- Figer un petit pilote représentatif

- Choisir un seul script ou fonction et un jeu de test modeste.

- Définir une seule métrique de succès (par exemple, réduire le temps écoulé de 30 % sur un exemple construit).

- Capturer une ligne de base propre

$sw = [System.Diagnostics.Stopwatch]::StartNew()
& .\pilot.ps1 -InputPath .\sample-input.json -OutPath .\out.json
$sw.Stop(); "Baseline: {0:n2}s" -f $sw.Elapsed.TotalSeconds 

- Appliquer un changement ciblé à la fois

- Modifier un seul point chaud, relancer, enregistrer les résultats.

- Conserver le script original inchangé dans un chemin distinct pour diff et retour arrière faciles.

- Vérifier la parité fonctionnelle

 $expected = Get-Content .\expected.json -Raw
$actual = Get-Content .\out.json -Raw
if ($expected -ne $actual) { throw 'Output mismatch; aborting change.' } 

- Valider l'amélioration seulement après des gains répétables sur plusieurs exécutions.

## Optimisation du débit : réduire le surcoût par objet

 Le débit est dominé par le nombre d'objets que vous poussez dans le pipeline et le nombre de transformations. Concentrez-vous sur la minimisation du travail par élément et l'évitement des allocations inutiles.

### Privilégier foreach (mot-clé) pour les boucles serrées

Pour de grandes collections, le mot‑clé foreach surpasse souvent ForEach-Object car il évite le pipeline.

# Ligne de base : pipeline
Measure-Command {
 1..50000 | ForEach-Object { [math]::Sqrt($_) > $null }
}

# Optimisé : boucle en mémoire
Measure-Command {
 foreach ($n in 1..50000) { [math]::Sqrt($n) > $null }
} 
 Résultat attendu : la variante foreach doit s'exécuter plus vite sur de grandes itérations car elle évite le surcoût du pipeline. Vérifiez en comparant TotalMilliseconds des deux mesures.

### Filtrer tôt, projeter tard

Demandez aux cmdlets ou fournisseurs de filtrer avant d'envoyer vers PowerShell lorsque c'est possible.

# Moins efficace : filtre après énumération
Get-ChildItem C:\Logs -Recurse | Where-Object { $_.Extension -eq '.log' }

# Plus efficace : laisser le fournisseur pré-filtrer
Get-ChildItem C:\Logs -Filter *.log -File -Recurse -ErrorAction SilentlyContinue 
 Résultat attendu : moins d'objets traversent le pipeline, réduisant CPU et mémoire. Vérifiez en comptant les éléments et en chronométrant les deux variantes :

Measure-Command { Get-ChildItem C:\Logs -Recurse | Where-Object Extension -eq '.log' | Out-Null }
Measure-Command { Get-ChildItem C:\Logs -Filter *.log -File -Recurse | Out-Null } 

### Flux de fichiers volumineux au lieu de tout charger d'un coup

 # Streaming avec .NET pour réduire la mémoire
$path = '.\large.txt'
foreach ($line in [System.IO.File]::ReadLines($path)) {
 # traiter la ligne
} 
 Ou pour un streaming par blocs PowerShell :

Get-Content .\large.txt -ReadCount 1000 | ForEach-Object {
 foreach ($line in $_) { # traiter la ligne }
} 
 Résultat attendu : le working set doit être plus bas et le traitement plus fluide. Vérifiez avec :

Get-Process -Id $PID | Select-Object Id, WS, PM 

### Éviter Select-Object * et les propriétés inutiles

 Sélectionner toutes les propriétés matérialise des données supplémentaires. Spécifiez seulement ce dont vous avez besoin.

# Moins efficace
Get-Process | Select-Object * | Where-Object WS -gt 200MB

# Meilleur
Get-Process | Select-Object Id, ProcessName, WS | Where-Object WS -gt 200MB 

### Réutiliser des objets pour l'accumulation

 Pour une accumulation lourde, envisagez une liste typée afin de réduire les réallocations (exemple construit) :

# Exemple construit pour de gros ajouts
$list = [System.Collections.Generic.List[psobject]]::new()
foreach ($item in 1..200000) {
 $null = $list.Add([pscustomobject]@{ N = $item })
} 
 Vérifiez l'utilisation mémoire avant et après le remplacement d'un tableau PowerShell non borné par une liste typée.

## Contrôles de latence : mesurer ce que vous ne contrôlez pas

La latence provient généralement de l'extérieur de votre script. Mesurez-la et adaptez votre stratégie.

### Réseau et DNS

# Connectivité TCP et timing basique
Test-NetConnection -ComputerName api.example.test -Port 443

# Timing approximatif de la résolution DNS
Measure-Command { Resolve-DnsName api.example.test | Out-Null } 
 Répétez ces vérifications lorsque le temps d'exécution global augmente pour confirmer si la cause est externe.

### REST et commandes distantes

# Chronométrer l'appel distant lui-même
Measure-Command {
 Invoke-RestMethod -Uri 'https://api.example.test/status' -Method Get | Out-Null
}

# Fan-out sécurisé avec limitation (PowerShell 7+)
$uris = 1..20 | ForEach-Object { "https://api.example.test/item/$_" }
Measure-Command {
 $results = $uris | ForEach-Object -Parallel {
 Invoke-RestMethod -Uri $_ -TimeoutSec 15
 } -ThrottleLimit 5
} 
 Résultat attendu : le débit s'améliore avec un parallélisme modéré, mais plafonne ou régresse si le point de terminaison distant est saturé. Ajustez ThrottleLimit selon la latence et les taux d'erreur observés.

## Réduire les surcoûts évitables

- Désactiver les barres de progression bavardes pendant les opérations en masse, puis restaurer la valeur précédente :

$old = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
try {
 # travail en masse ici
}
finally {
 $ProgressPreference = $old
} 

- Supprimer explicitement la sortie inutilisée pour éviter le surcoût de formatage :

 $null = Some-Command -Param X 

- Minimiser Write-Verbose et Write-Debug dans les boucles serrées. Journalisez des résumés au lieu de messages par élément.

- Préférer -ErrorAction Stop avec try/catch pour échouer rapidement au lieu de continuer avec un état partiel.

## Assemblage : un flux de travail d'optimisation minimal

- Ligne de base avec chronométrage

 $base = Measure-Command { & .\pilot.ps1 -InputPath .\data.csv -OutPath .\out.csv }
"Baseline: {0:n2}s" -f $base.TotalSeconds 

- Identifier les points chauds en segmentant le script

 $sw = [System.Diagnostics.Stopwatch]::StartNew()
# Étape A : lecture
$rows = Import-Csv .\data.csv
"Stage A: {0:n2}s" -f $sw.Elapsed.TotalSeconds

# Étape B : transformation
$sw.Restart()
$rows = $rows | Where-Object { $_.Enabled -eq 'true' }
"Stage B: {0:n2}s" -f $sw.Elapsed.TotalSeconds

# Étape C : écriture
$sw.Restart()
$rows | Export-Csv .\out.csv -NoTypeInformation
"Stage C: {0:n2}s" -f $sw.Elapsed.TotalSeconds 

- Appliquer des correctifs ciblés (exemples)

- Remplacer les boucles de pipeline par foreach là où c'est critique.

- Utiliser Import-Csv -Delimiter et -Header exactement selon les besoins pour réduire l'analyse.

- Filtrer les lignes avant les calculs coûteux.

- Regrouper les écritures lorsque possible (un seul Export-Csv plutôt que plusieurs Add-Content ).

- Re-mesurer et comparer à la ligne de base . Conserver uniquement les changements avec des gains clairs.

## Vérification et diagnostics

 Créez des contrôles simples et reproductibles couvrant la correction, le temps et l'utilisation des ressources.

### Correction

# Comparer les compteurs de lignes et hachages d'échantillon après refactor
$expCount = (Get-Content .\expected.csv).Count
$actCount = (Get-Content .\out.csv).Count
if ($expCount -ne $actCount) { throw 'Row count changed unexpectedly.' }

# Vérification ponctuelle de quelques champs
Import-Csv .\out.csv | Select-Object -First 5 

### Temps et CPU

 # Chronométrage de bout en bout
Measure-Command { & .\pilot.ps1 -InputPath .\data.csv -OutPath .\out.csv }

# Instantané CPU pendant l'exécution (3 échantillons)
Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 3 

### Mémoire et handles

 Get-Process -Id $PID | Select-Object ProcessName, Id, WS, PM, Handles 

### E/S

 # Sonde approximative de débit disque sur un fichier test (exemple construit)
$test = 'C:\temp\iobench.bin'
$bytes = New-Object byte[](100MB)
[System.IO.File]::WriteAllBytes($test, $bytes)
Measure-Command { [void][System.IO.File]::ReadAllBytes($test) }
Remove-Item $test -Force 

### Référence rapide : sondes de vérification

- Temps de bout en bout → Measure-Command { & .\script.ps1 }

- Pic CPU → Get-Counter '\Processor(_Total)\% Processor Time'

- Utilisation mémoire → Get-Process -Id $PID | Select-Object WS, PM

- Accessibilité réseau → Test-NetConnection hôte -Port 443

## Modes de défaillance et récupération

 L'optimisation des performances peut introduire des risques subtils. Prévoyez-les et gardez le retour arrière simple.

- Surcharge de dépendance par fan-out parallèle

- Symptôme : taux d'erreurs plus élevé, timeouts, réponses de limitation

- Action : réduire ThrottleLimit, ajouter backoff et relances, ou revenir au mode série

- Croissance mémoire non bornée

- Symptôme : working set qui grimpe continuellement

- Action : flux de données (ReadLines, -ReadCount), écrire des résultats intermédiaires, ou traiter par fenêtres

- Progression masquée supprimant le retour utilisateur

- Symptôme : aucun travail visible ; longues exécutions silencieuses

- Action : journaliser des jalons périodiques et toujours restaurer $ProgressPreference

- Écritures partielles laissant une sortie corrompue

- Symptôme : fichiers tronqués après interruption

- Action : écrire dans un fichier temporaire et renommer atomiquement en cas de succès

$target = '.\out.csv'; $temp = "$target.tmp"
Export-Csv $temp -NoTypeInformation -InputObject $rows
Move-Item -Force $temp $target 

- Logique modifiée altérant les résultats

- Symptôme : comptes ou champs clés différents

- Action : comparer les sorties d'échantillon et vérifications de hachage avant d'adopter le changement

### Modèle de retour arrière

- Conserver le script précédent sous script.ps1.bak .

- Si un changement régresse, restaurer le .bak et relancer la ligne de base pour confirmer :

 Copy-Item .\script.ps1.bak .\script.ps1 -Force
& .\script.ps1 -InputPath .\data.csv -OutPath .\out.csv 

## Techniques d'optimisation pratiques en un coup d'œil

- Mot-clé foreach → Boucles volumineuses en mémoire → foreach ($x in $data) { ... }

- Filtres fournisseur → Système de fichiers ou fournisseurs → Get-ChildItem C:\Logs -Filter *.log -Recurse

- Flux E/S → Fichiers volumineux → [System.IO.File]::ReadLines($path)

- Limiter les propriétés → Objets lourds → Select-Object Id, Name uniquement

- Supprimer la progression → Opérations en masse → $ProgressPreference = 'SilentlyContinue'

- Parallélisme modéré → Appels distants, PS 7+ → ForEach-Object -Parallel -ThrottleLimit 5

## Dimensionnement des ressources et attentes

 Le dimensionnement fixe le plafond de ce que votre script peut atteindre. Utilisez ces vérifications rapides pour aligner les attentes.

- Confirmer PowerShell 64 bits lors de travaux avec de gros volumes mémoire :

[Environment]::Is64BitProcess
$PSVersionTable.PSEdition 

- Capture instantanée CPU et mémoire disponibles avant une grosse exécution et après le premier run pilote :

 Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize, FreePhysicalMemory
Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 3 
 Si la mémoire libre est déjà serrée ou le CPU saturé par d'autres services, concentrez-vous sur le streaming et le traitement par lots plutôt que sur l'augmentation du parallélisme.

## Liste de contrôle opérationnelle

Utilisez cette liste chaque fois que vous optimisez ou révisez un script.

- Inventaire

- Enregistrer la version PowerShell, le statut 64 bits, le nombre de CPU et la mémoire libre.

- Noter les tailles de données, les dépendances distantes et la concurrence attendue.

- Ligne de base

- Exécuter Measure-Command sur un jeu de données représentatif.

- Capturer des instantanés CPU, mémoire et E/S.

- Identifier les points chauds

- Insérer un chronométrage léger entre les étapes.

- Journaliser les comptes d'itérations et les tailles.

- Appliquer des changements sûrs

- Privilégier foreach à ForEach-Object dans les boucles critiques.

- Filtrer tôt avec les filtres cmdlet ou fournisseur.

- Flux pour les gros fichiers ; éviter Select-Object * .

- Supprimer la progression dans le travail en masse et la restaurer.

- Envisager un parallélisme modéré pour les appels distants avec limitation.

- Vérifier

- Comparer les sorties pour égalité ou invariants.

- Re-mesurer temps et ressources ; répéter les runs pour la cohérence.

- Décider et documenter

- Conserver uniquement les changements avec des gains clairs et reproductibles.

- Enregistrer l'environnement et les mesures aux côtés du script.

- Plan de retour arrière

- Garder une version précédente immédiate prête.

- En cas de régression, restaurer et re-baseliner.

## Conclusion

L'optimisation des performances en PowerShell est la plus efficace lorsqu'elle est pratique, incrémentale et vérifiable. Commencez par un petit pilote représentatif ; collectez une ligne de base propre ; changez une seule chose à la fois ; et gardez le retour arrière trivial. Concentrez-vous d'abord sur l'efficacité du débit en réduisant le surcoût par objet et en filtrant tôt, puis gérez la latence des dépendances externes par la mesure et le parallélisme contrôlé. Vérifiez chaque changement avec le chronométrage, les instantanés de ressources et les comparaisons de sortie. Avec ces techniques et la liste de contrôle ci-dessus, vous pouvez améliorer le temps d'exécution, réduire les pics de mémoire et construire des scripts qui restent rapides et fiables à mesure que vos charges de travail augmentent.