PowerShell
Créer un module PowerShell professionnel : structure, tests Pester et publication sur PSGallery

Créer un module PowerShell professionnel : structure, tests Pester et publication sur PSGallery

Pourquoi créer un module PowerShell ?

Regrouper vos fonctions en module présente plusieurs avantages : import en une ligne, versioning indépendant, distribution via PSGallery ou un feed interne Artifacts, documentation automatique et tests unitaires intégrés.

1. Structure d'un module professionnel

TechByHM.Infrastructure
├── TechByHM.Infrastructure.psd1   # Manifeste du module
├── TechByHM.Infrastructure.psm1   # Loader principal
├── Public                        # Fonctions exportées (accessibles à l'utilisateur)
│   ├── Get-ServerInventory.ps1
│   ├── Export-ADUserReport.ps1
│   └── Invoke-PatchCompliance.ps1
├── Private                       # Fonctions internes (non exportées)
│   ├── Connect-SecureDB.ps1
│   └── Format-ByteSize.ps1
├── Tests                         # Tests Pester
│   ├── Public
│   │   └── Get-ServerInventory.Tests.ps1
│   └── Private
│       └── Format-ByteSize.Tests.ps1
└── Docs                          # Documentation markdown (platyPS)
    └── Get-ServerInventory.md

2. Créer le manifeste (.psd1)

New-ModuleManifest -Path ".TechByHM.Infrastructure.psd1" `
    -RootModule         "TechByHM.Infrastructure.psm1" `
    -ModuleVersion      "1.0.0" `
    -Author             "Hussein Makhzoum" `
    -CompanyName        "TechBy HM" `
    -Description        "Module d'outils d'administration infrastructure Microsoft" `
    -PowerShellVersion  "5.1" `
    -RequiredModules    @("ActiveDirectory", "Az.Compute") `
    -FunctionsToExport  @("Get-ServerInventory", "Export-ADUserReport", "Invoke-PatchCompliance") `
    -Tags               @("Azure", "ActiveDirectory", "Infrastructure", "Administration") `
    -ProjectUri         "https://github.com/hmakhzoum/TechByHM.Infrastructure" `
    -LicenseUri         "https://github.com/hmakhzoum/TechByHM.Infrastructure/blob/main/LICENSE"

3. Loader .psm1

# TechByHM.Infrastructure.psm1
# Charger toutes les fonctions privées
Get-ChildItem -Path "$PSScriptRootPrivate*.ps1" -ErrorAction SilentlyContinue |
    ForEach-Object { . $_.FullName }

# Charger et exporter les fonctions publiques
Get-ChildItem -Path "$PSScriptRootPublic*.ps1" -ErrorAction SilentlyContinue |
    ForEach-Object {
        . $_.FullName
        Export-ModuleMember -Function $_.BaseName
    }

4. Exemple de fonction publique documentée

function Get-ServerInventory {
<#
.SYNOPSIS
    Collecte un inventaire matériel et logiciel d'un ou plusieurs serveurs Windows.

.DESCRIPTION
    Interroge WMI/CIM pour récupérer OS, CPU, RAM, disques et informations réseau.
    Supporte le ciblage multiserveurs en parallèle via PSRemoting.

.PARAMETER ComputerName
    Nom(s) du ou des serveurs à inventorier. Défaut : localhost.

.PARAMETER ExportCsv
    Chemin de sortie du rapport CSV. Si omis, retourne les objets dans le pipeline.

.EXAMPLE
    Get-ServerInventory -ComputerName SRV-WEB01, SRV-DB01 -ExportCsv C:Reportsinv.csv

.EXAMPLE
    $servers = Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=contoso,DC=com" |
               Select-Object -ExpandProperty Name
    Get-ServerInventory -ComputerName $servers
#>
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string[]] $ComputerName = $env:COMPUTERNAME,

        [Parameter()]
        [string] $ExportCsv
    )

    $results = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
        $os   = Get-CimInstance Win32_OperatingSystem
        $cpu  = Get-CimInstance Win32_Processor
        $bios = Get-CimInstance Win32_BIOS
        $nic  = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object IPEnabled

        [PSCustomObject]@{
            Serveur       = $env:COMPUTERNAME
            OS            = $os.Caption
            ServicePack   = $os.ServicePackMajorVersion
            Architecture  = $os.OSArchitecture
            CPU           = $cpu.Name
            NbCoeurs      = $cpu.NumberOfLogicalProcessors
            RAM_Go        = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
            RAM_Libre_Go  = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
            IP            = ($nic.IPAddress | Where-Object { $_ -match "^d+" }) -join ", "
            SerialNumber  = $bios.SerialNumber
            Uptime_Jours  = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1)
        }
    } -ErrorAction SilentlyContinue

    if ($ExportCsv) {
        $results | Export-Csv -Path $ExportCsv -NoTypeInformation -Encoding UTF8
        Write-Host "[OK] Rapport exporté : $ExportCsv"
    } else {
        return $results
    }
}

5. Tests unitaires avec Pester v5

# TestsPublicGet-ServerInventory.Tests.ps1
BeforeAll {
    Import-Module "$PSScriptRoot....TechByHM.Infrastructure.psd1" -Force
}

Describe "Get-ServerInventory" {

    Context "Exécution locale" {
        It "Retourne un objet avec les propriétés attendues" {
            $result = Get-ServerInventory -ComputerName localhost
            $result | Should -Not -BeNullOrEmpty
            $result.Serveur    | Should -Not -BeNullOrEmpty
            $result.RAM_Go     | Should -BeGreaterThan 0
            $result.NbCoeurs   | Should -BeGreaterThan 0
        }

        It "Le nom du serveur correspond à l'environnement local" {
            $result = Get-ServerInventory
            $result.Serveur | Should -Be $env:COMPUTERNAME
        }
    }

    Context "Export CSV" {
        It "Crée le fichier CSV si ExportCsv est fourni" {
            $tmp = [System.IO.Path]::GetTempFileName() -replace ".tmp", ".csv"
            Get-ServerInventory -ExportCsv $tmp
            Test-Path $tmp | Should -BeTrue
            Remove-Item $tmp -Force
        }
    }
}

# Lancer les tests
Invoke-Pester -Path ".Tests" -Output Detailed -PassThru

6. Publier sur PowerShell Gallery

# Vérifier le module avant publication
Test-ModuleManifest ".TechByHM.Infrastructure.psd1"

# Publier (requiert un compte PSGallery et une API Key)
$apiKey = Read-Host "Entrez votre PSGallery API Key" -AsSecureString
Publish-Module -Path ".TechByHM.Infrastructure" `
               -NuGetApiKey (ConvertFrom-SecureString $apiKey -AsPlainText) `
               -Verbose

# Installation depuis n'importe quelle machine
Install-Module -Name TechByHM.Infrastructure -Repository PSGallery -Force

Conclusion

Un module PowerShell bien conçu — avec manifeste, séparation public/privé, tests Pester et documentation — est un actif durable. Partagez vos outils sur PSGallery ou un feed Azure Artifacts privé pour uniformiser les pratiques d'administration de toute votre équipe IT.

MAKHZOUM Hussein
Auteur
MAKHZOUM Hussein
Consultant Cloud & Infrastructure Engineer
Voir le profil

Articles similaires