E-NO
DevOps 11 min de lecture

Automatisation CI/CD PowerShell avec exemples pratiques : guide d'implémentation pratique

calendar_today Publié : 2026-07-28
update Dernière mise à jour : 2026-07-28
analytics Efficacité SEO : 97%
Illustration du guide technique pour « Automatisation CI/CD PowerShell avec exemples pratiques : guide d'implémentation pratique ».

Introduction

Ce guide est une présentation pratique, de bout en bout, pour les équipes qui souhaitent automatiser la construction, les tests et le déploiement PowerShell avec CI/CD. Vous allez :

  • Faire l'inventaire des versions et des dépendances pour que votre pipeline soit reproductible.
  • Implémenter un essai pilote sûr qui modifie un fichier de configuration avec la sémantique WhatIf et Confirm.
  • Valider avec PSScriptAnalyzer et Pester avant d'empaqueter un artefact versionné.
  • Déployer avec des vérifications préalables, un essai à blanc et une vérification post-déploiement.
  • Gérer les échecs courants et effectuer un retour arrière propre.

Les exemples sont volontairement minimaux et ne nécessitent pas de conteneurs. Vous pouvez adapter les scripts à votre service CI/CD préféré ; l'accent reste mis sur les chemins de code PowerShell sur lesquels les opérateurs s'appuient.

Inventaire des versions et de l'environnement

Avant d'automatiser, capturez les versions et les paramètres exacts que vos scripts et exécuteurs utiliseront.

Vérifications rapides

# Version et plateforme PowerShell
$PSVersionTable

# S'assurer que PSGallery est approuvé et que les outils sont disponibles
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser -Force
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force

# Vérifier les outils
Get-Module Pester -ListAvailable | Select-Object Name, Version | Sort-Object Version -Descending | Select-Object -First 1
Get-Module PSScriptAnalyzer -ListAvailable | Select-Object Name, Version | Sort-Object Version -Descending | Select-Object -First 1

# Disponibilité du fournisseur NuGet (nécessaire pour certaines opérations de modules)
Get-PackageProvider -Name NuGet -ForceBootstrap | Format-List Name, Version

# Confirmer l'accès en écriture à la racine cible prévue (exemple)
$targetRoot = 'C:\Modules\MyCompany.Tools'
Test-Path $targetRoot -PathType Container

Utilisez un court tableau pour aligner les attentes entre les machines et les exécuteurs.

VérificationCommandeRésultat attendu (exemple)
Version PowerShell$PSVersionTable.PSVersion7.4.x ou 5.1.x
Pester installéGet-Module Pester -ListAvailableAffiche 5.5.0+
ScriptAnalyzerGet-Module PSScriptAnalyzerToute version stable
Fournisseur NuGetGet-PackageProvider NuGetListe une version
Racine cible existeTest-Path C:\Modules\MyCompany.ToolsTrue ou créer

Si les valeurs diffèrent selon l'environnement, enregistrez-les afin de pouvoir épingler les versions compatibles et éviter la dérive.

Chemin de configuration sécurisé

Définir un essai pilote étroit et inspectable

Commencez par un essai pilote rapide à comprendre et simple à observer : une fonction qui écrit un fichier de configuration, crée une sauvegarde et prend en charge WhatIf/Confirm. Cela enseigne les motifs de sécurité dont vos scripts plus importants auront besoin.

La fonction (avec comportement WhatIf et favorable au retour arrière)

# Fichier : Module/Public/Set-ConfigFile.ps1
function Set-ConfigFile {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true)]
        [string] $Path,
        [Parameter(Mandatory = $true)]
        [string] $Content,
        [switch] $CreateBackup
    )

    $resolvedPath = (Resolve-Path -LiteralPath $Path -ErrorAction SilentlyContinue) ?? $Path
    $dir = Split-Path -Parent $resolvedPath
    if (-not (Test-Path -LiteralPath $dir)) {
        if ($PSCmdlet.ShouldProcess($dir, 'Créer le répertoire')) {
            New-Item -ItemType Directory -Path $dir -Force | Out-Null
        }
    }

    $backupPath = if ($CreateBackup) { "$resolvedPath.bak-$(Get-Date -Format yyyyMMddHHmmss)" } else { $null }

    if (Test-Path -LiteralPath $resolvedPath -PathType Leaf -ErrorAction SilentlyContinue -and $CreateBackup) {
        if ($PSCmdlet.ShouldProcess($resolvedPath, "Sauvegarder vers $backupPath" )) {
            Copy-Item -LiteralPath $resolvedPath -Destination $backupPath -Force
        }
    }

    if ($PSCmdlet.ShouldProcess($resolvedPath, 'Écrire le contenu')) {
        Set-Content -LiteralPath $resolvedPath -Value $Content -Encoding UTF8 -NoNewline
    }

    [pscustomobject]@{
        Success = $true
        Path = $resolvedPath
        BackupPath = $backupPath
    }
}

Points clés :

  • [CmdletBinding(SupportsShouldProcess = $true)] active -WhatIf et -Confirm pour des exécutions sûres.
  • Les sauvegardes sont versionnées avec un horodatage et n'écrasent pas l'état précédent.
  • La fonction retourne un objet simple que les opérateurs peuvent journaliser et inspecter.

Tests unitaires avec Pester

# Fichier : Module/Tests/Set-ConfigFile.Tests.ps1
$here = Split-Path -Parent $PSCommandPath
$root = Split-Path -Parent $here
Import-Module "$root\Public\Set-ConfigFile.ps1" -Force

Describe 'Set-ConfigFile' {
    It 'Crée un fichier quand aucun n\'existe' {
        $tmp = Join-Path $env:TEMP 'cfg-test-1.txt'
        if (Test-Path $tmp) { Remove-Item $tmp -Force }
        $result = Set-ConfigFile -Path $tmp -Content 'abc' -WhatIf:$false
        Test-Path $tmp | Should -BeTrue
        (Get-Content -Raw $tmp) | Should -Be 'abc'
        $result.Success | Should -BeTrue
    }

    It 'Crée une sauvegarde quand demandée' {
        $tmp = Join-Path $env:TEMP 'cfg-test-2.txt'
        Set-Content -Path $tmp -Value 'v1'
        $result = Set-ConfigFile -Path $tmp -Content 'v2' -CreateBackup -WhatIf:$false
        Test-Path $result.BackupPath | Should -BeTrue
        (Get-Content -Raw $tmp) | Should -Be 'v2'
    }

    It 'Respecte WhatIf pour les essais à blanc' {
        $tmp = Join-Path $env:TEMP 'cfg-test-3.txt'
        if (Test-Path $tmp) { Remove-Item $tmp -Force }
        $ = Set-ConfigFile -Path $tmp -Content 'dry' -WhatIf
        Test-Path $tmp | Should -BeFalse
    }
}

Analyse statique

Invoke-ScriptAnalyzer -Path .\Module -Recurse -Severity Warning, Error

Résolvez les erreurs avant de continuer. Les avertissements constituent un backlog utile mais doivent être triés.

Empaqueter un artefact versionné

Gardez l'empaquetage simple et déterministe : copiez le module/fichiers dans un dossier versionné, ajoutez un VERSION.txt et compressez-le.

# Fichier : build.ps1 (exécution locale ou dans CI)
$ErrorActionPreference = 'Stop'
$version = Get-Date -Format 'yyyy.MM.dd.HHmm'
$out = Join-Path $PSScriptRoot 'out'
New-Item -ItemType Directory -Path $out -Force | Out-Null
$dst = Join-Path $out 'MyCompany.Tools'
Copy-Item -Path .\Module\* -Destination $dst -Recurse -Force
Set-Content -Path (Join-Path $dst 'VERSION.txt') -Value $version -NoNewline
$zip = Join-Path $out ("MyCompany.Tools-$version.zip" )
if (Test-Path $zip) { Remove-Item $zip -Force }
Compress-Archive -Path $dst -DestinationPath $zip -Force
Write-Host "Artefact empaqueté : $zip"

Exemple CI minimal (exécuteur Windows)

Ceci est un exemple que vous pouvez adapter. L'accent est mis sur les étapes PowerShell.

# .github/workflows/ci-powershell.yml
name: ci-powershell
on:
  push:
    branches: [ main ]
jobs:
  test:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
      - name: Installer les outils
        shell: pwsh
        run: |
          Set-PSRepository PSGallery -InstallationPolicy Trusted
          Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser -Force
          Install-Module PSScriptAnalyzer -Scope CurrentUser -Force
      - name: Analyse statique
        shell: pwsh
        run: Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning, Error
      - name: Tests unitaires
        shell: pwsh
        run: Invoke-Pester -CI -Output Detailed
      - name: Empaqueter l'artefact
        shell: pwsh
        run: .\build.ps1
      - name: Téléverser l'artefact
        uses: actions/upload-artifact@v4
        with:
          name: MyCompany.Tools
          path: out/*.zip

Déploiement avec vérifications préalables, essai à blanc et retour arrière

Script de déploiement

Ce script déploie un artefact versionné dans une racine cible avec un motif de dossiers Current et Previous. Le retour arrière est un renommage + restauration.

# Fichier : deploy.ps1
[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [Parameter(Mandatory = $true)]
    [string] $ArtifactZip,
    [Parameter(Mandatory = $true)]
    [string] $TargetRoot
)

$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'

function Invoke-PreChecks {
    param([string]$Zip, [string]$Root)
    Write-Verbose "Vérification de l'artefact à $Zip"
    if (-not (Test-Path -LiteralPath $Zip -PathType Leaf)) {
        throw "Artefact introuvable : $Zip"
    }
    Write-Verbose "Vérification de la racine cible à $Root"
    if (-not (Test-Path -LiteralPath $Root)) {
        New-Item -ItemType Directory -Path $Root -Force | Out-Null
    }
    $space = ([System.IO.DriveInfo] (Split-Path -Qualifier $Root)).AvailableFreeSpace
    if ($space -lt 200MB) { throw "Espace disque insuffisant à $Root" }
}

function Expand-Artifact {
    param([string]$Zip, [string]$Root)
    $stamp = Get-Date -Format 'yyyyMMddHHmmss'
    $staging = Join-Path $Root "Staging-$stamp"
    New-Item -ItemType Directory -Path $staging -Force | Out-Null
    Write-Verbose "Décompression de $Zip vers $staging"
    Expand-Archive -Path $Zip -DestinationPath $staging -Force
    return $staging
}

function Switch-Version {
    param([string]$Root, [string]$Staging)
    $current = Join-Path $Root 'Current'
    $previous = Join-Path $Root 'Previous'

    if (Test-Path $previous) { Remove-Item $previous -Recurse -Force }
    if (Test-Path $current) {
        Rename-Item -Path $current -NewName 'Previous' -Force
    }
    Rename-Item -Path $Staging -NewName 'Current' -Force
}

Invoke-PreChecks -Zip $ArtifactZip -Root $TargetRoot

# Prise en charge de l'essai à blanc via -WhatIf
if ($PSCmdlet.ShouldProcess($TargetRoot, 'Déployer l\'artefact')) {
    $staging = Expand-Artifact -Zip $ArtifactZip -Root $TargetRoot
    Switch-Version -Root $TargetRoot -Staging $staging
}

Write-Host 'Déploiement terminé. Contenu Current :'
Get-ChildItem -Path (Join-Path $TargetRoot 'Current') -Recurse | Select-Object -First 5 | Format-List

Exemples d'utilisation :

# Essai à blanc
pwsh -File .\deploy.ps1 -ArtifactZip .\out\MyCompany.Tools-2024.05.01.1200.zip -TargetRoot C:\Modules\MyCompany.Tools -WhatIf

# Déploiement réel
pwsh -File .\deploy.ps1 -ArtifactZip .\out\MyCompany.Tools-2024.05.01.1200.zip -TargetRoot C:\Modules\MyCompany.Tools -Confirm:$false

Import post-déploiement et test de fumée

$moduleRoot = 'C:\Modules\MyCompany.Tools\Current'
$public = Join-Path $moduleRoot 'Public'
$functionPath = Join-Path $public 'Set-ConfigFile.ps1'
. $functionPath

$tmp = Join-Path $env:TEMP 'post-deploy-check.txt'
$result = Set-ConfigFile -Path $tmp -Content 'ok' -CreateBackup -WhatIf:$false
$result.Success -and (Get-Content -Raw $tmp) -eq 'ok'

Vous devriez voir True et le contenu du fichier devrait être exactement ok.

Vérification et diagnostics

Rendez le succès observable et l'échec diagnostiquable.

  • Analyse statique : Invoke-ScriptAnalyzer doit se terminer sans constatations de sévérité Error.
  • Tests : Invoke-Pester doit signaler 0 Failed et un nombre de tests non nul.
  • Empaquetage : Le fichier zip de l'artefact doit contenir un dossier MyCompany.Tools avec VERSION.txt.
  • Pré-déploiement : L'essai à blanc (-WhatIf) doit afficher les actions planifiées sans modifier la racine cible.
  • Post-déploiement : Importer et exécuter la fonction ; les changements de fichiers attendus doivent exister sur le disque.

Commandes et vérifications d'exemple :

# Vérifier l'analyse et les tests dans CI ou localement
Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error
Invoke-Pester -CI -Output Detailed

# Valider la structure de l'artefact
Expand-Archive -Path .\out\MyCompany.Tools-*.zip -DestinationPath .\out\unpacked -Force
Test-Path .\out\unpacked\MyCompany.Tools\Public\Set-ConfigFile.ps1

# Confirmer que l'essai à blanc ne modifie pas le système de fichiers
$before = Get-ChildItem C:\Modules\MyCompany.Tools -Recurse -Force | Select-Object FullName, Length
pwsh -File .\deploy.ps1 -ArtifactZip .\out\MyCompany.Tools-*.zip -TargetRoot C:\Modules\MyCompany.Tools -WhatIf
$after = Get-ChildItem C:\Modules\MyCompany.Tools -Recurse -Force | Select-Object FullName, Length
Compare-Object $before $after

# Exécution du test de fumée post-déploiement
. C:\Modules\MyCompany.Tools\Current\Public\Set-ConfigFile.ps1
$res = Set-ConfigFile -Path "$env:TEMP\ops-check.txt" -Content 'ok' -CreateBackup -WhatIf:$false
$res.Success

Pour les diagnostics, activez la sortie verbose et, au besoin, une transcription.

$VerbosePreference = 'Continue'
Start-Transcript -Path "$env:TEMP\deploy-$(Get-Date -Format yyyyMMddHHmmss).log"
try {
    # exécuter le déploiement
}
finally {
    Stop-Transcript
}

Modes d'échec et récupération

Anticipez ces problèmes courants et gardez les correctifs à portée de main.

SymptômeCause probableCorrectif
Script bloqué au démarrageExecutionPolicy restreinteExécuter powershell.exe -ExecutionPolicy Bypass -File deploy.ps1 pour les chemins de code approuvés ou définir une stratégie appropriée via la gestion des stratégies.
Fournisseur 'NuGet' introuvableFournisseur NuGet non amorcéExécuter Get-PackageProvider -Name NuGet -ForceBootstrap.
WhatIf affiche des actions mais rien ne se passe en exécution réelleSupportsShouldProcess manquant ou ShouldProcess non appeléS'assurer que [CmdletBinding(SupportsShouldProcess=$true)] et envelopper les actions avec $PSCmdlet.ShouldProcess.
Erreurs de chemin sur Linux/macOSChemins Windows codés en durUtiliser Join-Path, éviter les barres obliques inverses et paramétrer les racines.
Fichier verrouillé pendant le déploiementUn autre processus retient le fichierUtiliser la permutation des dossiers Current/Previous pour éviter l'écrasement sur place ; réessayer après libération du verrou.
Pester n'arrive pas à importer la fonctionChemin d'importation incorrectFaire un dot-source du chemin correct ou convertir en manifeste de module et Import-Module.

Procédure de retour arrière

Le script de déploiement préserve la version précédente sous Previous. Pour revenir en arrière :

$root = 'C:\Modules\MyCompany.Tools'
$current = Join-Path $root 'Current'
$previous = Join-Path $root 'Previous'

if (-not (Test-Path $previous)) { throw 'Aucune version Previous vers laquelle revenir.' }

# Essai à blanc d'abord
$PSCmdlet.WhatIfPreference = $true
Rename-Item -Path $current -NewName "Broken-$(Get-Date -Format yyyyMMddHHmmss)" -Force
Rename-Item -Path $previous -NewName 'Current' -Force

# Vérifier
Test-Path (Join-Path $root 'Current')

Si vous préférez ne pas renommer le dossier cassé, supprimez-le plus tard après avoir confirmé que le retour arrière a fonctionné.

Récupération après un déploiement partiel

  • Arrêtez les changements supplémentaires. Ne relancez pas le déploiement avant d'avoir confirmé l'état.
  • Utilisez le dossier Previous comme source faisant autorité pour la restauration.
  • Réexécutez les tests de fumée post-déploiement pour confirmer la santé.
  • Capturez les journaux et les différences pour l'analyse des causes racines avant la prochaine tentative.

Liste de contrôle des opérations

Utilisez ce guide d'exploitation pour maintenir la cohérence des opérations. Remplacez les chemins d'exemple par les vôtres.

  • Inventaire
  • Exécuter $PSVersionTable et enregistrer la version.
  • Installer ou mettre à jour Pester et PSScriptAnalyzer.
  • Confirmer l'existence du fournisseur NuGet.
  • Vérifier le chemin de la racine cible et l'espace disque libre.
  • Valider
  • Exécuter Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning, Error.
  • Exécuter Invoke-Pester -CI et confirmer 0 échec.
  • Empaqueter
  • Exécuter build.ps1 pour générer out/MyCompany.Tools-<version>.zip.
  • Vérifier que VERSION.txt existe dans le paquet.
  • Pré-déploiement
  • Exécuter deploy.ps1 -WhatIf et confirmer que seules les actions planifiées sont listées.
  • S'assurer qu'aucun processus ne verrouille les fichiers sous la racine cible.
  • Déploiement
  • Exécuter deploy.ps1 -Confirm:$false.
  • Observer les dossiers Current et Previous sous la racine cible.
  • Vérifier
  • Faire un dot-source de Set-ConfigFile.ps1 depuis Current et exécuter un test de fumée.
  • Vérifier le contenu du fichier sur le disque comme prévu.
  • Retour arrière (si nécessaire)
  • Renommer Current en dossier horodaté et Previous à nouveau en Current.
  • Réexécuter le test de fumée pour confirmer la récupération.
  • Documenter
  • Enregistrer la version, les horodatages de début/fin, réussite/échec, et toute déviation.

Conclusion

Un essai pilote étroit et inspectable est le moyen le plus rapide de gagner en confiance dans votre chemin CI/CD PowerShell. En séparant la validation de l'empaquetage, en utilisant WhatIf et Confirm pour étager le risque, et en déployant avec un motif Current/Previous réversible, vous obtenez :

  • Des mises en production plus sûres que vous pouvez tester à blanc et vérifier.
  • Des étapes claires et répétables qui réduisent la reprise et les surprises.
  • Des artefacts déterministes que vous pouvez suivre et auditer.

Prochaines étapes :

  • Convertir vos fonctions en un module approprié avec un manifeste de module et des versions sémantiques.
  • Ajouter la signature de code pour augmenter la confiance et réduire les frictions liées à la stratégie d'exécution.
  • Étendre les tests pour inclure des tests de fumée spécifiques à l'environnement (configuration d'application IIS, définitions de tâches planifiées, ou redémarrages de service, selon le cas) en utilisant le même motif WhatIf-first.
  • Standardiser la disposition de vos artefacts entre les équipes pour que les opérateurs puissent déployer avec les mêmes commandes partout.

Avec ces motifs en place, vous pouvez passer d'une seule fonction à un portefeuille d'automatisations PowerShell fiables sans sacrifier la sécurité ni la vitesse.

Recherches connexes

Score de qualité de l’article

Utilité pour le lecteur 97%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO