Introduction
En 2026, PowerShell 7+ reste l'outil incontournable pour l'automatisation cross-platform en DevOps et sysadmin. Créer un module avancé permet de réutiliser du code complexe, comme la gestion parallèle de services système, les classes orientées objets et les tests unitaires avec Pester. Pourquoi c'est crucial ? Les modules encapsulent des fonctionnalités expertes (parallélisme avec ForEach-Object -Parallel, validation stricte des paramètres, logging intégré), rendant vos scripts scalables et maintenables dans des environnements CI/CD. Ce tutoriel vous guide pas à pas pour bâtir un module 'ServiceManager' complet : monitoring multi-serveurs, redémarrages parallèles sécurisés et rapports JSON. À la fin, vous déployez sur PowerShell Gallery. Idéal pour gérer des flottes de 100+ machines sans downtime. (132 mots)
Prérequis
- PowerShell 7.4+ installé (via winget ou PSResourceGet)
- Visual Studio Code avec extension PowerShell
- Module Pester 5+ (
Install-Module Pester -Force) - Connaissances avancées en pipelines, splatting et error handling
- Droits admin pour tester les services système
Initialiser la structure du module
$ModuleName = 'ServiceManager'
$ModulePath = "$PSScriptRoot/$ModuleName"
# Créer le dossier du module
New-Item -Path $ModulePath -ItemType Directory -Force
# Générer le manifeste
New-ModuleManifest -Path "$ModulePath/$ModuleName.psd1" -RootModule "$ModuleName.psm1" -Author 'Expert Dev' -Description 'Module avancé pour gestion services' -PowerShellVersion 7.0 -RequiredModules @() -FunctionsToExport @('Get-ServiceStatus', 'Start-ServiceParallel') -AliasesToExport @()
# Créer le fichier principal vide
New-Item -Path "$ModulePath/$ModuleName.psm1" -ItemType File -Force
Write-Output "Module $ModuleName initialisé dans $ModulePath"Ce script crée la structure standard d'un module : dossier, manifeste PSD1 avec exports explicites et fichier PSM1 principal. Le manifeste définit les métadonnées SEO pour la Gallery, évitant les pièges comme l'export implicite qui pollue le namespace global. Exécutez-le en tant qu'admin pour tester.
Comprendre le manifeste et les exports
Le fichier PSD1 agit comme un contrat : il liste les fonctions exportées, modules requis et versions minimales. Sans FunctionsToExport, vos fonctions restent privées. Ajoutez PrivateData pour PSResourceGet en 2026.
Implémenter les fonctions avancées de base
function Get-ServiceStatus {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$ServiceName,
[ValidateSet('Running', 'Stopped', 'All')]
[string]$Status = 'All'
)
process {
foreach ($name in $ServiceName) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if ($svc) {
[PSCustomObject]@{
Name = $svc.Name
Status = $svc.Status
StartType = $svc.StartType
} | Where-Object { $Status -eq 'All' -or $_.Status -eq $Status }
}
}
}
}
export-modulemember -Function Get-ServiceStatusCette advanced function gère le pipeline avec ValueFromPipeline, validation stricte et PSCustomObject pour sortie structurée. Le process {} permet le streaming, essentiel pour de gros volumes ; ErrorAction SilentlyContinue évite les crashes sur services manquants.
Pipeline et validation des paramètres
[ValidateSet] restreint les inputs, [CmdletBinding()] active splatting. Utilisez toujours PSCustomObject pour des objets cohérents, compatibles avec Export-Csv ou JSON.
Ajouter des classes pour modélisation OO
class ServiceReport {
[string]$Name
[ServiceControllerStatus]$Status
[ServiceStartMode]$StartType
[DateTime]$LastCheck
[string]$ErrorMessage
ServiceReport([string]$name, [ServiceControllerStatus]$status, [ServiceStartMode]$startType) {
$this.Name = $name
$this.Status = $status
$this.StartType = $startType
$this.LastCheck = Get-Date
}
[void] AddError([string]$msg) {
$this.ErrorMessage = $msg
}
}
function Get-ServiceStatus {
# ... code précédent ...
process {
foreach ($name in $ServiceName) {
try {
$svc = Get-Service -Name $name -ErrorAction Stop
[ServiceReport]::new($svc.Name, $svc.Status, $svc.StartType)
}
catch {
[ServiceReport]::new($name, 'Error', 'Unknown') | ForEach-Object { $_.AddError($_.Exception.Message); $_ }
}
}
}
}Les classes PowerShell 5+ encapsulent la logique (constructeur, méthodes). Ici, ServiceReport gère erreurs avec try/catch, rendant le module robuste. Ajoutez au PSM1 existant ; surcharge la fonction précédente pour OO sans casser la compatibilité.
Avantages des classes en PowerShell
Les classes offrent intellisense VSCode, héritage et propriétés calculées. Parfait pour modéliser des entités complexes comme des rapports agrégés.
Implémenter le parallélisme avec jobs
function Start-ServiceParallel {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$ServiceName,
[int]$ThrottleLimit = 5,
[scriptblock]$InitScriptBlock = { }
)
begin {
$InitScriptBlock.Invoke()
}
process {
$ServiceName | ForEach-Object -Parallel {
$using:ThrottleLimit
try {
$svc = Get-Service -Name $_ -ErrorAction Stop
if ($svc.Status -ne 'Running') {
Start-Service -Name $_ -ErrorAction Stop
"Started $_"
}
}
catch {
"Error starting $_ : $($_.Exception.Message)"
}
} -ThrottleLimit $ThrottleLimit
}
}
export-modulemember -Function Start-ServiceParallelForEach-Object -Parallel (PS7+) exécute en threads .NET, avec $using: pour variables parent. SupportsShouldProcess ajoute -WhatIf/-Confirm. ThrottleLimit évite surcharge CPU ; testez sur 10+ services.
Gestion du parallélisme et throttling
En 2026, le parallélisme est natif mais consomme mémoire ; utilisez toujours $using: et limitez à 10-20 threads. Ajoutez logging avec Write-Verbose.
Créer les tests unitaires avec Pester 5
$Here = Split-Path -Parent $PSCommandPath
Import-Module "$Here/../ServiceManager/ServiceManager.psm1" -Force
Describe 'Get-ServiceStatus' {
It 'Retourne un rapport pour service existant' {
$result = Get-ServiceStatus -ServiceName 'BITS'
$result.Status | Should -Be 'Running'
$result | Should -BeOfType ServiceReport
}
It 'Gère les erreurs gracefully' {
$result = Get-ServiceStatus -ServiceName 'NonExistant'
$result.ErrorMessage | Should -Not -BeNullOrEmpty
}
}
Describe 'Start-ServiceParallel' {
It 'Supporte WhatIf' {
{ Start-ServiceParallel -ServiceName 'BITS' -WhatIf } | Should -Not -Throw
}
}Pester 5 utilise Should assertions modernes. Placez dans tests/ ; exécutez Invoke-Pester. Couvre happy path, erreurs et flags. Intégrez à CI avec GitHub Actions.
Script de build et déploiement
$ModuleName = 'ServiceManager'
$ModulePath = "$PSScriptRoot/$ModuleName"
# Build : tester et packer
Import-Module Pester -PassThru | Install-Module -Force
Invoke-Pester -Path "$ModulePath/tests" -Output Detailed
# Publish avec PSResourceGet (2026 standard)
$manifestPath = "$ModulePath/$ModuleName.psd1"
Publish-Module -Path $ModulePath -NuGetApiKey $env:PSGALLERY_APIKEY -Repository PSGallery
Write-Output "Module publié ! Importez avec: Install-Module $ModuleName"Ce build script teste via Pester puis publie sur Gallery avec Publish-Module. Définissez $env:PSGALLERY_APIKEY ; PSResourceGet remplace PackageManagement en 2026 pour sécurité accrue.
Bonnes pratiques
- Toujours exporter explicitement dans PSM1 pour éviter pollution.
- Utilisez
[CmdletBinding(SupportsShouldProcess)]pour toutes actions mutantes. - Implémentez logging :
Write-Error,Write-Verbose,Write-Debug. - Versionnez sémantiquement dans PSD1 et taggez Git.
- Documentez avec
.EXAMPLEdansGet-Help.
Erreurs courantes à éviter
- Oublier
export-modulemember: fonctions invisibles après import. - Parallélisme sans
ThrottleLimit: surcharge système et OOM. - Ignorer
ErrorAction: scripts cassent sur erreurs mineures. - Classes sans try/catch : exceptions non gérées polluent la sortie.
Pour aller plus loin
Approfondissez avec Desired State Configuration (DSC) v3, PowerShell 7.5 threading model et intégration Azure/AWS. Consultez la doc officielle PowerShell. Découvrez nos formations Learni sur l'automatisation pour certification expert.