## 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.

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Vérification</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Commande</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Résultat attendu (exemple)</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Version PowerShell</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">$PSVersionTable.PSVersion</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">7.4.x</code> ou <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">5.1.x</code></td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pester installé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Get-Module Pester -ListAvailable</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Affiche <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">5.5.0+</code></td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">ScriptAnalyzer</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Get-Module PSScriptAnalyzer</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Toute version stable</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Fournisseur NuGet</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Get-PackageProvider NuGet</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Liste une version</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Racine cible existe</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Test-Path C:\Modules\MyCompany.Tools</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">True</code> ou créer</td></tr></tbody>
</table>
</div>
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.

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Symptôme</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Cause probable</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Correctif</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Script bloqué au démarrage</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">ExecutionPolicy restreinte</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Exécuter <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">powershell.exe -ExecutionPolicy Bypass -File deploy.ps1</code> pour les chemins de code approuvés ou définir une stratégie appropriée via la gestion des stratégies.</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Fournisseur &#39;NuGet&#39; introuvable</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Fournisseur NuGet non amorcé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Exécuter <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Get-PackageProvider -Name NuGet -ForceBootstrap</code>.</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">WhatIf affiche des actions mais rien ne se passe en exécution réelle</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">SupportsShouldProcess</code> manquant ou <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">ShouldProcess</code> non appelé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">S&#39;assurer que <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">[CmdletBinding(SupportsShouldProcess=$true)]</code> et envelopper les actions avec <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">$PSCmdlet.ShouldProcess</code>.</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Erreurs de chemin sur Linux/macOS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Chemins Windows codés en dur</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Utiliser <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Join-Path</code>, éviter les barres obliques inverses et paramétrer les racines.</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Fichier verrouillé pendant le déploiement</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Un autre processus retient le fichier</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Utiliser la permutation des dossiers Current/Previous pour éviter l&#39;écrasement sur place ; réessayer après libération du verrou.</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pester n&#39;arrive pas à importer la fonction</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Chemin d&#39;importation incorrect</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Faire un dot-source du chemin correct ou convertir en manifeste de module et <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">Import-Module</code>.</td></tr></tbody>
</table>
</div>

### 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.