7.2 : Exécuter une tâche sur 50 serveurs à la fois

Sophie pose un ticket qui aurait semblé impensable en début de formation : « Nordika vient de racheter une filiale. Il faut vérifier l’espace disque sur les 50 nouveaux serveurs, et me remonter uniquement ceux qui ont moins de 20 % d’espace libre. Aujourd’hui, pas dans trois jours. » Vous avez déjà vu en 7.1 comment cibler plusieurs serveurs avec Invoke-Command. Reste à comprendre ce qui se passe vraiment à cette échelle, et comment garder le contrôle.

Le parallélisme par défaut, et sa limite

Comme vu en 7.1, Invoke-Command avec plusieurs machines s’exécute déjà en parallèle, pas séquentiellement. Mais ce parallélisme n’est pas illimité : PowerShell plafonne par défaut à 32 connexions simultanées, réglable via -ThrottleLimit :

$serveurs = Get-Content -Path ".\liste-50-serveurs.txt"

Invoke-Command -ComputerName $serveurs -ThrottleLimit 50 -ScriptBlock {
    Get-Volume -DriveLetter C
}

Retour d’expérience terrain : augmenter -ThrottleLimit au-delà de la valeur par défaut accélère l’exécution globale, mais sollicite davantage votre poste local (chaque connexion consomme des ressources) et le réseau. Sur un lot de plusieurs centaines de serveurs, mieux vaut tester d’abord sur un sous-ensemble restreint (5 à 10 serveurs) avant de lancer le script sur l’ensemble du parc — un script qui fonctionne sur 5 machines ne se comporte pas toujours de la même façon sur 300, notamment si un serveur répond lentement ou pas du tout.

Ne pas laisser un serveur injoignable bloquer tout le lot

C’est ici que la leçon 5.5 sur la gestion des erreurs redevient essentielle, à une échelle différente. Sans précaution, un seul serveur éteint ou injoignable peut ralentir l’ensemble de l’exécution, le temps que la connexion échoue par timeout :

$resultats = Invoke-Command -ComputerName $serveurs -ScriptBlock {
    Get-Volume -DriveLetter C
} -ErrorAction SilentlyContinue -ErrorVariable erreursServeurs

if ($erreursServeurs) {
    Write-Warning "$($erreursServeurs.Count) serveur(s) injoignable(s) :"
    $erreursServeurs | ForEach-Object { Write-Warning $_.TargetObject }
}

-ErrorVariable (vu en 5.5) permet ici de continuer le traitement de tous les serveurs joignables, tout en gardant une trace claire de ceux qui ont échoué — exactement le principe déjà appliqué au TP de création de comptes en masse.

Traiter le résultat comme n’importe quelle collection d’objets

Une fois les résultats collectés, on retrouve exactement les réflexes de la section 2 : filtrer, trier, formater.

$resultats | 
    Select-Object -Property PSComputerName, 
                            @{Name="EspaceLibrePourcent"; Expression={ [math]::Round(($_.SizeRemaining / $_.Size) * 100, 1) }} |
    Where-Object { $_.EspaceLibrePourcent -lt 20 } |
    Sort-Object -Property EspaceLibrePourcent |
    Format-Table -AutoSize

Le ticket de Sophie est ainsi rempli en quelques lignes : sur 50 serveurs interrogés en parallèle, seuls ceux sous le seuil critique de 20 % apparaissent dans le résultat final, triés du plus critique au moins critique.

Les Jobs : lancer une tâche sans bloquer sa console

Pour une tâche particulièrement longue, PowerShell permet de la lancer en arrière-plan, libérant immédiatement la console pour continuer à travailler pendant l’exécution :

$job = Invoke-Command -ComputerName $serveurs -ScriptBlock { Get-Volume -DriveLetter C } -AsJob

# La console est immédiatement libre ici, le job tourne en arrière-plan

Get-Job -Id $job.Id

Une fois le job terminé (Get-Job indique l’état Completed), on en récupère le résultat :

$resultats = Receive-Job -Id $job.Id

Retour d’expérience terrain : les jobs sont particulièrement utiles pour des tâches de plusieurs minutes lancées manuellement en cours de journée. En revanche, pour une automatisation planifiée sans surveillance (le sujet de la section 8), on préfère généralement laisser le script s’exécuter normalement jusqu’à sa fin plutôt que de gérer des jobs asynchrones, plus complexes à superviser correctement sans intervention humaine.

Un exemple concret Nordika : le rapport complet du ticket de Sophie

$serveurs = Get-Content -Path ".\liste-50-serveurs.txt"

$resultats = Invoke-Command -ComputerName $serveurs -ThrottleLimit 50 -ScriptBlock {
    $volume = Get-Volume -DriveLetter C
    [PSCustomObject]@{
        EspaceLibrePourcent = [math]::Round(($volume.SizeRemaining / $volume.Size) * 100, 1)
    }
} -ErrorAction SilentlyContinue -ErrorVariable erreursServeurs

Write-Output "Serveurs interrogés : $($serveurs.Count)"
Write-Output "Serveurs injoignables : $($erreursServeurs.Count)"

$resultats | 
    Where-Object { $_.EspaceLibrePourcent -lt 20 } | 
    Sort-Object -Property EspaceLibrePourcent | 
    Select-Object -Property PSComputerName, EspaceLibrePourcent |
    Format-Table -AutoSize

Source officielle : la documentation Microsoft about_Job et about_Jobs_Remoting détaille les différents types de jobs disponibles (AsJob, Start-Job, jobs planifiés vus en section 8), leurs différences de performance et leurs limites respectives.

Retenez la logique de cette leçon : à 40 ou 50 serveurs, ce qui change fondamentalement par rapport à la section 5 n’est pas la syntaxe PowerShell elle-même, mais la nécessité de prévoir l’échec individuel d’une machine sans compromettre le résultat global — exactement l’esprit du TP à venir.

Le cas particulier des machines en Workgroup (hors domaine)

Tout ce qui a été vu jusqu’ici avec WinRM suppose implicitement un contexte simple : des machines jointes au même domaine Active Directory, qui se font confiance mutuellement via Kerberos. Sophie prévient : « Sur un poste en Workgroup — chez un client sans domaine, ou une machine de test isolée — WinRM devient beaucoup plus pénible à configurer. »

Pourquoi WinRM se complique hors domaine

Sans domaine, l’authentification Kerberos n’est pas disponible : WinRM retombe sur NTLM, qui exige que chaque machine fasse explicitement confiance aux autres. Il faut déclarer manuellement les machines autorisées dans une liste appelée TrustedHosts :

Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value "SRV-ISOLE-01,SRV-ISOLE-02"

Puis, contrairement au contexte domaine où l’authentification est transparente, il faut fournir explicitement des identifiants à chaque connexion :

$credential = Get-Credential

Invoke-Command -ComputerName "SRV-ISOLE-01" -Credential $credential -ScriptBlock {
    Get-Service -Name "Spooler"
}

Retour d’expérience terrain : TrustedHosts accepte des caractères génériques (* pour tout autoriser), mais c’est une très mauvaise idée en dehors d’un labo de test isolé : ce réglage désactive une bonne partie des vérifications d’identité de la machine distante, exposant la connexion à un risque d’usurpation. Listez toujours les machines explicitement, une par une ou par plage IP précise.

Pourquoi SSH devient souvent plus simple dans ce cas

C’est précisément dans ce contexte que le transport SSH, vu juste au-dessus, prend tout son sens. SSH ne repose pas sur la confiance NTLM/Kerberos de Windows : il utilise son propre mécanisme d’authentification (mot de passe ou, mieux, clé), indépendant de toute notion de domaine ou de Workgroup.

Invoke-Command -HostName "SRV-ISOLE-01" -UserName "admin" -ScriptBlock { 
    Get-Service -Name "Spooler" 
}

Aucune configuration de TrustedHosts à faire, aucune gestion de NTLM : une fois le serveur SSH installé et une clé échangée, la connexion fonctionne de façon identique, que la machine soit en Workgroup, sur un domaine différent, ou même sous Linux.

Bonne pratique constatée en production : pour administrer un parc mixte incluant des machines Workgroup isolées (fréquent dans les environnements Nordika multi-sites ou après un rachat d’entreprise, comme évoqué en 7.2), beaucoup d’équipes IT standardisent désormais sur le transport SSH pour ce type de machines spécifiquement, et réservent WinRM/Kerberos aux serveurs pleinement intégrés au domaine — évitant ainsi la gestion fastidieuse de TrustedHosts au cas par cas.

Dans la prochaine leçon, un TP pour construire un vrai rapport de supervision multi-serveurs, de bout en bout.