Optimisation de PowerShell : Tâches planifiées, Jobs en arrière-plan et Multithreading

L'importance de la parallélisation dans l'automatisation

Dans le développement de scripts PowerShell, le pasage d'une exécution séquentielle à une approche parallèle est crucial pour la performance. Par défaut, PowerShell traite les commandes les unes après les autres. Si une tâche prend du temps (comme une requête réseau ou une analyse disque), l'intégralité du script est bloquée.

# Exemple de latence en mode séquentiel
$domaines = "google.com", "bing.com", "duckduckgo.com"
foreach ($domaine in $domaines) {
    $debut = Get-Date
    Test-Connection -ComputerName $domaine -Count 1 -ErrorAction SilentlyContinue | Out-Null
    $duree = ((Get-Date) - $debut).TotalSeconds
    Write-Host "Vérification de $domaine terminée en $duree secondes."
}

Ici, le temps total est la somme des délais de chaque test. Pour optimiser cela, nous disposons de trois approches principales.

1. Les Jobs en arrière-plan (Start-Job)

Il s'agit de la méthode la plus ancienne pour isoler des tâches. Chaque "Job" lance un nouveau processus PowerShell en arrière-plan.

  • Avantage : Isolation complète (les variables n'interfèrent pas).
  • Inconvénient : Consommation de ressources élevée (chaque job consomme de la mémoire comme une nouvelle instance de PowerShell).
$serveurs = "127.0.0.1", "localhost", "192.168.1.1"

# Lancement des processus esclaves
$mesJobs = foreach ($ip in $serveurs) {
    Start-Job -ScriptBlock {
        param($cible)
        $status = Test-Connection $cible -Count 1 -Quiet
        return [PSCustomObject]@{ Machine = $cible; EnLigne = $status }
    } -ArgumentList $ip
}

# Récupération des données
$resultats = $mesJobs | Wait-Job | Receive-Job
$mesJobs | Remove-Job # Nettoyage impératif

$resultats | Format-Table -AutoSize

2. Parallélisation native avec ForEach-Object (PowerShell 7+)

Depuis PowerShell 7, l'instruction ForEach-Object dispose d'un paramètre -Parallel. Contrairement aux Jobs, cette méthode utilise des threads au sein du même processus, ce qui est beaucoup plus léger.

# Vérification de l'état de services en parallèle
$nomsServices = "Spooler", "WinRM", "LanmanWorkstation"

$nomsServices | ForEach-Object -Parallel {
    $etat = Get-Service -Name $_ -ErrorAction SilentlyContinue
    [PSCustomObject]@{
        Nom    = $_
        Statut = if ($etat) { $etat.Status } else { "Inexistant" }
    }
} -ThrottleLimit 5 # Limite à 5 threads simultanés

Note : Pour utiliser des variables définies à l'extérieur du bloc script, vous devez utiliser le préfixe $using: (ex: $using:maVariable).

3. Utilisation des Runspaces (Haute Performance)

Les RunspacePools représentent la méthode la plus performante et compatible (PS 5.1 et 7). Ils permettent de réutiliser des threads sans le surcoût de création de processus.

function Invoke-ActionParallele {
    param(
        [Parameter(Mandatory)] [array]$ListeEntree,
        [Parameter(Mandatory)] [scriptblock]$BlocScript,
        [int]$MaxThreads = 4
    )

    $pool = [RunspaceFactory]::CreateRunspacePool(1, $MaxThreads)
    $pool.Open()

    $taches = New-Object System.Collections.Generic.List[object]

    foreach ($item in $ListeEntree) {
        $ps = [PowerShell]::Create().AddScript($BlocScript).AddArgument($item)
        $ps.RunspacePool = $pool
        $taches.Add([PSCustomObject]@{
            Instance = $ps
            Handle   = $ps.BeginInvoke()
        })
    }

    # Extraction des données
    $resultats = foreach ($t in $taches) {
        $t.Instance.EndInvoke($t.Handle)
        $t.Instance.Dispose()
    }

    $pool.Close()
    $pool.Dispose()
    return $resultats
}

# Exemple pratique : Calcul complexe simulé
$nombres = 1..10
$monCalcul = { param($n); Start-Sleep -Milliseconds 500; return $n * $n }

Invoke-ActionParallele -ListeEntree $nombres -BlocScript $monCalcul

Automatisation avec le Planificateur de tâches

L'exécution parallèle est souvent couplée à une planification. Voici comment enregistrer un script de maintenance pour qu'il s'exécute automatiquement.

# Définition du script de maintenance
$cheminScript = "C:\Scripts\PurgeLogs.ps1"
'Get-ChildItem "C:\Logs" -Filter *.log | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } | Remove-Item' | Set-Content $cheminScript

# Configuration de la tâche planifiée
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-File $cheminScript"
$declencheur = New-ScheduledTaskTrigger -Weekly -At "03:00" -DaysOfWeek Sunday
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount

Register-ScheduledTask -TaskName "MaintenanceHebdoLogs" -Action $action -Trigger $declencheur -Principal $principal

Tableau Comparatif des Méthodes

Méthode Modèle Performance Compatibilité
Start-Job Multi-processus Faible PS 3.0+
ForEach-Object -Parallel Multi-thread Haute PS 7.0+
RunspacePool Multi-thread Optimale PS 5.1+

Synthèse des bonnes pratiques

  • Utilisez ForEach-Object -Parallel pour les scripts rapides sur des environnements modernes.
  • Privilégiez les Runspaces pour les outils d'adminitsration destinés à être partagés sur des serveurs avec différentes versions de PowerShell.
  • N'oubliez jamais de libérer les ressources (Dispose ou Remove-Job) pour éviter les fuites de mémoire.
  • Pour la persistance (exécution après redémarrage), utilisez Register-ScheduledTask.

Étiquettes: PowerShell multithreading Runspace automation devops

Publié le 2 août à 14h02