Cet article s’inscrit dans un objectif de développement CI/CD (Intégration Continue et Distribution Continue). A terme, via Azure DevOps (peut-être Github aussi) il sera possible de mettre à jour tous les modules (qui ont besoin de l’être) après un push ou un merge du repository.

Si vous êtes sysadmin Windows (ou même Linux), vous avez sûrement déjà été amené à utiliser un module Powershell pour une tâche, quelle qu’elle soit. La majorité du temps, on utilise une commande similaire à Install-Module -Name nom_du_module.

On peut aussi utiliser directement un projet Github, on le télécharge ou le clone en local sur son PC puis on utilise Import-Module "chemin_vers_.psd1".

Si vous êtes ici, c’est que vous touchez un peu au Powershell, vous avez sûrement déjà eu besoin de faire votre propre module. C’est que du bonus:

  • On le réutilise à volonté facilement
  • On l’adapte à notre besoin
  • On agrémente tout un tas de fonctions au même endroit et on les rend disponibles en commandline
  • On peut le partager à son équipe

C’est sur ce dernier point que cet article va porter: le partage. Car il n’est pas toujours simple de savoir comment publier son module et on a vite tendance à déposer le dossier avec le PSD1 (ou PSM1) dans un partage réseau commun.

Rappel Extensions :

  • PSD1: Manifest d’un module, on déclare les informations relatives au module: son nom, son auteur, sa version, etc.
  • PSM1: Fichier de module, peut contenir toutes les fonctions hardcodé ou fait le lien avec les fichiers contenant les fonctions du module.
  • PS1: Contient le code Powershell exécutable

⚠️ Avant d’aller plus loin, j’aimerais préciser que je n’ai rien inventé. Ma façon de faire se base sur celle d’un autre: Kamil Procyszyn. Je l’ai ensuite faite évoluée en fonction de mes besoins et mon apprentissage.

ModuleBuilder

Une bonne base de travail. C’est en quelques sortes un concensus sur la méthode pour construire un module. Et c’est également un module en soit, disponible sur PowershellGallery: Install-Module ModuleBuilder

Voir le Github du projet.

Que fait ce module ?

Il va utiliser le PSD1 que vous avez généré, ainsi que les dossiers Public, Private, etc. à côté, pour créer un dossier de module utilisable.

Structure des dossiers

Suivant la convention de ModuleBuilder, une structure de dossiers doit ressembler à l’exemple suivant. Je vais ajouter ma touche pour rendre cette arborescence plus intéressante dans les articles suivants. Mais l’idée est là.

Le dossier principal (Modules) est en fait un “repository” Git. L’idée est de mettre tous ses modules dans le même dépôt.

├── Modules
│   ├── ModuleA
│   │   ├── Source
│   │   │   ├── Private
│   │   │   │   ├── GetFonction1.ps1
│   │   │   │   ├── GetFonction2.ps1
│   │   │   │   ├── GetFonction3.ps1
│   │   │   ├── Public
│   │   │   │   ├── Get-FonctionA.ps1
│   │   │   │   ├── Get-FonctionB.ps1
│   │   │   │   ├── Get-FonctionC.ps1
│   │   │   │   ├── Get-FonctionD.ps1
│   │   │   ├── Classes
│   │   │   ├── Enum
│   │   │   ├── Assets
│   │   │   │   ├── Fichier.xml
│   │   │   ├── ModuleA.psd1
│   │   │   ├── ModuleA.nuspec
│   ├── ModuleB
│   │   ├── Source
│   │   │   ├── Public
│   │   │   ├── ModuleB.psd1
│   │   │   ├── ModuleB.nuspec
└──
  1. Un dossier général qui contient tous les dossiers de module
  2. Un sous-dossier par module
  3. Pour chaque module, un dossier Source qui contient au moins un dossier: Public; et le fichier PSD1 du module

Les dossiers dans Source:

  • Public (requis): Contient les fonctions qui doivent être “publiques”, utilisables une fois que le module a été installé/importé par l’utilisateur.
  • Private: Contient les fonctions qui n’ont pas besoin ou ne doivent pas être utilisables par l’utilisateur final. C’est le “background” du module.
  • Classes: Si vous avez des classes, c’est ici qu’elles doivent être placées.
  • Autres: Vous pouvez ensuite ajouter d’autres dossiers nécessaires à votre module.

Les règles à respecter:

  • Une seule fonction par fichier .ps1
  • Le nom de vos fichiers .ps1 doivent porter le nom de la fonction qu’ils contiennent
  • Le .psd1 doit être généré avec un New-ModuleManifest. Ne pas compléter les parties FunctionsToExport, AliasesToExport, Prerelease, ReleaseNotes, elles seront écrasées par ModuleBuilder. Il doit également porter le nom du module.

Générer le module

Pour générer le module avec la commande Build-Module il faut d’abord se déplacer dans le dossier Source.

cd \dossier\parent\source
Build-Module -SourcePath .\Source\test_ps.psd1 -OutputDirectory ..\Output -CopyPaths .\Assets\ -Version 0.0.1

Pensez à consulter l’aide du module pour voir toutes les possibilités avec un Get-Help Build-Module

Attention aux chemins relatifs si vous utilisez des fichiers dans d’autres dossiers. Une fois compilées les fonctions du module se retrouvent dans le .psm1, exemple ci-après

│   ├── ModuleA
│   │   ├── Output
│   │   │   ├── 0.0.1
│   │   │   │   ├── Assets
│   │   │   │   │   ├── Fichier.xml
│   │   │   │   ├── ModuleA.psd1
│   │   │   │   ├── ModuleA.psm1

A cette étape, on peut d’ores et déjà utiliser le module tel quel. Il suffit de faire un Import-Module et d’indiquer le chemin vers le fichier \Output\ModuleA.PSD1. Cependant, cette méthode est restrictive et comporte le gros inconvéniant présenté au début de ce billet: Difficilement partagable.

On peut donc s’intéresser au plus sympa, le partage !

Nuget

Nuget permet de partager son code avec d’autres, sous forme de package .nupkg contenant votre code et autres fichiers nécessaires. Ces packages sont stockés sur des dépôts (“repository”), par des “providers”.

Pour pouvoir compiler son module en package Nuget, il faut, au même titre que les modules Powershell classiques, créer un manifest. Il se manifeste (oui, j’ai le droit de faire ce genre de vanne!) par un fichier .nuspec, à placer à la racine du dossier Source.

Il y a deux moyens de créer ce fichier:

  1. A la main, via un modèle par défaut (ce que je conseille)
  2. Via la commande nuget spec depuis le dossier Source (je ne recommande pas car de toute façon il faut tout modifier à l’intérieure…)

Dans la convention, son nom doit correspondre au nom de votre module. Exemple de contenu d’un fichier de base:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
    <metadata>
        <id>Test_ps</id>
        <version>0.0.1</version>
        <authors>Cyb3rd0x</authors>
        <projectUrl>https://URL_DE_MON_PROJET</projectUrl>
        <license type="expression">MIT</license>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <releaseNotes>First Release</releaseNotes>
        <description>Description de mon projet super cool.</description>
        <copyright>Copyright ©2023 Cyb3rd0x</copyright>
        <tags>psmodule tag2</tags>
    </metadata>
</package>

Une fois le fichier complété, on génère le package final avec:

cd dossier\ModuleA
nuget pack ".\Source\test_ps.nuspec" -OutputDirectory "Output"

Maintenant, le fichier test_ps.0.0.1.nupkg est disponible dans le dossier Output.

Pour une publication manuelle, il ne reste plus qu’à le publier avec un Publish-Module. Mais, comme décrit au début de l’article, le but ici est d’utiliser de l’intégration et déploiement continue.

🔥 Nous verrons donc dans les prochain articles 🔥 :

  1. Les outils Azure Devops
  2. Créer un pipeline Azure Devops
  3. Créer un feed Azure Devops
  4. Configurer le fichier de pipeline
  5. Configurer l’environnement pour l’exécution du pipeline
  6. Ajouter des éléments à l’arborescence pour rendre le versionning, la mise à jour des modules et packages Nuget automatisés

A bientôt 🚀