diff --git a/.gitignore b/.gitignore index 4d144a8..9418ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ Assets/Rainmeter/ .DS_Store # Notes de reprise locales PimsOS_Reprise_*.txt + +# Local package catalog backups +Config/Packages/*.local-backup.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6cce0..fc8c916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ Le format s'inspire de **Keep a Changelog** et respecte le versionnement sémant --- +## 2026-09-01 — Stabilisation Build / diagnostic / CI + +### Added + +- Outil `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour classifier les tests `SAFE`, `BUILD-CAPABLE` et `UNKNOWN`. +- ADR-0013 sur le diagnostic sécurisé avant exécution. +- Notes de synchronisation documentaire du 01/09/2026. + +### Changed + +- Validation du pipeline réel complet jusqu'à la génération de l'ISO. +- Synchronisation WIM → source ISO vérifiée par SHA256. +- Documentation de continuité renforcée pour les reprises de séance avec ChatGPT/GitHub. +- Workflow PimsOS CI protégé par `concurrency` et filtres de chemins. + +### Fixed + +- Classification du test `Complete-Build.Tests.ps1` : le faux positif `UNKNOWN` a été supprimé après correction du test. +- Détection `oscdimg.exe` confirmée via le Windows ADK sans modification du pipeline de production. + +### Validation + +- Build réel : `Completed`, code retour `0`. +- Integration ciblée : `20 Passed / 0 Failed / 0 Skipped`. +- Diagnostic : `63 Unit / 4 Integration / 3 Build-capable / 0 Unknown` fichiers inventoriés. + +--- + # [Unreleased] ## Added diff --git a/Config/Packages/Chocolatey.json b/Config/Packages/Chocolatey.json index 81e1ab7..25cb983 100644 --- a/Config/Packages/Chocolatey.json +++ b/Config/Packages/Chocolatey.json @@ -3,34 +3,209 @@ "Version": "1.0", "Description": "Catalogue des packages Chocolatey utilisés par PimsOS.", "Packages": [ - {"Id":"vcredist140","Enabled":true,"Category":"Prerequisite"}, - {"Id":"DoNet3.5","Enabled":true,"Category":"Prerequisite"}, - {"Id":"chocolatey","Enabled":true,"Category":"Chocolatey"}, - {"Id":"chocolateygui","Enabled":true,"Category":"Chocolatey"}, - {"Id":"chocolatey-core.extension","Enabled":true,"Category":"Chocolatey"}, - {"Id":"choco-package-list-backup","Enabled":true,"Category":"Chocolatey"}, - {"Id":"googlechrome","Enabled":true,"Category":"Browser"}, - {"Id":"brave","Enabled":true,"Category":"Browser"}, - {"Id":"firefox","Enabled":true,"Category":"Browser"}, - {"Id":"tor-browser","Enabled":true,"Category":"Browser"}, - {"Id":"winrar","Enabled":true,"Category":"Utility"}, - {"Id":"keepass","Enabled":true,"Category":"Utility"}, - {"Id":"notepadplusplus","Enabled":true,"Category":"Utility"}, - {"Id":"vlc","Enabled":true,"Category":"Multimedia"}, - {"Id":"teamviewer","Enabled":true,"Category":"Remote"}, - {"Id":"iCloud","Enabled":true,"Category":"Cloud"}, - {"Id":"everything","Enabled":true,"Category":"Utility"}, - {"Id":"rufus","Enabled":true,"Category":"Utility"}, - {"Id":"filezilla","Enabled":true,"Category":"Network"}, - {"Id":"treesizefree","Enabled":true,"Category":"Utility"}, - {"Id":"bluescreenview","Enabled":true,"Category":"Diagnostic"}, - {"Id":"powertoys","Enabled":true,"Category":"Utility"}, - {"Id":"rainmeter","Enabled":true,"Category":"Desktop"}, - {"Id":"XnViewMP","Enabled":true,"Category":"Graphics"}, - {"Id":"yacreader","Enabled":true,"Category":"Reading"}, - {"Id":"razer-synapse-4","Enabled":true,"Category":"Hardware"}, - {"Id":"autohotkey","Enabled":true,"Category":"Utility"}, - {"Id":"powershell-core","Enabled":true,"Category":"Development"}, - {"Id":"soundblaster-command","Enabled":true,"Category":"Hardware"} + { + "Id": "vcredist140", + "Enabled": true, + "Category": "Prerequisite", + "Mode": "Online", + "Version": "14.51.36247" + }, + { + "Id": "dotnet3.5", + "Enabled": true, + "Category": "Prerequisite", + "Mode": "Online", + "Version": "3.5.20260626" + }, + { + "Id": "chocolatey", + "Enabled": true, + "Category": "Chocolatey", + "Mode": "Offline", + "Version": null + }, + { + "Id": "chocolateygui", + "Enabled": true, + "Category": "Chocolatey", + "Mode": "Online", + "Version": "3.2.0" + }, + { + "Id": "chocolatey-core.extension", + "Enabled": true, + "Category": "Chocolatey", + "Mode": "Online", + "Version": "1.4.0" + }, + { + "Id": "choco-package-list-backup", + "Enabled": true, + "Category": "Chocolatey", + "Mode": "Online", + "Version": "2023.6.28" + }, + { + "Id": "googlechrome", + "Enabled": true, + "Category": "Browser", + "Mode": "Online", + "Version": "153.0.8010.12", + "FailurePolicy": "Continue" + }, + { + "Id": "brave", + "Enabled": true, + "Category": "Browser", + "Mode": "Online", + "Version": null + }, + { + "Id": "firefox", + "Enabled": true, + "Category": "Browser", + "Mode": "Online", + "Version": "154.0.1" + }, + { + "Id": "tor-browser", + "Enabled": true, + "Category": "Browser", + "Mode": "Online", + "Version": "15.0.20" + }, + { + "Id": "winrar", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "7.23.0" + }, + { + "Id": "keepass", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "2.61.1" + }, + { + "Id": "notepadplusplus", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "8.9.8" + }, + { + "Id": "vlc", + "Enabled": true, + "Category": "Multimedia", + "Mode": "Online", + "Version": "3.0.23" + }, + { + "Id": "teamviewer", + "Enabled": true, + "Category": "Remote", + "Mode": "Online", + "Version": "15.81.5" + }, + { + "Id": "iCloud", + "Enabled": true, + "Category": "Cloud", + "Mode": "Online", + "Version": "7.21.0.23" + }, + { + "Id": "everything", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "1.4.11032" + }, + { + "Id": "rufus", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "4.15.0" + }, + { + "Id": "filezilla", + "Enabled": true, + "Category": "Network", + "Mode": "Online", + "Version": "3.71.1" + }, + { + "Id": "treesizefree", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": null + }, + { + "Id": "bluescreenview", + "Enabled": true, + "Category": "Diagnostic", + "Mode": "Online", + "Version": "1.55" + }, + { + "Id": "powertoys", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "0.101.2362" + }, + { + "Id": "rainmeter", + "Enabled": true, + "Category": "Desktop", + "Mode": "Online", + "Version": "4.5.26" + }, + { + "Id": "XnViewMP", + "Enabled": true, + "Category": "Graphics", + "Mode": "Online", + "Version": "1.11.5" + }, + { + "Id": "yacreader", + "Enabled": true, + "Category": "Reading", + "Mode": "Online", + "Version": null + }, + { + "Id": "razer-synapse-4", + "Enabled": true, + "Category": "Hardware", + "Mode": "Online", + "Version": "2.5.0.882" + }, + { + "Id": "autohotkey", + "Enabled": true, + "Category": "Utility", + "Mode": "Online", + "Version": "2.0.26" + }, + { + "Id": "powershell-core", + "Enabled": true, + "Category": "Development", + "Mode": "Online", + "Version": "7.6.5" + }, + { + "Id": "soundblaster-command", + "Enabled": true, + "Category": "Hardware", + "Mode": "Online", + "Version": "3.5.10" + } ] } diff --git a/CurrentSprint.md b/CurrentSprint.md index bb1db7e..133f9fa 100644 --- a/CurrentSprint.md +++ b/CurrentSprint.md @@ -1,482 +1,99 @@ -# État de clôture du sprint — 31/08/2026 - -Le sprint a franchi un jalon important : le pipeline PimsOS a produit une ISO réelle avec succès et le Wizard de personnalisation des Tweaks est maintenant validé par ses tests. - -## Réalisé - -- Pipeline complet exécuté avec succès, code retour `0`. -- Windows 11 Professionnel, index 6 sélectionné. -- Drivers : étape intégrée et validée ; aucun driver à intégrer dans le scénario de build réalisé. -- PostInstall préparé dans le WIM et `unattend.xml` généré. -- Configuration chargée et fusionnée avec le profil `Tests\Registry`. -- Trois Tweaks du profil ont été appliqués dans le Build réel. -- Démontage des ruches et du WIM réussi. -- WIM synchronisé vers la source ISO avec vérification SHA256. -- ISO PimsOS générée avec succès. -- Wizard Tweaks : sélection individuelle, liste et plage validées ; campagne `Wizard.Tests.ps1` à **15/15**. -- Campagne PostInstall/Unattend communiquée : **744/744 passés, 1 skip**. - -## Points restant à fermer - -- Régénérer `Tests\testResults.xml` afin qu’il corresponde à la dernière campagne. -- Réaliser la validation Hyper-V de la **nouvelle ISO du 31/08/2026**, notamment FirstBoot, `Write-Log`, réseau et idempotence. -- Réaliser ensuite la validation physique/Rufus. -- Continuer l’enrichissement du catalogue Tweaks et des profils. -- Finaliser les providers Chocolatey/Winget et les composants encore au backlog. - -## Prochaine séquence - -```text -ISO 3.0.0 générée - ↓ -validation Hyper-V - ↓ -FirstBoot / PostInstall - ↓ -réseau / Write-Log / état - ↓ -validation physique - ↓ -régénération des résultats Pester - ↓ -commit Git -``` - - -# PimsOS Builder - -# Sprint 6 — Stabilisation du moteur de Build +# PimsOS Builder — Sprint courant / point de reprise > Version technique : **3.0.0** > -> Statut : **Architecture stabilisée / développement fonctionnel en cours** -> -> Dernière mise à jour : **2026-08-31** - ---- - -# Objectif du sprint - -Finaliser et stabiliser le cœur du moteur de Build afin de disposer d'un framework fiable, modulaire et extensible permettant de personnaliser des images Windows compatibles. - -Le Sprint 6 a principalement porté sur : - -- la stabilisation de l'architecture du module unique ; -- la consolidation du BuildContext et du BuildState ; -- la stabilisation de la configuration ; -- le développement des Engines spécialisés ; -- le développement des Managers ; -- l'extension de la couverture Pester ; -- la synchronisation de la documentation. - -La génération complète de l'ISO finale reste en cours de finalisation. - ---- - -# Infrastructure - -- [x] Dépôt Git -- [x] Architecture du projet -- [x] Module PowerShell unique -- [x] BuildContext -- [x] BuildState -- [x] Configuration centralisée -- [x] version.json -- [x] Logger -- [x] Recovery -- [x] Validation -- [x] Security -- [x] Service -- [ ] Converters - ---- - -# Core - -- [x] Core -- [x] Workflow -- [x] Pipeline -- [x] ActionRegistry -- [x] ActionEngine -- [x] Complete-Build -- [x] Report -- [x] Engine -- [x] Initialisation du module PimsOS -- [x] Finalisation du Build - ---- - -# Pipeline - -Les principales étapes sont implémentées : - -- [x] Vérification de l'environnement -- [x] Recovery -- [x] Préparation du Workspace -- [x] Gestion de l'ISO -- [x] Détection du WIM -- [x] Copie du WIM -- [x] Lecture des éditions Windows -- [x] Sélection de l'édition -- [x] Montage du WIM -- [x] Gestion des ruches du registre -- [x] Chargement de la configuration -- [x] Validation de la configuration -- [x] Chargement des profils -- [x] Fusion Profil → Configuration -- [x] Exécution des Actions -- [x] Commit des modifications -- [x] Démontage des ruches -- [x] Démontage du WIM -- [x] Nettoyage -- [x] Finalisation du Build - -À compléter : - -- [ ] Validation complète d'un Build de bout en bout -- [ ] Génération complète de l'ISO finale - ---- - -# Configuration - -- [x] Catégories -- [x] Tweaks JSON -- [x] Profils -- [x] Validation -- [x] Sélection des Tweaks -- [x] Fusion Profil + Tweaks -- [x] Configuration finale -- [x] Préservation des définitions sources des Tweaks -- [x] Tests de configuration - ---- - -# Action Engine - -## Architecture - -- [x] ActionEngine -- [x] ActionRegistry -- [x] Routage centralisé des Actions -- [x] Contrat `Context + Action` -- [x] Gestion des erreurs -- [x] Gestion des résultats d'Action -- [x] Mise à jour des statistiques lorsque nécessaire - -## Engines spécialisés - -Tous les Engines actuellement intégrés au module sont implémentés : - -- [x] RegistryEngine -- [x] ServiceEngine -- [x] PackageEngine -- [x] DriverEngine -- [x] FeatureEngine -- [x] CapabilityEngine -- [x] CommandEngine -- [x] FileEngine -- [x] FolderEngine -- [x] EnvironmentEngine -- [x] ScheduledTaskEngine -- [x] ShortcutEngine - -La stabilisation et l'extension des tests se poursuivent. - ---- - -# Managers - -Les Managers suivants sont désormais implémentés : - -- [x] PackageManager -- [x] DriverManager -- [x] FeatureManager -- [x] CapabilityManager -- [x] CommandManager -- [x] FileManager -- [x] FolderManager -- [x] EnvironmentManager -- [x] ScheduledTaskManager -- [x] ShortcutManager - -Les contrats de providers comprennent notamment : - -- validation des paramètres ; -- résolution du provider ; -- résolution du handler ; -- exécution ; -- enregistrement de providers lorsque prévu ; -- réinitialisation pour les tests lorsque prévu. - -Les tests unitaires des Managers concernés sont en place et en progression. - ---- - -# Registry - -- [x] Registry.ps1 -- [x] Chargement des ruches -- [x] Création des clés -- [x] Création des valeurs -- [x] Gestion des types -- [x] Validation -- [x] Journalisation -- [x] Tests du Registry - ---- - -# Package - -Les providers sont intégrés dans l'organisation du framework : +> Dernière mise à jour : **2026-09-02** + +## État confirmé + +- [x] Architecture du module PowerShell unique stabilisée. +- [x] BuildContext / BuildState / Workflow / Pipeline stabilisés. +- [x] 27 Tweaks chargés et appliqués. +- [x] Drivers `CurrentSystem` exportés et injectés dans le WIM par DISM. +- [x] Chocolatey bootstrap Offline obligatoire au Build. +- [x] Chocolatey Online après Network + DriverCheck. +- [x] `FailurePolicy=Stop|Continue` implémentée et testée. +- [x] `FailurePolicy=Continue` validée en VM sur l’échec Google Chrome. +- [x] PostInstall / FirstLogon / State validés en VM. +- [x] Finalization / Cleanup différé validés en VM. +- [x] Microsoft Store / iCloud / Widgets vérifiés en VM. +- [x] Diagnostic sécurisé : **815 Passed / 0 Failed / 1 Skipped**. +- [x] Build réel : code retour **0**. + +## Preuve Build ```text -Modules\Package -├── Chocolatey.ps1 -└── Winget.ps1 +ISO : Output\PimsOS_3.0.0_20260902_141928.iso +Taille : 11,29 Go +WIM SHA256 : B6BA0B8E8474761380FCC26DB165DC786162EA916B8D35192A977C49E72E9941 +Build ID : 6302b96b-2cd1-4ba7-b66e-dc8b9980eebe +Index : 6 — Windows 11 Professionnel ``` -État actuel : - -- [ ] Provider Chocolatey finalisé -- [ ] Provider Winget finalisé -- [x] PackageManager -- [x] PackageEngine -- [ ] Validation complète d'un scénario Package de bout en bout - ---- - -# Image - -- [x] Dism.ps1 -- [x] Iso.ps1 -- [x] Wim.ps1 -- [x] Détection de l'ISO -- [x] Détection du WIM -- [x] Lecture des éditions -- [x] Sélection de l'édition -- [x] Montage du WIM -- [x] Démontage du WIM -- [x] Nettoyage des ressources -- [ ] Reconstruction finale complète de l'ISO - ---- - -# Support Windows - -- [x] Windows 11 25H2 comme environnement de référence -- [x] Build Windows 26100 comme référence actuelle -- [x] Découverte dynamique de l'image Windows -- [x] Sélection de l'édition présente dans le WIM -- [x] Centralisation des métadonnées dans `version.json` -- [x] Préparation du support multi-version - -À compléter : - -- [ ] Validation automatique complète de compatibilité des Tweaks -- [ ] Extension à d'autres versions de Windows compatibles - ---- - -# Reporting - -- [x] Report -- [x] Intégration au BuildContext -- [x] Collecte des résultats du Build -- [x] Collecte des erreurs et avertissements - -À enrichir : - -- [ ] Reporting HTML -- [ ] Reporting JSON complet -- [ ] Reporting PDF -- [ ] Statistiques détaillées -- [ ] Rapport final enrichi - ---- - -# Tests - -## Couverture actuelle - -- [x] Tests du module / architecture -- [x] Tests BuildContext -- [x] Tests Configuration -- [x] Tests ActionRegistry -- [x] Tests ActionEngine -- [x] Tests Engines spécialisés -- [x] Tests Managers -- [x] Tests Registry -- [x] Tests de régression sur les corrections récentes - -## À renforcer - -- [ ] Couverture complémentaire de Recovery -- [ ] Couverture complémentaire de Security -- [ ] Tests d'intégration du Pipeline -- [ ] Validation complète d'un Build de bout en bout -- [ ] Couverture de code -- [ ] Publication automatisée des résultats Pester dans la CI - ---- - -# Documentation +## Preuve Pester -Documentation principale mise à jour pour l'état technique actuel : - -- [x] Architecture -- [x] Architecture Rules -- [x] BuildContext -- [x] API -- [x] Lifecycle -- [x] Milestones -- [x] Roadmap -- [x] ProjectStatus -- [x] ProjectStructure -- [x] Coding Standards -- [x] Developer Guide -- [x] Getting Started -- [x] Prerequisites -- [x] Schema -- [x] Legacy -- [x] Module Guide -- [x] Testing -- [x] Technical Decisions -- [x] Release Notes -- [x] ADR -- [x] CONTRIBUTING -- [x] CHANGELOG - ---- - -# GitHub / qualité - -- [x] Dépôt Git -- [x] Workflow GitHub Actions -- [x] CodeQL -- [x] Dependabot -- [x] Badges GitHub -- [x] Templates Issue -- [x] Template Pull Request - -À renforcer : - -- [ ] PSScriptAnalyzer dans la CI -- [ ] Publication automatisée des résultats Pester -- [ ] Couverture de code -- [ ] Workflow de Release - ---- - -# Objectif immédiat - -Les priorités techniques sont désormais : - -1. Finaliser la couverture des composants critiques. -2. Renforcer les tests Recovery et Security. -3. Finaliser les providers Package lorsque leur implémentation est prête. -4. Enrichir le Reporting. -5. Valider un scénario de Build complet de bout en bout. -6. Finaliser la génération de l'ISO. - ---- - -# Objectif de la prochaine version - -## Version 3.1.x - -Objectifs possibles : - -- augmentation de la couverture de tests ; -- amélioration du Reporting ; -- finalisation des providers nécessaires ; -- amélioration des scénarios d'intégration ; -- stabilisation supplémentaire du Pipeline ; -- amélioration de la génération d'artefacts. - ---- - -# Objectif à moyen terme - -## Version 4.x - -Objectifs : +```text +Rapport : Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md +815 Passed +0 Failed +1 Skipped +0 Inconclusive +0 NotRun +816 Total +``` -- génération d'image Windows entièrement automatisée ; -- génération finale d'ISO robuste ; -- validation complète des Builds ; -- reporting complet ; -- support élargi des versions Windows compatibles ; -- préparation d'une release publique stable. +Le `Skipped` est conditionnel et intentionnel. Ne pas le traiter comme un défaut. ---- +## Preuve VM Finalization -# État du sprint +Le `state.json` final indique : -Le Sprint 6 ne correspond plus à une phase où les Engines et Managers sont encore à développer. +```text +Status = Completed +Completed = true +Failed = false +Verification.Verified = true +Cleanup.Status = Scheduled +Cleanup.Scheduled = true +``` -L'architecture et les principaux composants sont désormais en place. +Le nettoyage a supprimé les scripts temporaires et `C:\Windows\Panther\unattend.xml`. Les éléments suivants sont conservés : `state.json`, `PostInstall.log`, `Chocolatey\`. -Le travail restant porte principalement sur : +## Anomalie connue -```text -Stabilisation - ↓ -Tests - ↓ -Validation End-to-End - ↓ -Reporting - ↓ -Génération ISO - ↓ -Release -``` +`googlechrome` peut échouer sur son checksum lorsque l’artefact servi par Google évolue avant la mise à jour du package Chocolatey. `FailurePolicy=Continue` permet de poursuivre sans désactiver les contrôles de sécurité. Aucun `--ignore-checksums` ne doit être ajouté. ---- +Chrome n’est pas bloquant pour le projet : Firefox s’installe correctement et reste le navigateur de référence du catalogue actuel. -# Conclusion +## Prochaine séquence -Le Sprint 6 a permis de faire évoluer PimsOS Builder d'une architecture en construction vers un framework techniquement structuré et largement fonctionnel. +### 1. Git — immédiat -Les principaux composants du moteur sont désormais présents : +- Inspecter `git status`. +- Vérifier les fichiers réellement modifiés depuis le dernier commit. +- Vérifier le remote et la branche. +- Examiner les artefacts à exclure (`Output`, `Workspace`, logs générés, rapports temporaires) selon `.gitignore`. +- Créer un commit cohérent regroupant la stabilisation technique et la documentation. +- Push vers `origin/main` après vérification. -- BuildContext ; -- BuildState ; -- Workflow ; -- Pipeline ; -- Configuration ; -- ActionRegistry ; -- ActionEngine ; -- Engines spécialisés ; -- Managers ; -- Registry ; -- Image ; -- Infrastructure. +### 2. Qualité -La prochaine étape n'est plus la construction de l'architecture fondamentale, mais sa **validation complète, son enrichissement et sa finalisation opérationnelle**. +- Régénérer `Tests\testResults.xml` si nécessaire. +- Conserver le rapport diagnostic 02/09 comme preuve de référence. -## PostInstall / FirstBoot — état au 2026-08-27 +### 3. Produit -### Validé +- Validation physique / Rufus. +- Audit réel des packages Chocolatey destinés au futur mode Offline. +- Développer Winget. +- Compléter Recovery / Security / Reporting. -- [x] State.ps1 -- [x] Network.ps1 -- [x] PostInstall.ps1 -- [x] Bootstrap.ps1 -- [x] FirstBoot.ps1 -- [x] Unattend.ps1 -- [x] Installer.ps1 -- [x] tests unitaires et d'intégration -- [x] intégration de `PreparePostInstall` dans le pipeline -- [x] injection réelle du runtime dans un WIM -- [x] génération réelle de `unattend.xml` +## Règle de reprise -### À faire +À chaque nouvelle session PimsOS : -- [ ] tester l'exécution réelle de FirstLogonCommands -- [ ] valider le premier démarrage dans une VM -- [ ] valider la reprise après disponibilité réseau -- [ ] intégrer Chocolatey -- [ ] intégrer Winget -- [ ] intégrer Microsoft Store +1. lire ce fichier ; +2. lire `Documentation/DocumentationSync-2026-09-02.md` ; +3. lire `Documentation/ProjectStatus.md` ; +4. vérifier `git status` ; +5. ne pas relancer une campagne Pester globale sans passer d’abord par le diagnostic sécurisé. diff --git a/Documentation/ADR/ADR-0013-SafeDiagnostics.md b/Documentation/ADR/ADR-0013-SafeDiagnostics.md new file mode 100644 index 0000000..69e3519 --- /dev/null +++ b/Documentation/ADR/ADR-0013-SafeDiagnostics.md @@ -0,0 +1,60 @@ +# ADR-0013 — Diagnostic sécurisé avant exécution des tests + +- **Statut : Accepted** +- **Date : 2026-09-01** +- **Version : 3.0.0** + +## Contexte + +PimsOS possède des tests unitaires et d'intégration dont certains peuvent appeler directement ou indirectement des opérations de Build, WIM, DISM ou ISO. + +Un lancement aveugle de toute la suite peut donc provoquer un Build réel, un montage WIM ou une génération d'ISO alors qu'une simple validation de code était demandée. + +La connexion du dépôt à GitHub et l'exécution CI renforcent également le besoin de distinguer clairement les tests automatisables des validations de Build réel. + +## Décision + +Le dépôt utilise `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` comme garde-fou statique avant les campagnes ciblées. + +Le diagnostic classe les fichiers en trois catégories : + +- `SAFE` ; +- `BUILD-CAPABLE` ; +- `UNKNOWN`. + +Les catégories `BUILD-CAPABLE` et `UNKNOWN` sont exclues des modes normaux `Unit` et `Integration`. + +Une validation réelle du Build exige deux éléments explicites : + +```powershell +-BuildValidation -AllowBuild +``` + +L'inventaire seul ne doit jamais exécuter de test. + +## Raisons + +- éviter un Build réel involontaire ; +- rendre le risque visible avant exécution ; +- conserver une séparation claire entre tests automatisés et validation destructive ; +- faciliter le diagnostic lorsqu'un test est classé comme dangereux ; +- permettre une reprise de session reproductible. + +## Conséquences + +### Positives + +- réduction du risque de montage WIM/ISO accidentel ; +- distinction explicite entre validation de code et validation du produit ; +- rapports Markdown/JSON historisés ; +- méthode utilisable par le développeur et par ChatGPT. + +### Acceptées + +L'analyse est statique et conservatrice. Un test réellement sûr peut être classé `BUILD-CAPABLE` si le scanner ne dispose pas d'une preuve suffisante de neutralisation. + +Cette prudence est volontaire : un faux positif est préférable à l'exécution involontaire d'un Build. + +## Règle d'évolution + +Toute amélioration du scanner doit réduire les faux positifs sans affaiblir la règle de sécurité. Une modification de classification doit être accompagnée d'un test démontrant que les opérations de Build restent bloquées lorsqu'elles ne sont pas explicitement autorisées. diff --git a/Documentation/ADR/ADR-0014-SafeTestClassification.md b/Documentation/ADR/ADR-0014-SafeTestClassification.md new file mode 100644 index 0000000..d78e7f3 --- /dev/null +++ b/Documentation/ADR/ADR-0014-SafeTestClassification.md @@ -0,0 +1,171 @@ +# ADR-0014 — Classification et sécurité des tests PimsOS + +- **Statut :** Accepté +- **Date :** 2026-09-01 +- **Décision :** classification explicite des tests selon leur capacité à déclencher un Build réel + +--- + +## Contexte + +PimsOS possède des tests unitaires et des tests d'intégration qui peuvent référencer des opérations sensibles du Builder. + +Certaines opérations peuvent modifier, monter, démonter ou générer des ressources réelles : + +- `Initialize-PimsOS` +- `Invoke-BuildPipeline` +- `Complete-Build` +- `Mount-Wim` +- `Mount-WindowsImage` +- `Dismount-Wim` +- `oscdimg` + +Une simple recherche textuelle ne permet pas toujours de déterminer si un appel est réellement neutralisé par Pester, TestDrive, un faux outil ou un mode DryRun. + +Il est donc nécessaire de distinguer la sécurité d'exécution des tests de leur nature fonctionnelle. + +--- + +## Décision + +Le diagnostic PimsOS classe chaque fichier de test dans trois catégories. + +### SAFE + +Un test est `SAFE` lorsque le diagnostic ne détecte aucun appel Build réel non neutralisé. + +Il peut être exécuté dans les campagnes Unit et Integration normales. + +`SAFE` signifie que le scanner possède suffisamment de preuves pour considérer les opérations dangereuses comme neutralisées ou absentes. + +### BUILD-CAPABLE + +Un test est `BUILD-CAPABLE` lorsqu'il contient un appel qui peut réellement déclencher une opération Build. + +Exemples : + +- un test d'intégration appelant réellement `Initialize-PimsOS` ; +- un test appelant réellement `Invoke-BuildPipeline`. + +Cette classification ne signifie pas que le test est mauvais. + +Elle signifie que son exécution doit être explicitement contrôlée. + +Les tests `BUILD-CAPABLE` ne sont sélectionnés que par le mode : + +```powershell +-BuildValidation -AllowBuild +``` + +### UNKNOWN + +Un test est `UNKNOWN` lorsque le diagnostic détecte une opération potentiellement dangereuse mais ne dispose pas de preuves suffisantes pour établir sa neutralisation. + +`UNKNOWN` est donc une catégorie de sécurité conservatrice. + +Un test `UNKNOWN` est bloqué par défaut, y compris lors d'une BuildValidation. + +Il doit être analysé et reclassé avant de pouvoir devenir exécutable. + +--- + +## Règles d'exécution + +| Mode | SAFE | BUILD-CAPABLE | UNKNOWN | +|---|---:|---:|---:| +| Unit | Oui | Non | Non | +| Integration | Oui | Non | Non | +| BuildValidation | Non | Oui | Non | + +`-AllowBuild` autorise le mode BuildValidation, mais ne transforme pas automatiquement les tests `UNKNOWN` en tests exécutables. + +--- + +## Principe de sécurité + +Le diagnostic doit préférer une classification trop prudente à une classification trop permissive. + +En cas de doute : + +```text +UNKNOWN +``` + +plutôt que : + +```text +SAFE +``` + +Cela protège contre l'exécution accidentelle d'un Build réel depuis une campagne de tests normale. + +--- + +## Cas particulier : Initialize-PimsOS + +`Initialize-PimsOS` constitue l'API publique du Builder et peut déclencher le pipeline réel. + +Un test d'intégration qui l'appelle réellement est donc `BUILD-CAPABLE`. + +Un Mock Pester explicite permettant de neutraliser l'appel peut toutefois maintenir le test dans une classification sûre lorsque le diagnostic peut l'établir. + +Cette distinction évite de confondre : + +- test de l'API ; +- test d'intégration réel ; +- exécution accidentelle du Build. + +--- + +## Outil de diagnostic + +La classification est produite par : + +```text +Tests/Tools/Invoke-PimsOSDiagnostics.ps1 +``` + +L'outil produit également des rapports Markdown et JSON permettant de conserver une trace de l'inventaire et des décisions de classification. + +--- + +## Conséquences + +### Positives + +- réduit le risque de lancer un Build réel par erreur ; +- rend explicite le niveau de danger des tests ; +- permet de séparer les tests ordinaires des validations réelles ; +- fournit un inventaire reproductible ; +- facilite la reprise de session et le diagnostic. + +### Contraintes + +- un nouveau test utilisant une opération sensible peut apparaître `UNKNOWN` ou `BUILD-CAPABLE` ; +- il faut alors examiner le test plutôt que désactiver arbitrairement la protection ; +- les tests réellement intégratifs doivent rester identifiables comme tels. + +--- + +## Alternatives rejetées + +### Exécuter tous les tests sans distinction + +Rejeté : un test d'intégration peut déclencher une opération Build réelle. + +### Autoriser automatiquement les appels détectés + +Rejeté : la détection seule ne constitue pas une preuve de sécurité. + +### Classer tous les tests suspects comme SAFE + +Rejeté : cela rendrait le garde-fou inutile. + +--- + +## Références + +- `Documentation/Testing.md` +- `Documentation/ChatGPT-Workflow.md` +- `Documentation/SessionChecklist.md` +- `Tests/Tools/Invoke-PimsOSDiagnostics.ps1` diff --git a/Documentation/ADR/README.md b/Documentation/ADR/README.md index 90ff246..cf5f5c3 100644 --- a/Documentation/ADR/README.md +++ b/Documentation/ADR/README.md @@ -4,7 +4,7 @@ > > Statut : Référence > -> Dernière mise à jour : 2026-08-28 +> Dernière mise à jour : 2026-09-01 --- @@ -83,6 +83,7 @@ La conservation de l'historique est prioritaire sur la suppression d'une ancienn | ADR-0010 | Cycle de vie du BuildContext | Accepted | | ADR-0011 | Contrats d'interface entre composants | Accepted | | ADR-0012 | Module PowerShell unique | Accepted | +| ADR-0013 | Diagnostic sécurisé avant exécution des tests | Accepted | --- diff --git a/Documentation/Architecture.md b/Documentation/Architecture.md index fa6e66f..472bd70 100644 --- a/Documentation/Architecture.md +++ b/Documentation/Architecture.md @@ -1,1475 +1,538 @@ -# PimsOS Builder - Architecture +# PimsOS Builder - Structure du projet -> Documentation de l'architecture logicielle -> > Version technique : 3.0.0 > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 - ---- - -# Sommaire - -- Présentation -- État architectural actuel -- Philosophie -- Objectifs -- Principes d'architecture -- Architecture du module PimsOS -- Vue d'ensemble -- Architecture logique -- Les couches du Builder -- Engines spécialisés -- Managers -- Modules techniques -- Flux d'exécution -- Communication entre les composants -- Dépendances -- BuildContext -- ActionRegistry -- Recovery -- Décisions d'architecture -- Documentation associée -- Conclusion +> Dernière mise à jour : 2026-09-02 --- -# Présentation - -PimsOS Builder est un framework PowerShell permettant de construire et de personnaliser des images Windows à partir d'images sources compatibles. - -Le projet automatise progressivement les différentes étapes du processus de Build tout en conservant une architecture : - -- modulaire ; -- testable ; -- extensible ; -- maintenable ; -- indépendante d'une version précise de Windows ciblée. - -Le Builder ne doit pas modifier directement les ressources sources lorsqu'une copie de travail est nécessaire. +# Objectif -Les opérations de modification sont réalisées dans l'environnement de travail du Build afin de préserver les ressources sources. +Ce document décrit l'organisation réelle du projet **PimsOS Builder**. ---- - -# État architectural actuel - -La version technique actuelle du framework est : +Le projet est structuré autour d'un module PowerShell unique : ```text -3.0.0 +Modules\PimsOS.psm1 ``` -L'architecture est considérée comme stabilisée. - -Le framework dispose notamment de : - -- un module PowerShell unique ; -- un BuildContext centralisé ; -- un BuildState ; -- un Workflow ; -- un Pipeline ; -- un ActionRegistry ; -- un ActionEngine ; -- des Engines spécialisés ; -- des Managers spécialisés ; -- des composants techniques pour les images, le registre et l'environnement ; -- une couverture de tests Pester importante. - -Le pipeline prend notamment en charge : - -- la préparation de l'environnement ; -- la vérification des prérequis ; -- la gestion des ressources ISO et WIM ; -- le chargement de la configuration ; -- la sélection et la fusion des profils et Tweaks ; -- la validation de la configuration ; -- l'exécution des Actions via les Engines spécialisés ; -- le nettoyage et la finalisation du Build. - -La génération complète de l'ISO finale ainsi que certains providers et composants restent en cours de finalisation. - -L'architecture est conçue pour permettre l'ajout de nouveaux types d'Actions avec un impact limité sur les composants existants. - ---- - -# Philosophie - -L'architecture de PimsOS repose sur quelques principes simples qui guident l'ensemble du développement. - -## Responsabilité unique - -Chaque composant possède une responsabilité clairement définie. - -Un composant ne doit pas réaliser plusieurs responsabilités indépendantes. - ---- - -## Modularité - -Les fonctionnalités sont isolées dans des composants spécialisés. - -L'ajout d'une nouvelle fonctionnalité doit rester aussi localisé que possible. - ---- - -## Séparation des responsabilités - -Chaque couche possède un rôle précis. - -Les Engines portent la logique métier de leur domaine. - -Les Managers et modules techniques encapsulent les opérations techniques. - ---- - -## Testabilité - -Les composants doivent pouvoir être testés indépendamment lorsque cela est pertinent. - -Les tests unitaires font partie intégrante du développement. - ---- - -## Extensibilité - -L'architecture doit permettre l'ajout de nouveaux Engines, Managers, providers et types d'Actions sans modifier inutilement les composants existants. - ---- - -## Maintenabilité - -Le projet privilégie : - -- un code lisible ; -- des contrats clairs ; -- une documentation synchronisée ; -- une architecture cohérente ; -- des changements localisés. +Le module centralise le chargement des composants internes et l'exposition de l'API publique du framework. --- -# Objectifs - -Le développement de PimsOS poursuit plusieurs objectifs. - -## Construire une image Windows personnalisée - -Créer automatiquement une image adaptée aux besoins de l'utilisateur. +# Structure générale ---- - -## Industrialiser les personnalisations +```text +PimsOS +│ +├── Build +├── Config +├── Documentation +├── ISO +├── Logs +├── Modules +├── Output +├── Packages +├── Tests +├── Workspace +├── version.json +└── README.md +``` -Les personnalisations sont décrites sous forme de données de configuration. +Le projet est organisé de manière à séparer clairement : -Aucune logique métier exécutable ne doit être placée dans les fichiers JSON. +- le code du framework ; +- la configuration ; +- les ressources de Build ; +- les tests ; +- la documentation ; +- les artefacts et espaces de travail. --- -## Garantir la reproductibilité - -Deux Builds réalisés avec les mêmes données, les mêmes paramètres et le même environnement compatible doivent viser un résultat reproductible. +# Modules ---- - -## Faciliter la maintenance +Le code du framework se trouve dans : -Les composants du framework sont séparés par responsabilité. +```text +Modules\ +``` -Les évolutions doivent pouvoir être réalisées avec un impact limité sur le reste du projet. +La structure principale est : ---- +```text +Modules +│ +├── Actions +├── Configuration +├── Core +├── Image +├── Infrastructure +├── Managers +├── Package +├── PostInstall +├── Windows +├── PimsOS.psd1 +└── PimsOS.psm1 +``` -## Centraliser les échanges +Les composants internes appartiennent au module PowerShell unique **PimsOS**. -Les informations partagées pendant le Build transitent par un BuildContext unique. +Ils ne constituent pas des modules PowerShell indépendants. --- -# Principes d'architecture +# PostInstall -L'architecture de PimsOS repose notamment sur les principes suivants : - -- module PowerShell unique ; -- API publique minimale ; -- BuildContext centralisé ; -- BuildState centralisé pour l'état d'exécution ; -- séparation Workflow / Pipeline ; -- ActionRegistry pour le routage ; -- séparation Engine / Manager ; -- modules techniques spécialisés ; -- dépendances descendantes ; -- absence de dépendance circulaire ; -- validation des données avant exécution ; -- journalisation centralisée ; -- tests automatisés. - -Les règles détaillées sont définies dans : +Le projet contient désormais un sous-système dédié à l'exécution après l'installation de Windows : ```text -Documentation\ArchitectureRules.md +Modules\PostInstall\ ``` ---- - -# Architecture du module PimsOS +Ce dossier contient le runtime PostInstall embarqué dans l'image Windows. -L'architecture de PimsOS repose sur un module PowerShell unique. - -Le projet n'est pas constitué d'une collection de modules PowerShell indépendants, mais d'un framework unique composé de composants internes. - -Cette organisation simplifie les dépendances, facilite le chargement et permet de conserver une API publique cohérente. - ---- - -## Module unique - -Le module public du projet est : +Les composants actuellement présents comprennent notamment : ```text -PimsOS +Bootstrap.ps1 +FirstBoot.ps1 +Installer.ps1 +Network.ps1 +PostInstall.ps1 +State.ps1 +UI.ps1 +Unattend.ps1 ``` -Tous les composants internes appartiennent à ce module. +Le runtime est préparé pendant le Build puis installé dans l'image Windows sous : ---- - -## Rôle de PimsOS.psd1 - -Le manifeste décrit le module. +```text +C:\ProgramData\PimsOS\PostInstall\ +``` -Il contient notamment : +Le sous-système PostInstall prend notamment en charge : -- les métadonnées ; -- la version ; -- les paramètres du module ; -- l'identification du module. +- l'initialisation du runtime ; +- la persistance de l'état ; +- la détection du réseau ; +- la détection de l'accès Internet ; +- l'attente de la disponibilité réseau ; +- l'interface console du premier démarrage ; +- la préparation FirstBoot ; +- la génération de `unattend.xml`. -Il ne contient pas la logique métier du Builder. +Le PostInstall reste séparé de la logique du Build exécutée hors ligne. --- -## Rôle de PimsOS.psm1 +# Diagnostic avant tests -`PimsOS.psm1` constitue le point d'entrée du framework. +Avant une campagne Pester, utiliser `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour vérifier que les fichiers sélectionnés ne sont pas susceptibles de lancer un Build réel. Les validations `BUILD-CAPABLE` nécessitent explicitement `-BuildValidation -AllowBuild`. -Il est responsable : +# Tests -- du chargement des composants internes ; -- de l'initialisation du framework ; -- de l'exposition de l'API publique. +Les tests du framework sont organisés dans : -L'API publique actuelle est volontairement minimale et expose : - -```powershell -Initialize-PimsOS +```text +Tests\ ``` ---- - -## Organisation des composants +La structure comprend notamment : ```text -PimsOS +Tests │ -├── Build -├── Config -├── Documentation -├── ISO -├── Logs -├── Modules -│ ├── Actions -│ ├── Configuration -│ ├── Core -│ ├── Image -│ ├── Infrastructure -│ ├── Managers -│ ├── Package -│ ├── Windows -│ ├── PimsOS.psd1 -│ └── PimsOS.psm1 -├── Output -├── Tests -├── Workspace -└── version.json +├── Unit +├── Integration +├── Acceptance +└── Legacy ``` ---- - -## API publique +Les tests Legacy sont conservés séparément et ne participent pas à la validation officielle du framework actif. -Les fonctions publiques sont exportées uniquement depuis `PimsOS.psm1`. - -Les composants internes ne doivent pas définir leur propre API publique. +--- -Les fonctions internes ne deviennent pas publiques simplement parce qu'elles sont chargées dans le module. +# Tests PostInstall ---- +Les tests dédiés au sous-système PostInstall sont regroupés dans : -## Composants internes +```text +Tests\Unit\Modules\PostInstall\ +``` -Les composants internes comprennent notamment : +Ils couvrent notamment : -- Core ; -- Configuration ; -- Infrastructure ; -- Image ; -- Actions ; -- Managers ; -- Package ; -- Windows. +```text +Bootstrap.Tests.ps1 +FirstBoot.Tests.ps1 +Installer.Tests.ps1 +Network.Tests.ps1 +PostInstall.Tests.ps1 +State.Tests.ps1 +UI.Tests.ps1 +Unattend.Tests.ps1 +``` -Ils ne sont pas destinés à être importés individuellement. +Les tests PostInstall vérifient les composants individuellement ainsi que leurs contrats fonctionnels. -Ils sont chargés par `PimsOS.psm1` et constituent les composants internes du module PimsOS. +L'intégration du PostInstall dans le pipeline est également couverte par les tests d'intégration du BuildPipeline. --- -# Architecture en couches - -Chaque couche possède une responsabilité unique. +# Architecture des dépendances -Une couche ne doit pas contourner inutilement une autre couche. +Le framework suit une architecture en couches. -Le flux logique principal est : +Le flux principal est : ```text +Infrastructure + │ + ▼ +Core + │ + ▼ +Configuration + │ + ▼ Workflow - │ - ▼ + │ + ▼ Pipeline - │ - ▼ + │ + ▼ ActionEngine - │ - ▼ + │ + ▼ ActionRegistry - │ - ▼ + │ + ▼ Engine spécialisé - │ - ▼ + │ + ▼ Manager - │ - ▼ + │ + ▼ Module technique - │ - ▼ + │ + ▼ Windows ``` -Les composants Core et Infrastructure fournissent les services nécessaires autour de ce flux. +Le PostInstall constitue un sous-système runtime distinct du chemin d'exécution principal du Build. ---- - -# Vue d'ensemble - -Le fonctionnement général de PimsOS peut être résumé ainsi : +Sa préparation est intégrée au Pipeline : ```text -Utilisateur - │ - ▼ -Build-PimsOS.ps1 - │ - ▼ -Import-Module PimsOS +MountWim │ ▼ -PimsOS.psm1 +ApplyDrivers │ ▼ -Initialize-PimsOS +PreparePostInstall │ ▼ -BuildContext - │ - ├───────────────┬────────────────┐ - ▼ ▼ ▼ -Infrastructure Core Configuration - │ │ │ - └───────────────┴────────────────┘ - │ - ▼ - Workflow - │ - ▼ - Pipeline - │ - ▼ - ActionEngine - │ - ▼ - ActionRegistry - │ - ▼ - Engine spécialisé - │ - ▼ - Manager - │ - ▼ - Module technique - │ - ▼ - Windows +MountSoftwareHive ``` -Cette organisation permet : +--- -- un faible couplage entre composants ; -- une forte modularité ; -- une bonne testabilité ; -- une maintenance simplifiée ; -- une extensibilité maîtrisée. +# Build ---- +Le dossier : -# Architecture logique +```text +Build\ +``` -PimsOS Builder est organisé selon une architecture en couches. +contient les scripts utilisés pour lancer et orchestrer le processus de Build. -Chaque couche possède une responsabilité clairement définie. +Le lanceur principal est : ```text -Utilisateur - │ - ▼ -Initialize-PimsOS - │ - ▼ -BuildContext - │ - ▼ -Workflow - │ - ▼ -Pipeline - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique - │ - ▼ -Windows +Build\Build-PimsOS.ps1 ``` -Cette organisation garantit : +Le Build prépare notamment : -- une séparation stricte des responsabilités ; -- un faible couplage ; -- une forte cohésion ; -- une bonne testabilité. +- l'environnement ; +- les ressources ISO et WIM ; +- les drivers ; +- le runtime PostInstall ; +- FirstBoot ; +- la configuration ; +- les personnalisations ; +- le nettoyage ; +- la finalisation. --- -# Les couches du Builder +# Configuration -## Entrée du Builder +La configuration du Builder se trouve dans : -Le script de lancement prépare l'environnement nécessaire au démarrage du framework. +```text +Config\ +``` -Le point d'entrée fonctionnel du module reste : +Elle contient notamment : -```powershell -Initialize-PimsOS +```text +Categories.json +Profiles\ +Tweaks\ ``` ---- +Les profils sélectionnent les personnalisations et les Tweaks définissent les Actions à exécuter. -## Builder +La configuration reste séparée du code PowerShell. -Le Builder est implémenté au sein du module PimsOS et constitue l'orchestrateur principal du projet. +--- -Il prépare l'environnement avant le lancement du Workflow. +# Documentation -Il est notamment responsable de : +La documentation technique se trouve dans : -- l'initialisation du projet ; -- la création du BuildContext ; -- l'initialisation du Logger ; -- la vérification de l'environnement ; -- le démarrage du Workflow ; -- la finalisation du Build. +```text +Documentation\ +``` -Le Builder ne réalise pas directement les opérations techniques Windows. +Elle comprend notamment : ---- +```text +API.md +Architecture.md +ArchitectureRules.md +BuildContext.md +DeveloperGuide.md +GettingStarted.md +Legacy.md +Lifecycle.md +Milestones.md +ModuleGuide.md +PostInstall.md +Prerequisites.md +ProjectStatus.md +ProjectStructure.md +ReleaseNotes.md +Roadmap.md +Schema.md +TechnicalDecisions.md +Testing.md +``` -## Workflow +Les décisions d'architecture sont documentées dans : -Le Workflow décrit les grandes phases d'un Build. +```text +Documentation\ADR\ +``` -Il reste principalement déclaratif et ne doit pas contenir de logique technique détaillée. +--- -La phase Recovery intervient en amont afin d'identifier et de traiter les ressources éventuellement laissées par un Build précédent. +# ISO -Exemple : +Le dossier : ```text -Recovery - │ - ▼ -Environment - │ - ▼ -ISO - │ - ▼ -WIM - │ - ▼ -Registry - │ - ▼ -Configuration - │ - ▼ -Actions - │ - ▼ -Commit - │ - ▼ -Cleanup +ISO\ ``` -Chaque phase est ensuite traitée par les composants appropriés. +contient les ressources et éléments nécessaires aux opérations liées aux médias Windows et à la génération de l'image. --- -## Pipeline +# Logs -Le Pipeline exécute les différentes étapes définies par le Workflow. +Le dossier : -Ses responsabilités sont notamment : +```text +Logs\ +``` -- exécuter les étapes dans le bon ordre ; -- propager le BuildContext ; -- suivre l'état d'exécution ; -- gérer les erreurs au niveau de l'orchestration ; -- journaliser les étapes. +contient les journaux générés pendant les opérations du Builder. -Le Pipeline ne connaît pas les détails des personnalisations. +--- -Les décisions techniques doivent être déléguées à des composants spécialisés. +# Output -Par exemple, la décision de réutiliser un montage WIM est centralisée par : +Le dossier : -```powershell -Test-WimMountState() +```text +Output\ ``` -Le Pipeline orchestre et ne doit pas dupliquer cette décision. +est destiné aux artefacts produits par le Build. --- -# Engine +# Workspace + +Le dossier : -L'Engine représente la logique de traitement des Actions. +```text +Workspace\ +``` -Il reçoit les objets construits à partir de la configuration et utilise le routage centralisé pour déterminer quel Engine spécialisé doit traiter chaque Action. +contient les ressources de travail temporaires utilisées pendant les opérations du Builder. -Il ne réalise pas directement les opérations Windows. +Il peut notamment contenir : + +- les copies de travail ; +- les montages WIM ; +- les fichiers temporaires ; +- les ressources intermédiaires. --- -# ActionEngine +# Packages + +Le dossier : -`ActionEngine` centralise le routage des Actions. +```text +Packages\ +``` -Le routage repose sur `ActionRegistry`, qui associe les types d'Actions aux Engines spécialisés. +contient les ressources liées aux packages ou aux fournisseurs utilisés par le projet. + +--- -Cette architecture permet d'ajouter un nouveau type d'Action sans modifier la logique de routage principale. +# Classes -Exemple : +Le dossier historique : ```text -Registry ─────► RegistryEngine -Service ─────► ServiceEngine -Feature ─────► FeatureEngine -Package ─────► PackageEngine -Driver ─────► DriverEngine -Command ─────► CommandEngine +Classes\ ``` -Cette couche permet : +n'est plus utilisé comme couche de classes métier du Builder. -- d'isoler les Engines spécialisés ; -- d'ajouter de nouveaux types d'Actions ; -- de limiter le couplage. +Les composants actifs suivent désormais l'architecture modulaire du framework. --- -# Les Engines spécialisés +# Legacy -Chaque Engine est spécialisé dans un domaine fonctionnel. +Les composants historiques peuvent être conservés dans : -Les Engines utilisent un contrat commun : - -**Entrées** +```text +Tools\ +Tests\Legacy\ +``` -- BuildContext ; -- Action. +Ils ne sont pas chargés par : -**Sortie** +```text +Modules\PimsOS.psm1 +``` -- BuildContext. +et ne participent pas au fonctionnement normal du Builder. -Les Engines actuellement implémentés sont : +Aucune nouvelle fonctionnalité ne doit être développée dans ces emplacements. -| Engine | Responsabilité | -|---------|----------------| -| RegistryEngine | Registre Windows | -| ServiceEngine | Services Windows | -| PackageEngine | Packages | -| DriverEngine | Pilotes | -| FeatureEngine | Fonctionnalités Windows | -| CapabilityEngine | Windows Capabilities | -| CommandEngine | Exécution de commandes | -| FileEngine | Opérations sur les fichiers | -| FolderEngine | Opérations sur les dossiers | -| EnvironmentEngine | Variables d'environnement | -| ScheduledTaskEngine | Tâches planifiées | -| ShortcutEngine | Raccourcis Windows | +--- -Chaque nouveau type d'Action doit être accompagné d'un Engine spécialisé lorsque le domaine le justifie. +# Point d'entrée ---- +Le point d'entrée du framework est : -# Les Managers +```text +Modules\PimsOS.psm1 +``` -Les Engines ne réalisent pas directement les opérations techniques Windows. +L'API publique est centralisée dans ce module. -Ils délèguent les traitements aux Managers spécialisés. +La fonction publique principale actuellement définie est : -Managers actuellement implémentés : +```powershell +Initialize-PimsOS +``` -- PackageManager ; -- DriverManager ; -- FeatureManager ; -- CapabilityManager ; -- CommandManager ; -- FileManager ; -- FolderManager ; -- EnvironmentManager ; -- ScheduledTaskManager ; -- ShortcutManager. +--- -Les Managers encapsulent les opérations techniques et la résolution des providers de leur domaine. +# Principe général -Ils disposent de tests unitaires dédiés. +L'organisation du projet doit préserver la séparation entre : ---- +```text +Configuration + ↓ +Framework + ↓ +Build + ↓ +Runtime PostInstall + ↓ +Windows installé +``` -# Les modules techniques +Le Build prépare l'image. -Les modules techniques constituent la couche qui interagit directement avec les technologies Windows. +Le runtime PostInstall s'exécute ensuite dans Windows installé. -Ils encapsulent les opérations techniques utilisées par le framework. +Cette séparation permet de maintenir une architecture claire, testable et évolutive. -Exemples actuels : +--- -| Module | Responsabilité | -|---------|----------------| -| Registry.ps1 | Registre Windows | -| Service.ps1 | Services Windows | -| Dism.ps1 | Opérations DISM | -| Wim.ps1 | Images WIM | -| Iso.ps1 | Images ISO | -| Recovery.ps1 | Récupération et préparation de l'environnement | +# Références -Les providers de packages suivants existent dans l'organisation du projet : +Consulter également : -| Provider | État | -|----------|------| -| Chocolatey.ps1 | Architecture prévue, implémentation à finaliser | -| Winget.ps1 | Architecture prévue, implémentation à finaliser | +- `Architecture.md` +- `ArchitectureRules.md` +- `BuildContext.md` +- `DeveloperGuide.md` +- `ModuleGuide.md` +- `PostInstall.md` +- `Testing.md` +- `Legacy.md` -Les modules techniques ne doivent pas contenir de logique métier liée aux profils, Tweaks ou Actions. --- -# Principes de fonctionnement +## Validation de la chaîne réelle — 02/09/2026 -Les personnalisations suivent le parcours suivant : +La chaîne réelle actuellement validée est : ```text -Configuration - │ - ▼ -Action - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique - │ - ▼ -Windows -``` - -Cette architecture garantit : - -- un comportement uniforme ; -- une exécution prévisible ; -- une maintenance simplifiée ; -- une forte extensibilité. - ---- - -# Flux d'exécution - -Le cycle général du Build est : - -```text -Initialize-PimsOS - │ - ▼ -New-BuildContext - │ - ▼ -Initialize-BuildContext - │ - ▼ -Recovery - │ - ▼ -Environment Checks - │ - ▼ -Workflow / Pipeline - │ - ▼ -Configuration - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique - │ - ▼ -Windows - │ - ▼ -Complete-Build -``` - -Chaque étape utilise le BuildContext approprié. - ---- - -# Communication entre les composants - -Les composants de PimsOS communiquent selon les règles suivantes. - -## Communication interne au module - -Tous les composants internes appartiennent au module PimsOS. - -Ils partagent le même espace d'exécution et peuvent collaborer au sein du module. - -Aucun composant interne ne doit être importé individuellement. - -Les composants internes ne doivent jamais utiliser : - -```powershell -Import-Module -``` - -pour charger un autre composant interne. - -Ils sont chargés par `PimsOS.psm1`. - ---- - -## Communication descendante - -Le flux normal des dépendances est descendant : - -```text -Workflow - │ - ▼ -Pipeline - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique -``` - -Une couche ne doit pas contourner inutilement une couche intermédiaire. - ---- - -## Communication via le BuildContext - -Les informations nécessaires au partage de l'état et des données du Build transitent par le BuildContext. - -Aucun composant ne doit utiliser : - -- une variable globale ; -- un état global implicite ; -- un mécanisme de partage caché. - -Les variables `script:` peuvent exister pour l'état interne limité d'un composant, par exemple une table de providers, mais elles ne doivent pas servir de substitut au BuildContext. - -Le BuildContext constitue le contrat central du Build. - ---- - -# Dépendances - -Les dépendances suivent une structure descendante : - -```text -Workflow - │ - ▼ -Pipeline - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique +MountIso + ↓ +CopyIsoContent + ↓ +DetectWim + ↓ +CopyWim + ↓ +ReadWimImages + ↓ +SelectImage + ↓ +MountWim + ↓ +ApplyDrivers + ↓ +PreparePostInstall + ↓ +LoadConfiguration + ↓ +MountConfigurationRegistryHives + ↓ +ApplyConfiguration + ↓ +DismountConfigurationRegistryHives + ↓ +ValidatePostInstallDeployment + ↓ +DismountWim + ↓ +SyncWimToIsoSource + ↓ +DismountIso + ↓ +NewPimsOSIso ``` -Les dépendances circulaires sont interdites. - -Le module PimsOS constitue la racine de chargement des composants internes. - -Les composants internes ne sont pas des modules PowerShell indépendants. - -Ils sont chargés par : - -```text -PimsOS.psm1 -``` - -Le manifeste `PimsOS.psd1` décrit le module public et ses métadonnées ; il ne constitue pas le système de chargement des composants internes. - ---- - -# BuildContext - -Le BuildContext constitue le contrat central entre les composants du framework. - -Il est créé au début du Build puis enrichi progressivement. - -Il contient notamment : - -- les informations du projet ; -- les informations de version ; -- les chemins de travail ; -- le BuildState ; -- les statistiques ; -- les rapports ; -- les configurations ; -- les ressources montées ; -- les objets métier tels que Tweaks et Actions. - -Chaque étape du Pipeline met à jour le contexte relevant de sa responsabilité. - -Aucun état global supplémentaire ne doit être créé pour transporter les informations du Build. - -Les détails du modèle sont documentés dans : - -- `BuildContext.md` ; -- `Schema.md` ; -- `ADR-0002` ; -- `ADR-0010`. - ---- - -# ActionRegistry - -`ActionRegistry` centralise l'association entre les types d'Actions et leurs Engines spécialisés. - -Son objectif est de permettre au moteur de résoudre un Engine sans coder cette association directement dans chaque appelant. - -Pour ajouter un nouveau type d'Action : - -1. créer l'Engine spécialisé ; -2. définir son contrat ; -3. enregistrer le type dans `ActionRegistry` ; -4. ajouter les tests correspondants ; -5. mettre à jour la documentation lorsque nécessaire. - ---- - -# Recovery - -Le mécanisme Recovery constitue le point d'entrée technique pour la récupération et la préparation de l'environnement. - -Il vérifie et traite notamment : - -- les montages DISM ; -- les ressources ISO ; -- les ruches du registre ; -- le Workspace. - -La décision de réutiliser un montage WIM est centralisée dans : - -```powershell -Test-WimMountState() -``` - -Le mécanisme Recovery est implémenté. - -Sa couverture de tests et certains diagnostics détaillés restent à compléter. - ---- - -# Décisions d'architecture (ADR) - -Les décisions structurantes de PimsOS sont documentées sous forme d'Architecture Decision Records (ADR). - -Les ADR décrivent les choix d'architecture, leurs motivations ainsi que leurs conséquences. - -Les ADR actuellement publiées sont : - -| ADR | Sujet | -|------|-------| -| ADR-0001 | Architecture modulaire | -| ADR-0002 | BuildContext central | -| ADR-0003 | Organisation des composants | -| ADR-0004 | Pipeline de Build | -| ADR-0005 | Journalisation centralisée | -| ADR-0006 | Configuration JSON | -| ADR-0007 | Stratégie de tests | -| ADR-0008 | Gestion des erreurs | -| ADR-0009 | Dépendances entre composants | -| ADR-0010 | Cycle de vie du BuildContext | -| ADR-0011 | Contrats entre composants | -| ADR-0012 | Module PowerShell unique | - -Toute évolution importante de l'architecture doit être accompagnée d'une nouvelle ADR lorsque nécessaire. - ---- - -# Documentation associée - -Le présent document décrit l'architecture générale de PimsOS. - -Les aspects détaillés sont documentés dans : - -## Documentation technique - -- `API.md` -- `BuildContext.md` -- `CodingStandards.md` -- `DeveloperGuide.md` -- `GettingStarted.md` -- `Lifecycle.md` -- `ModuleGuide.md` -- `Prerequisites.md` -- `ProjectStatus.md` -- `ProjectStructure.md` -- `Roadmap.md` -- `Schema.md` -- `Testing.md` -- `TechnicalDecisions.md` - -## Gouvernance - -- `CHANGELOG.md` -- `README.md` - -## Architecture - -- `Documentation\ADR\` - ---- - -# Conclusion - -L'architecture de PimsOS repose sur une séparation stricte des responsabilités et sur un module PowerShell unique. - -Le module PimsOS centralise le chargement des composants internes, expose l'API publique et fournit un espace d'exécution commun aux différents composants du framework. - -Le flux principal est : - -```text -BuildContext - │ - ▼ -Workflow - │ - ▼ -Pipeline - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique -``` - -Cette architecture permet : - -- une maintenance simplifiée ; -- une bonne testabilité ; -- une forte extensibilité ; -- une évolution maîtrisée ; -- une gestion cohérente des responsabilités. - -Les décisions structurantes sont documentées dans les ADR afin de préserver la cohérence de l'architecture au fil des évolutions. - -## PostInstall - -Le Build prépare le système, tandis que le runtime **PostInstall** -exécute les opérations nécessaires après l'installation de Windows. - -La séparation des responsabilités est : - -```text -Build - | - v -WIM préparé - | - v -FirstBoot - | - v -PostInstall Bootstrap - | - v -Initialisation de l'état - | - v -Vérification réseau / Internet - | - +-- Réseau disponible - | | - | v - | Installation locale - | | - | v - | Opérations réseau - | - +-- Réseau indisponible - | - v - WaitingForNetwork - | - v - Reprise automatique - | - v - PostInstall - | - v - PackageManager - | - v - Applications -``` - -### Runtime autonome - -Le runtime PostInstall est embarqué dans le WIM lors du Build. - -Il est ensuite exécuté depuis : - -```text -C:\ProgramData\PimsOS\PostInstall\ -``` - -Le runtime ne dépend pas du chemin du dépôt PimsOS présent sur la -machine ayant construit l'image. - -Les fichiers nécessaires au runtime sont notamment : - -```text -Bootstrap.ps1 -Network.ps1 -UI.ps1 -PostInstall.ps1 -State.ps1 -``` - -L'installation du runtime est réalisée par : - -```powershell -Install-PimsOSPostInstallRuntime -``` - -Cette fonction valide la présence du runtime source, crée le dossier -de destination et copie les fichiers nécessaires dans l'image Windows. - -### Bootstrap - -`Bootstrap.ps1` constitue le point d'entrée du runtime PostInstall. - -Il : - -- localise le runtime installé ; -- vérifie la présence des composants nécessaires ; -- charge `State.ps1` ; -- charge `Network.ps1` ; -- charge `UI.ps1` ; -- charge `PostInstall.ps1` ; -- démarre l'exécution PostInstall. - -Le Bootstrap permet ainsi au runtime d'être initialisé indépendamment -du dépôt de Build. - -### État PostInstall - -L'exécution PostInstall repose sur un état persistant permettant -d'identifier la progression du processus. - -L'état permet notamment de gérer : - -- l'initialisation ; -- l'exécution des phases locales ; -- l'attente du réseau ; -- la reprise après disponibilité du réseau ; -- les erreurs d'exécution. - -### Gestion réseau - -La couche réseau distingue la disponibilité du réseau local de -l'accès réel à Internet. - -La vérification suit le principe : - -```text -Adaptateur réseau - | - v -Connexion réseau - | - v -Accès Internet -``` - -Un adaptateur actif ne signifie donc pas nécessairement qu'Internet -est disponible. - -PostInstall distingue notamment les situations suivantes : - -```text -Aucun adaptateur - | - v -Réseau indisponible -``` - -et : - -```text -Adaptateur actif - | - v -Réseau local disponible - | - v -Internet indisponible -``` - -Cette distinction est importante car certaines opérations PostInstall -nécessitent un accès Internet. - -### Interface réseau du premier démarrage - -`UI.ps1` fournit l'interface console utilisée pendant le premier -démarrage. - -Elle expose notamment : - -```powershell -Show-PostInstallNetworkStatus -Show-PostInstallNetworkHelp -Wait-PostInstallNetworkUI -``` - -`Show-PostInstallNetworkStatus` présente l'état courant du réseau et -de l'accès Internet. - -`Show-PostInstallNetworkHelp` fournit les indications nécessaires à -l'utilisateur lorsqu'une connexion réseau est requise. - -`Wait-PostInstallNetworkUI` assure l'attente avec affichage de l'état -et permet la reprise automatique lorsque la connexion devient -disponible. - -La couche UI reste séparée de la logique métier de PostInstall. - -### Intégration FirstBoot - -Le Build génère également la configuration `unattend.xml` permettant -de lancer le Bootstrap lors du premier démarrage de Windows. - -Le fichier est installé dans : - -```text -C:\Windows\Panther\unattend.xml -``` - -Le flux est donc : - -```text -Build - | - v -Installation du runtime - | - v -Génération unattend.xml - | - v -Installation de Windows - | - v -FirstLogonCommands - | - v -Bootstrap.ps1 - | - v -PostInstall -``` - -La génération du fichier `unattend.xml` est réalisée par les -composants FirstBoot/Unattend du framework. - -### Séparation Build / PostInstall - -Le Build et PostInstall possèdent des responsabilités différentes. - -**Build :** - -- prépare l'image Windows ; -- applique les personnalisations offline ; -- installe le runtime PostInstall ; -- prépare FirstBoot ; -- génère `unattend.xml`. - -**PostInstall :** - -- s'exécute dans Windows installé ; -- initialise son état ; -- vérifie l'environnement réseau ; -- vérifie l'accès Internet ; -- attend si nécessaire la disponibilité du réseau ; -- reprend automatiquement l'exécution lorsque les conditions sont - réunies ; -- exécute les opérations prévues après installation. - -Cette séparation conserve une frontière claire entre les opérations -offline du Build et les opérations runtime réalisées après -l'installation de Windows. - -## État de validation — PostInstall / FirstBoot - -### Fonctionnalités validées - -- FirstBoot validé réellement dans une VM Hyper-V Generation 2. -- TPM virtuel activé dans la VM de test. -- Installation de Windows depuis l'ISO PimsOS validée. -- `RunOnce` présent dans la ruche `SOFTWARE` de l'image installée. -- `RunOnce` exécuté au premier démarrage puis consommé. -- `state.json` créé par le PostInstall. -- `state.json` validé avec une seule tâche `Local`. -- Protection contre une seconde exécution du PostInstall ajoutée. -- Synchronisation du WIM de travail vers la source ISO implémentée. -- Vérification SHA256 du WIM après synchronisation implémentée et testée. -- Ordre des étapes critiques du BuildPipeline testé. - -### Tests automatisés - -- `PostInstall.Tests.ps1` : 13/13 tests réussis. -- `BuildPipeline.Tests.ps1` : 22/22 tests réussis. -- Suite globale : 971 tests réussis, 0 échec, 1 test ignoré. -- Le test ignoré est conditionnel et concerne `Categories.Tests.ps1`. -- Les trois fichiers sous `Tests/Legacy/Modules/` ne font pas partie de la suite fonctionnelle actuelle et sont actuellement signalés comme containers non exécutables par Pester. - -### Point restant à valider - -La correction de la dépendance `Write-Log` du Bootstrap doit encore être validée dans l’ISO générée le 31/08/2026. - -La validation Hyper-V précédente a permis de valider le mécanisme FirstBoot / RunOnce, mais elle ne constitue pas à elle seule une validation de la nouvelle version du Bootstrap corrigée. - -### Procédure de validation ISO - -La chaîne de validation est : - -1. `BuildPimsOS` -2. génération de l'ISO PimsOS -3. test de l'ISO dans Hyper-V -4. validation de l'installation -5. validation du FirstBoot -6. validation du PostInstall -7. validation de `state.json` -8. validation du comportement réseau -9. validation finale de l'ISO avant utilisation avec Rufus. - -Pour une installation physique, la procédure de référence est : - -`BuildPimsOS → ISO → Rufus → clé USB` - -Le nom du compte Windows utilisé pendant l'installation ne doit pas être utilisé comme dépendance pour le fonctionnement du PostInstall. - -# Architecture PimsOS 3.0 - -## Configuration - -`Context.Project.Config` contient la configuration globale du projet (Workspace, Requirements, Image, Drivers, Build, etc.). - -`Context.Configuration` contient exclusivement la configuration finale des tweaks : une liste plate d'objets possédant au minimum `Id`, `Name`, `Enabled` et `Actions`. - -Le profil `Custom` n'est pas un fichier JSON. La sélection effectuée dans l'assistant est conservée dans `Context.Configuration` jusqu'à `ApplyConfiguration`. - -## Pipeline de configuration - -```text -Wizard - -> Context.Configuration - -> LoadConfiguration - -> MountConfigurationRegistryHives - -> ApplyConfiguration - -> Invoke-Configuration - -> Invoke-Tweak - -> Invoke-Action - -> ActionRegistry - -> moteur spécialisé -``` - -Les étapes du pipeline doivent toujours retourner le même `BuildContext`. Une étape ne doit jamais remplacer `Context.Configuration` par la configuration globale de `Project.Config`. - -## Profils - -Les profils JSON peuvent être placés dans des sous-dossiers de `Profiles`. Leur nom logique est leur chemin relatif sans extension, par exemple `Tests\Registry`. - -Les fichiers JSON vides ne sont pas proposés par l'assistant et sont refusés par `Load-Profile`. - -## Moteurs d'actions - -Le registre couvre les moteurs actuellement présents dans le projet : - -- Registry -- Service -- Driver -- Feature -- Capability -- Command -- File -- Folder -- Environment -- ScheduledTask -- Shortcut -- Package - -`New-Action` conserve les propriétés spécifiques de la définition JSON. L'ajout d'un nouveau moteur ou provider ne nécessite donc pas de modifier le modèle commun de l'action. - -## Extensions prévues - -### Chocolatey - -Provider de packages destiné à l'installation d'applications pendant le PostInstall, avec possibilité de cache local préparé dans `Workspace\Packages\Chocolatey`. - -### Microsoft Store - -Provider d'applications Microsoft Store, à isoler du moteur de tweaks. La stratégie d'installation devra être définie avant activation dans les profils. - -### Widgets Windows - -Les Widgets sont traités comme une capacité de configuration Windows via les tweaks existants dans `Tweaks\Widgets`. Les améliorations futures doivent rester compatibles avec la chaîne générique des actions. - -### Applications - -Les applications devront utiliser les providers de packages (Chocolatey, Winget, Microsoft Store) sans mélanger leur logique avec le moteur générique des tweaks. - - ---- - -# Catalogue des Tweaks - -Les personnalisations Windows sont décrites par des fichiers JSON situés dans -`Tweaks`. - -Chaque définition sépare : - -- les métadonnées destinées au Wizard ; -- le niveau de risque et la réversibilité ; -- les contraintes de compatibilité ; -- les Actions exécutables. - -Les réglages utilisateur destinés aux nouveaux comptes utilisent la ruche -`DEFAULT` (`C:\Users\Default\NTUSER.DAT`). Les stratégies machine utilisent -`SOFTWARE` lorsqu'elles doivent s'appliquer au niveau de l'image. - -Le catalogue doit privilégier les réglages documentés, réversibles et -indépendants. Les Tweaks Explorer `ShowHiddenFiles` et -`ShowProtectedSystemFiles` restent volontairement séparés : le premier affiche -les éléments marqués Caché, tandis que le second expose également les fichiers -système protégés tels que `pagefile.sys` ou certains `desktop.ini`. - -Le catalogue détaillé et les références Microsoft associées sont maintenus -dans `Documentation/Tweaks.md`. +La chaîne de production a produit une ISO PimsOS 3.0.0 avec code retour 0 le 02/09/2026, avec injection réelle des drivers `CurrentSystem`. Cette validation est une preuve du comportement actuel, pas une justification pour modifier l’architecture sans besoin. diff --git a/Documentation/Backlog.md b/Documentation/Backlog.md index 1d2dd55..ad8511f 100644 --- a/Documentation/Backlog.md +++ b/Documentation/Backlog.md @@ -1,38 +1,27 @@ -# Mise à jour du backlog — 31/08/2026 +# Mise à jour du backlog — 02/09/2026 -## Priorité immédiate — Wizard / Tweaks +## Travaux clôturés -- [x] Charger `TweakCatalog.ps1` dans `PimsOS.psm1`. -- [x] Stabiliser le catalogue de présentation du Wizard. -- [x] Permettre la sélection individuelle, par liste et par plage. -- [x] Corriger la résolution des profils depuis `Profiles\`. -- [x] Ajouter les tests du flux de sélection Tweaks. -- [ ] Continuer l’enrichissement du catalogue et des profils. +- [x] Build réel complet avec injection `CurrentSystem` validé. +- [x] Diagnostic sécurisé : 815 Passed / 0 Failed / 1 Skipped. +- [x] Chocolatey bootstrap Offline obligatoire au Build. +- [x] `FailurePolicy=Stop|Continue` implémentée et testée. +- [x] `FailurePolicy=Continue` validée en VM sur l’échec Chrome. +- [x] PostInstall/FirstBoot validé en VM. +- [x] Finalization et Cleanup différé validés en VM. +- [x] Microsoft Store / iCloud / Widgets vérifiés en VM sans modification de l’intégration de base. -## Tweaks / catégories +## Prochaine priorité -- [ ] Compléter les 9 fichiers JSON placeholders. -- [ ] Vérifier les 19 définitions existantes et leurs Actions. -- [ ] Harmoniser `Config\Categories.json` avec les `CategoryId` réellement utilisés. -- [ ] Ne pas considérer un placeholder comme un Tweak terminé. - -## Validation - -- [x] Régénérer/valider les campagnes ciblées Pester du sprint. -- [ ] Régénérer `Tests\testResults.xml`. -- [x] Générer une ISO PimsOS 3.0.0 avec le pipeline réel. -- [ ] Revalider Bootstrap/PostInstall/Write-Log dans Hyper-V sur la nouvelle ISO. -- [ ] Vérifier `state.json` et l’idempotence. +1. Synchronisation Git du projet et de la documentation. +2. Régénération éventuelle de `Tests\testResults.xml`. +3. Validation physique/Rufus. +4. Audit Offline réel des packages Chocolatey. +5. Winget et couverture Recovery/Security. --- -# PimsOS Builder - Backlog - -> Version technique : 3.0.0 -> -> Statut : Référence -> -> Dernière mise à jour : 2026-08-31 +## Backlog détaillé --- @@ -50,20 +39,20 @@ Les éléments sont réévalués régulièrement en fonction des priorités du p ## Génération d'image -- [ ] Finaliser la reconstruction automatique de l'ISO. -- [ ] Valider automatiquement l'ISO générée. -- [ ] Réaliser un Build complet de bout en bout. -- [ ] Vérifier les artefacts de sortie. -- [ ] Finaliser le nettoyage après génération. +- [x] Générer une ISO réelle avec le pipeline complet. +- [x] Valider la production de l'ISO par code retour 0, WIM SHA256 et absence de montage résiduel. +- [x] Réaliser un Build complet de bout en bout. +- [x] Vérifier les principaux artefacts de sortie. +- [x] Valider le nettoyage des ressources Build. - [ ] Améliorer la gestion des erreurs pendant la production de l'image. --- ## Providers de packages -- [ ] Implémenter le provider Chocolatey. +- [x] Stabiliser le provider/cache Chocolatey, son catalogue et `FailurePolicy`. - [ ] Implémenter le provider Winget. -- [ ] Ajouter les tests des providers. +- [x] Ajouter les tests Chocolatey/cache/catalogue. - [ ] Valider leur intégration avec `PackageManager`. --- @@ -75,9 +64,9 @@ Les éléments sont réévalués régulièrement en fonction des priorités du p - [ ] Ajouter ou compléter les tests d'intégration. - [ ] Ajouter des scénarios de régression supplémentaires. - [ ] Ajouter une couverture de code exploitable dans la CI. -- [ ] Intégrer PSScriptAnalyzer dans la CI. -- [ ] Vérifier automatiquement le chargement du module PimsOS dans la CI. -- [ ] Publier les résultats Pester dans GitHub Actions. +- [x] Intégrer PSScriptAnalyzer dans la CI. +- [x] Vérifier automatiquement le chargement du module PimsOS dans la CI. +- [x] Publier les résultats Pester comme artefacts GitHub Actions. --- @@ -86,6 +75,7 @@ Les éléments sont réévalués régulièrement en fonction des priorités du p ## Recovery et diagnostic - [ ] Améliorer `Test-WimMountState()`. +- [x] Ajouter un diagnostic statique sécurisé des tests. - [ ] Ajouter un diagnostic détaillé de l'état des ressources. - [ ] Améliorer la détection des ressources laissées par un Build précédent. - [ ] Renforcer les scénarios de récupération. @@ -269,3 +259,62 @@ Le Backlog permet de conserver une vision à moyen et long terme du projet sans Les éléments les plus importants doivent être déplacés vers la Roadmap ou un jalon avant leur réalisation. Toute tâche terminée doit être retirée du Backlog actif et reflétée dans la documentation de statut du projet. + +# PimsOS Builder - Backlog + +> Version technique : **3.0.0** +> +> Dernière mise à jour : **2026-09-02** + +## Priorité immédiate + +### Validation ISO + +- [ ] Reconstruire l'ISO depuis le commit 3bbaf73. +- [ ] Valider FirstBoot/PostInstall dans Hyper-V. +- [ ] Vérifier Bootstrap / Logger. +- [ ] Vérifier `state.json`. +- [ ] Vérifier l'idempotence. +- [ ] Tester la reprise après disponibilité réseau. +- [ ] Vérifier l'application réelle des Tweaks. +- [ ] Valider l'installation via Rufus. + +### Packages + +- [ ] Finaliser Chocolatey. +- [ ] Finaliser Winget. +- [ ] Définir le contrat des Providers. +- [ ] Tester les installations réelles en PostInstall. + +### Microsoft Store + +- [ ] Définir la stratégie Store. +- [ ] Implémenter son provider. +- [ ] Intégrer le provider au PackageManager. +- [ ] Ajouter les tests unitaires. +- [ ] Ajouter les tests d'intégration. + +## Tweaks + +- [ ] Enrichir le catalogue au-delà des 27 Tweaks actuels. +- [ ] Auditer les Tweaks existants. +- [ ] Compléter les placeholders réellement utiles. +- [ ] Harmoniser `Config/Categories.json`. +- [ ] Maintenir `Documentation/Tweaks.md`. + +## Qualité + +- [ ] Régénérer `Tests\testResults.xml`. +- [ ] Compléter Recovery. +- [ ] Compléter Security. +- [ ] Ajouter PSScriptAnalyzer. +- [ ] Renforcer la CI. +- [ ] Enrichir Reporting. +- [ ] Implémenter Converters. + +## Règles + +- `Tests\Legacy` reste hors campagne officielle. +- Les ADR historiques ne sont pas réécrits pour un simple changement d'état. +- Les nouveaux providers ne doivent pas être mélangés au moteur générique des + Tweaks/Registry. diff --git a/Documentation/BuildContext.md b/Documentation/BuildContext.md index 42805f4..0315711 100644 --- a/Documentation/BuildContext.md +++ b/Documentation/BuildContext.md @@ -1,57 +1,201 @@ -# PimsOS Builder - BuildContext +# PimsOS Builder - Structure du projet > Version technique : 3.0.0 > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- -# Présentation +# Objectif -Le **BuildContext** est le contrat central utilisé par les composants de **PimsOS Builder**. +Ce document décrit l'organisation réelle du projet **PimsOS Builder**. -Il est créé au démarrage du Builder puis enrichi progressivement tout au long du cycle de Build. +Le projet est structuré autour d'un module PowerShell unique : -Le BuildContext permet de partager l'état et les données nécessaires entre les différents composants sans utiliser d'état global pour transporter les informations du Build. +```text +Modules\PimsOS.psm1 +``` -Les Engines, Managers et autres composants reçoivent le contexte nécessaire à leur traitement et mettent à jour les informations relevant de leur responsabilité. +Le module centralise le chargement des composants internes et l'exposition de l'API publique du framework. --- -# Objectifs +# Structure générale + +```text +PimsOS +│ +├── Build +├── Config +├── Documentation +├── ISO +├── Logs +├── Modules +├── Output +├── Packages +├── Tests +├── Workspace +├── version.json +└── README.md +``` -Le BuildContext permet de : +Le projet est organisé de manière à séparer clairement : -- centraliser les données du Build ; -- partager un état commun entre les composants ; -- réduire les dépendances implicites ; -- faciliter les tests unitaires ; -- faciliter le diagnostic et le débogage ; -- maintenir une architecture modulaire ; -- conserver un contrat commun entre les différentes couches. +- le code du framework ; +- la configuration ; +- les ressources de Build ; +- les tests ; +- la documentation ; +- les artefacts et espaces de travail. --- -# Cycle de vie +# Modules -Le cycle de vie général du BuildContext est : +Le code du framework se trouve dans : ```text -Initialize-PimsOS - │ - ▼ -New-BuildContext - │ - ▼ -Initialize-BuildContext +Modules\ +``` + +La structure principale est : + +```text +Modules +│ +├── Actions +├── Configuration +├── Core +├── Image +├── Infrastructure +├── Managers +├── Package +├── PostInstall +├── Windows +├── PimsOS.psd1 +└── PimsOS.psm1 +``` + +Les composants internes appartiennent au module PowerShell unique **PimsOS**. + +Ils ne constituent pas des modules PowerShell indépendants. + +--- + +# PostInstall + +Le projet contient désormais un sous-système dédié à l'exécution après l'installation de Windows : + +```text +Modules\PostInstall\ +``` + +Ce dossier contient le runtime PostInstall embarqué dans l'image Windows. + +Les composants actuellement présents comprennent notamment : + +```text +Bootstrap.ps1 +FirstBoot.ps1 +Installer.ps1 +Network.ps1 +PostInstall.ps1 +State.ps1 +UI.ps1 +Unattend.ps1 +``` + +Le runtime est préparé pendant le Build puis installé dans l'image Windows sous : + +```text +C:\ProgramData\PimsOS\PostInstall\ +``` + +Le sous-système PostInstall prend notamment en charge : + +- l'initialisation du runtime ; +- la persistance de l'état ; +- la détection du réseau ; +- la détection de l'accès Internet ; +- l'attente de la disponibilité réseau ; +- l'interface console du premier démarrage ; +- la préparation FirstBoot ; +- la génération de `unattend.xml`. + +Le PostInstall reste séparé de la logique du Build exécutée hors ligne. + +--- + +# Diagnostic avant tests + +Avant une campagne Pester, utiliser `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour vérifier que les fichiers sélectionnés ne sont pas susceptibles de lancer un Build réel. Les validations `BUILD-CAPABLE` nécessitent explicitement `-BuildValidation -AllowBuild`. + +# Tests + +Les tests du framework sont organisés dans : + +```text +Tests\ +``` + +La structure comprend notamment : + +```text +Tests +│ +├── Unit +├── Integration +├── Acceptance +└── Legacy +``` + +Les tests Legacy sont conservés séparément et ne participent pas à la validation officielle du framework actif. + +--- + +# Tests PostInstall + +Les tests dédiés au sous-système PostInstall sont regroupés dans : + +```text +Tests\Unit\Modules\PostInstall\ +``` + +Ils couvrent notamment : + +```text +Bootstrap.Tests.ps1 +FirstBoot.Tests.ps1 +Installer.Tests.ps1 +Network.Tests.ps1 +PostInstall.Tests.ps1 +State.Tests.ps1 +UI.Tests.ps1 +Unattend.Tests.ps1 +``` + +Les tests PostInstall vérifient les composants individuellement ainsi que leurs contrats fonctionnels. + +L'intégration du PostInstall dans le pipeline est également couverte par les tests d'intégration du BuildPipeline. + +--- + +# Architecture des dépendances + +Le framework suit une architecture en couches. + +Le flux principal est : + +```text +Infrastructure │ ▼ -Recovery +Core │ ▼ -Environment Checks +Configuration │ ▼ Workflow @@ -60,9 +204,6 @@ Workflow Pipeline │ ▼ -Configuration - │ - ▼ ActionEngine │ ▼ @@ -78,340 +219,273 @@ Manager Module technique │ ▼ -Complete-Build +Windows ``` -Le même BuildContext est utilisé pendant toute l'exécution. +Le PostInstall constitue un sous-système runtime distinct du chemin d'exécution principal du Build. ---- - -# Structure générale +Sa préparation est intégrée au Pipeline : ```text -Context -│ -├── Project -├── Build -├── BuildState -├── Configuration -├── ConfigurationProfile -├── ISO -├── WIM -├── Image -├── Workspace -├── Registry -├── Packages -├── Drivers -├── Tweaks -├── Services -├── Features -├── Report -├── Logger -└── Statistics +MountWim + │ + ▼ +ApplyDrivers + │ + ▼ +PreparePostInstall + │ + ▼ +MountSoftwareHive ``` --- -# Project +# Build -Contient toutes les informations relatives au projet. +Le dossier : ```text -Project -│ -├── Name -├── Version -├── Windows -│ ├── Release -│ └── Build -├── Author -├── Company -├── Repository -├── Root -├── Paths -├── Config -├── StartTime -├── EndTime -└── Duration +Build\ ``` -Ces informations proviennent principalement du fichier **version.json**. +contient les scripts utilisés pour lancer et orchestrer le processus de Build. -Le BuildContext contient également les informations concernant la version de Windows ciblée. +Le lanceur principal est : -Exemples : +```text +Build\Build-PimsOS.ps1 +``` -- Release ; -- Build ; -- Édition sélectionnée. +Le Build prépare notamment : -Ces informations sont découvertes ou sélectionnées au moment du Build et ne sont pas figées dans le moteur. +- l'environnement ; +- les ressources ISO et WIM ; +- les drivers ; +- le runtime PostInstall ; +- FirstBoot ; +- la configuration ; +- les personnalisations ; +- le nettoyage ; +- la finalisation. --- -# Build - -Informations relatives au build courant. - -Exemples : +# Configuration -- Build ID ; -- mode interactif ; -- génération ISO ; -- génération du rapport ; -- mode DryRun. +La configuration du Builder se trouve dans : ---- - -# BuildState +```text +Config\ +``` -Le BuildState représente l'état courant du pipeline. +Elle contient notamment : -Il est mis à jour par chaque étape du Build. +```text +Categories.json +Profiles\ +Tweaks\ +``` -Il contient notamment : +Les profils sélectionnent les personnalisations et les Tweaks définissent les Actions à exécuter. -- état du Recovery ; -- état des vérifications ; -- état du Pipeline ; -- état des images montées ; -- état de la configuration ; -- état global du Build. +La configuration reste séparée du code PowerShell. --- -# Configuration - -Contient la configuration fusionnée prête à être exécutée. - -Elle est construite à partir : +# Documentation -- des fichiers JSON ; -- des catégories ; -- du profil sélectionné. +La documentation technique se trouve dans : ---- +```text +Documentation\ +``` -# ConfigurationProfile +Elle comprend notamment : -Nom du profil actuellement utilisé. +```text +API.md +Architecture.md +ArchitectureRules.md +BuildContext.md +DeveloperGuide.md +GettingStarted.md +Legacy.md +Lifecycle.md +Milestones.md +ModuleGuide.md +PostInstall.md +Prerequisites.md +ProjectStatus.md +ProjectStructure.md +ReleaseNotes.md +Roadmap.md +Schema.md +TechnicalDecisions.md +Testing.md +``` -Exemples : +Les décisions d'architecture sont documentées dans : -- Default ; -- Gaming ; -- Privacy ; -- Minimal ; -- Workstation ; -- Tests\Registry. +```text +Documentation\ADR\ +``` --- # ISO -Informations concernant l'image ISO montée. - ---- - -# WIM - -Informations relatives au fichier install.wim. +Le dossier : -Exemples : +```text +ISO\ +``` -- nom ; -- taille ; -- images disponibles ; -- chemin ; -- montage. +contient les ressources et éléments nécessaires aux opérations liées aux médias Windows et à la génération de l'image. --- -# Image - -Informations sur l'édition Windows sélectionnée. - -Exemples : +# Logs -- Index ; -- Nom ; -- Description ; -- Taille ; -- État de modification. +Le dossier : -Le Builder permet désormais de sélectionner dynamiquement l'édition Windows présente dans le WIM. +```text +Logs\ +``` -Il n'est plus limité à une version spécifique de Windows. +contient les journaux générés pendant les opérations du Builder. --- -# Workspace +# Output -Répertoires temporaires utilisés pendant le Build. +Le dossier : -Exemples : +```text +Output\ +``` -- Sources ; -- Mount ; -- ISO ; -- Output ; -- Temp ; -- Extract. +est destiné aux artefacts produits par le Build. --- -# Registry - -Informations concernant les ruches Windows actuellement montées. +# Workspace ---- +Le dossier : -# Packages +```text +Workspace\ +``` -Liste des packages à installer. +contient les ressources de travail temporaires utilisées pendant les opérations du Builder. -Les packages sont indépendants du gestionnaire utilisé. +Il peut notamment contenir : -Le choix entre Chocolatey, Winget ou un autre fournisseur est réalisé par les Managers. +- les copies de travail ; +- les montages WIM ; +- les fichiers temporaires ; +- les ressources intermédiaires. --- -# Drivers - -Liste des pilotes à intégrer. - ---- - -# Tweaks +# Packages -Liste des Tweaks sélectionnés après fusion du profil. +Le dossier : -Chaque Tweak contient : +```text +Packages\ +``` -- son état ; -- ses métadonnées ; -- ses Actions ; -- son résultat d'exécution ; -- ses statistiques. +contient les ressources liées aux packages ou aux fournisseurs utilisés par le projet. --- -# Services +# Classes -Liste des services Windows manipulés pendant le Build. +Le dossier historique : ---- +```text +Classes\ +``` -# Features +n'est plus utilisé comme couche de classes métier du Builder. -Liste des fonctionnalités Windows à installer ou supprimer. +Les composants actifs suivent désormais l'architecture modulaire du framework. --- -# PostInstall - -Le BuildContext conserve les informations nécessaires à la préparation du runtime **PostInstall** lorsque celles-ci sont partagées avec les composants du Build. +# Legacy -La préparation du PostInstall intervient dans le pipeline après l'application des drivers et avant les étapes suivantes de préparation de l'image. - -Le runtime PostInstall est installé dans l'image Windows sous : +Les composants historiques peuvent être conservés dans : ```text -C:\ProgramData\PimsOS\PostInstall\ +Tools\ +Tests\Legacy\ ``` -Les composants préparés comprennent notamment : +Ils ne sont pas chargés par : ```text -Bootstrap.ps1 -Network.ps1 -UI.ps1 -PostInstall.ps1 -State.ps1 +Modules\PimsOS.psm1 ``` -La configuration FirstBoot et `unattend.xml` permettent ensuite de déclencher le Bootstrap lors du premier démarrage de Windows. +et ne participent pas au fonctionnement normal du Builder. -Le BuildContext ne contient pas la logique d'exécution du PostInstall. Il transporte uniquement les informations nécessaires aux composants du Build. +Aucune nouvelle fonctionnalité ne doit être développée dans ces emplacements. --- -# Report - -Contient le rapport d'exécution. +# Point d'entrée -Il est enrichi tout au long du pipeline. +Le point d'entrée du framework est : -Il regroupe : - -- les phases ; -- les erreurs ; -- les avertissements ; -- les informations ; -- les résultats finaux. - ---- - -# Logger +```text +Modules\PimsOS.psm1 +``` -Informations utilisées par le système de journalisation. +L'API publique est centralisée dans ce module. -Exemples : +La fonction publique principale actuellement définie est : -- état ; -- fichier courant ; -- activation. +```powershell +Initialize-PimsOS +``` --- -# Statistics - -Le BuildContext centralise également toutes les statistiques du Build. +# Principe général -Exemples : +L'organisation du projet doit préserver la séparation entre : -- ActionsProcessed ; -- PackagesProcessed ; -- DriversProcessed ; -- FeaturesProcessed ; -- CapabilitiesProcessed ; -- CommandsProcessed ; -- FilesProcessed ; -- FoldersProcessed ; -- EnvironmentVariablesProcessed ; -- ScheduledTasksProcessed ; -- ShortcutsProcessed ; -- ServicesProcessed ; -- RegistryActionsProcessed ; -- TweaksApplied ; -- Errors ; -- Warnings. - -Ces statistiques sont mises à jour automatiquement par les différents Engines. - ---- +```text +Configuration + ↓ +Framework + ↓ +Build + ↓ +Runtime PostInstall + ↓ +Windows installé +``` -# Principes de conception +Le Build prépare l'image. -Le BuildContext respecte les principes suivants : +Le runtime PostInstall s'exécute ensuite dans Windows installé. -- créé une seule fois ; -- enrichi progressivement ; -- jamais remplacé ; -- jamais cloné ; -- transmis à tous les composants ; -- aucune logique métier ; -- aucune variable globale. +Cette séparation permet de maintenir une architecture claire, testable et évolutive. --- -# Évolutions - -Toute nouvelle fonctionnalité du Builder doit être intégrée au BuildContext uniquement si elle représente un état partagé entre plusieurs composants. - -Le BuildContext constitue le contrat officiel entre tous les modules du framework. +# Références -Toute évolution de sa structure doit être accompagnée : +Consulter également : -- d'une mise à jour de cette documentation ; -- d'une mise à jour de l'ADR correspondante lorsque nécessaire. +- `Architecture.md` +- `ArchitectureRules.md` +- `BuildContext.md` +- `DeveloperGuide.md` +- `ModuleGuide.md` +- `PostInstall.md` +- `Testing.md` +- `Legacy.md` diff --git a/Documentation/ChatGPT-Workflow.md b/Documentation/ChatGPT-Workflow.md index 3095361..ad21d60 100644 --- a/Documentation/ChatGPT-Workflow.md +++ b/Documentation/ChatGPT-Workflow.md @@ -4,7 +4,7 @@ > > Statut : Référence de travail > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- @@ -70,19 +70,103 @@ Toute nouvelle fonctionnalité doit donc : --- -# Début de chaque nouvelle session +# Début de chaque nouvelle session — GATE OBLIGATOIRE -Lorsque ce document est utilisé au début d'une nouvelle conversation : +**Cette étape est obligatoire avant toute proposition de correction, toute recherche de cause et toute modification.** -1. Lire `Documentation/ProjectStatus.md`. -2. Lire `Documentation/Roadmap.md`. -3. Lire `Documentation/Backlog.md` si nécessaire. -4. Lire les ADR nécessaires à la tâche. -5. Respecter les Architecture Rules. -6. Vérifier le contexte réel du dépôt avant toute modification. -7. Vérifier l'état Git lorsque cela est possible. +Le but est d'empêcher une reprise de session basée sur une mémoire partielle, une ancienne conversation ou une hypothèse. -Ne jamais commencer directement à modifier du code sans comprendre le contexte. +## 1. Lire la documentation de continuité + +Commencer par : + +1. `Documentation/DocumentationSync-2026-09-02.md` ou la note `DocumentationSync-*` la plus récente ; +2. `Documentation/ProjectStatus.md` ; +3. `CurrentSprint.md` ; +4. `Documentation/ChatGPT-Workflow.md` ; +5. `Documentation/Roadmap.md` et `Documentation/Backlog.md` si la tâche concerne la planification ; +6. les ADR et documents techniques directement concernés. + +**Ne jamais considérer qu'une information ancienne de conversation remplace cette lecture.** + +## 2. Vérifier le dépôt réel + +Avant toute modification : + +```powershell +git status +git branch --show-current +git log --oneline -5 +``` + +Lorsque GitHub est connecté, vérifier également le dernier commit et les fichiers concernés sur `main`. + +Le fait de penser que « le local est à jour » n'est pas une preuve. La synchronisation doit être vérifiée. + +## 3. Vérifier les changements récents + +Identifier : + +- les derniers commits ; +- les fichiers ajoutés ou supprimés ; +- les tests récemment ajoutés ; +- les nouveaux outils ; +- les changements de workflow CI ; +- les documents de synchronisation. + +## 4. Ne pas partir dans une théorie + +Règle absolue : + +> **Pas d'hypothèse présentée comme un diagnostic.** + +Si un comportement est suspect : + +1. chercher une preuve dans le code ; +2. chercher une preuve dans les tests ; +3. reproduire le comportement si nécessaire ; +4. comparer avec le dernier Build ou résultat connu ; +5. seulement ensuite conclure. + +Si les preuves contredisent une première intuition, abandonner l'intuition. + +## 5. Passer par le diagnostic sécurisé + +Avant une campagne Pester, utiliser : + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Unit -InventoryOnly -ExplainFailures +``` + +ou : + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Integration -InventoryOnly -ExplainFailures +``` + +Ne jamais lancer `-Unit` et `-Integration` ensemble : le script exige un seul mode. + +Pour une validation de Build réel, utiliser uniquement après décision explicite : + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -InventoryOnly -ExplainFailures +``` + +Puis exécuter les tests/builds réellement nécessaires. `-AllowBuild` signifie que des opérations WIM/ISO peuvent être exécutées : ce n'est pas un mode de sécurité. + +## 6. Reprise de séance + +Avant de continuer, annoncer en quelques lignes : + +- état confirmé ; +- derniers changements ; +- tests validés ; +- problèmes connus ; +- prochaine action concrète. + +Ce résumé doit être basé sur les preuves nouvellement vérifiées, pas sur une supposition issue d'une ancienne séance. + +Ne jamais commencer directement à modifier du code sans avoir passé ce gate. --- @@ -255,6 +339,8 @@ Les documents principaux comprennent notamment : - `Testing.md` - `TechnicalDecisions.md` - `API.md` +- `DocumentationSync-*.md` pour les états de synchronisation vérifiés +- `Tests\Tools\README.md` pour le diagnostic sécurisé La documentation doit décrire le comportement réel du projet. diff --git a/Documentation/ChocolateyArchitecture.md b/Documentation/ChocolateyArchitecture.md new file mode 100644 index 0000000..abeb397 --- /dev/null +++ b/Documentation/ChocolateyArchitecture.md @@ -0,0 +1,109 @@ +# PimsOS — Architecture Chocolatey Offline / Online + +## Principe + +PimsOS utilise un moteur Chocolatey unique avec deux sources d'installation déterminées par le catalogue : + +- `Mode = Offline` : le package `.nupkg` est téléchargé pendant le **Build**, vérifié puis embarqué dans l'ISO. Au premier démarrage, Chocolatey installe ce package exclusivement depuis le cache local. +- `Mode = Online` : aucun package n'est embarqué par défaut. Après `DriverCheck`, le Bootstrap utilise Chocolatey et Community pour télécharger puis installer le package. +- `Mode = Disabled` : le package est ignoré. + +## Séquence Build + +```text +Build + -> ApplyDrivers + -> PrepareChocolateyCache + -> catalogue + -> sélection Mode=Offline + -> téléchargement des .nupkg + -> cache Workspace\Packages\Chocolatey + -> PreparePostInstall + -> copie du catalogue + -> copie du cache Offline dans ProgramData\PimsOS\PostInstall\Chocolatey\Cache + -> ISO +``` + +Le build ne télécharge donc pas les packages `Online`. + +## Séquence FirstLogon + +```text +FirstLogonCommands + -> Bootstrap + -> Network + -> DriverCheck + -> Chocolatey Bootstrap local + -> packages Offline depuis le cache + -> packages Online depuis Community +``` + +Les téléchargements Internet de packages ont donc lieu **après le contrôle des pilotes**, conformément à l'architecture PimsOS retenue. + +## Règle Offline + +Un package n'est placé en `Mode=Offline` qu'après validation de son `.nupkg`, de ses dépendances et de ses éventuelles charges utiles externes. Le simple téléchargement du `.nupkg` ne suffit pas à garantir une installation hors ligne. Chocolatey recommande une source locale sous forme de dossier contenant les `.nupkg`; l'installation doit utiliser `--source` sur ce dossier plutôt que pointer directement vers un fichier `.nupkg`. citeturn0search2turn0search4 + +## Bootstrap Chocolatey + +`chocolatey.nupkg` est toujours traité comme un artefact Offline spécial. Le runtime le décompresse et exécute `tools\chocolateyInstall.ps1` localement avant d'utiliser `choco.exe`. Cette méthode est documentée par Chocolatey pour une installation complètement offline. citeturn0search0 + +## Conséquence pour la matrice + +La matrice conserve l'état d'audit (`OfflineReady`), tandis que `Config/Packages/Chocolatey.json` porte la décision d'exécution (`Mode`). Tant qu'un package n'est pas validé offline, son mode reste `Online`. + +## Bootstrap Chocolatey — règle de Build + +Chocolatey est un **pré-requis obligatoire du runtime PostInstall**. Le Build ne doit donc jamais dépendre d'une installation Internet de Chocolatey au premier démarrage. + +Le pipeline suit cette séquence : + +```text +Build + ↓ +PrepareChocolateyCache + ↓ +Téléchargement de chocolatey.nupkg + ↓ +Validation de tools/chocolateyInstall.ps1 + ↓ +Preuve BootstrapReady dans BuildState + ↓ +PreparePostInstall + ↓ +Copie du cache + catalogue dans l'image + ↓ +FirstLogon / Bootstrap + ↓ +Network + ↓ +DriverCheck + ↓ +Installation locale de Chocolatey + ↓ +Exécution du catalogue +``` + +### Garantie + +Le Build échoue volontairement si `chocolatey.nupkg` est absent ou inexploitable dans `Workspace\Packages\Chocolatey`. + +Le champ `Mode=Offline` de l'entrée `chocolatey` signifie ici **bootstrap local du moteur Chocolatey**. Il ne signifie pas que les autres packages Community sont disponibles hors ligne. + +Les packages applicatifs `Offline` ne pourront être ajoutés qu'après validation de leur `.nupkg`, de leurs dépendances, de leurs payloads et d'une installation réelle sans Internet. + + +## Gestion des échecs de packages Online + +Le catalogue peut définir `FailurePolicy` : + +- `Stop` (valeur par défaut) : un échec arrête la phase Chocolatey. +- `Continue` : l'échec est conservé dans `ChocolateyResults` / `ChocolateyFailures`, journalisé en `WARNING`, puis les packages suivants continuent. + +Cette politique ne désactive jamais les contrôles de checksum et n'utilise jamais `--ignore-checksums`. + +`googlechrome` utilise actuellement `FailurePolicy = Continue` afin qu'un problème temporaire du package Community n'empêche pas les autres installations. Son résultat reste `Status = Failed` et l'échec est conservé dans `state.json`. + +### Validation réelle + +Le scénario a été validé en VM le 02/09/2026 : Chrome échoue sur son checksum, le résultat est conservé, puis `brave` et les packages suivants sont exécutés normalement. Le PostInstall reste `Completed`. Aucun contournement de checksum n'est utilisé. diff --git a/Documentation/ChocolateyAudit-2026-09-02.md b/Documentation/ChocolateyAudit-2026-09-02.md new file mode 100644 index 0000000..ef928f1 --- /dev/null +++ b/Documentation/ChocolateyAudit-2026-09-02.md @@ -0,0 +1,47 @@ +# Audit Chocolatey — 2026-09-02 + +## Décision d'architecture + +Chocolatey lui-même est traité comme un **composant bootstrap obligatoire du Build**. Le Build prépare `chocolatey.nupkg`, le valide et l'embarque dans le runtime PostInstall. Au premier démarrage, le Bootstrap attend `Network` et `DriverCheck`, puis installe Chocolatey depuis le cache local. + +Les packages applicatifs ne sont pas automatiquement offline : `Mode=Offline` exige une validation complète des payloads et une installation sans Internet. + +## Catalogue actuel + +- 29 packages activés +- 28 `Online` +- 1 `Offline` : `chocolatey` +- 25 versions figées +- 4 versions non figées : `chocolatey`, `brave`, `treesizefree`, `yacreader` + +## Contrôles ajoutés + +`Prepare-ChocolateyCache` : + +1. charge le catalogue ; +2. prépare uniquement les entrées `Mode=Offline` ; +3. exige la présence de `chocolatey.nupkg` ; +4. vérifie que le package est une archive `.nupkg` exploitable ; +5. vérifie la présence de `tools/chocolateyInstall.ps1` ; +6. inscrit `Chocolatey.BootstrapReady` dans le `BuildState` ; +7. seulement ensuite autorise `Prepare-PostInstall`. + +## Validation réalisée le 02/09/2026 + +- Diagnostic sécurisé : 815 Passed / 0 Failed / 1 Skipped. +- Build réel avec bootstrap Chocolatey : OK. +- Installation runtime Chocolatey depuis le cache local : OK. +- `FailurePolicy=Continue` : OK en VM. +- `googlechrome` : échec checksum non bloquant ; `brave` et les packages suivants continuent. + +## Suites à exécuter + +- `Tests/Unit/Modules/ChocolateyCache.Tests.ps1` +- `Tests/Unit/Modules/Chocolatey.Tests.ps1` +- `Tests/Unit/Modules/PostInstall/Bootstrap.Tests.ps1` +- `Tests/Unit/Modules/PostInstall/PostInstall.Tests.ps1` +- `Tests/Integration/BuildPipeline.Tests.ps1` + +## Règle de sécurité + +Le Build réel et la VM ont maintenant fourni la preuve fonctionnelle. Les prochaines validations Chocolatey concernent l’audit Offline des packages applicatifs. diff --git a/Documentation/ChocolateyCatalogManager.md b/Documentation/ChocolateyCatalogManager.md new file mode 100644 index 0000000..a8ac8cf --- /dev/null +++ b/Documentation/ChocolateyCatalogManager.md @@ -0,0 +1,38 @@ +# Gestion du catalogue Chocolatey + +## Objectif + +L'Assistant PimsOS permet désormais de modifier le catalogue `Config/Packages/Chocolatey.json` sans éditer directement le JSON. + +Depuis le menu principal : + +`[5] Gérer les packages Chocolatey` + +Le gestionnaire permet de : + +- afficher les packages ; +- ajouter un package ; +- supprimer un package. + +## Ajout + +Lors d'un ajout, l'utilisateur fournit : + +- ID Chocolatey ; +- version facultative ; +- catégorie facultative ; +- mode `Online` ou `Offline`. + +Un package ajouté n'est pas téléchargé immédiatement. Le Build reste responsable de la préparation du cache `Offline`. + +## Sécurité + +Le package `chocolatey` est réservé au bootstrap PimsOS et ne peut pas être supprimé via l'Assistant. + +Un package `Offline` ne doit être choisi comme tel qu'après validation de son payload, de ses dépendances et d'une installation réelle sans Internet. + +## Suppression + +La suppression retire réellement l'entrée du catalogue. Elle ne supprime pas automatiquement d'éventuels fichiers déjà présents dans un cache de travail ; le cache est reconstruit selon le catalogue lors du Build. + +> **Architecture publique :** les fonctions `Read-ChocolateyCatalog`, `Add-ChocolateyCatalogPackage` et `Remove-ChocolateyCatalogPackage` sont des fonctions internes du gestionnaire de catalogue. Elles ne font pas partie de l’API publique du module `PimsOS` et ne sont donc pas exportées par `PimsOS.psm1`/`PimsOS.psd1`. diff --git a/Documentation/ChocolateyFailurePolicy-2026-09-02.md b/Documentation/ChocolateyFailurePolicy-2026-09-02.md new file mode 100644 index 0000000..6711efb --- /dev/null +++ b/Documentation/ChocolateyFailurePolicy-2026-09-02.md @@ -0,0 +1,20 @@ +# PimsOS — Gestion des échecs Chocolatey + +## 2026-09-02 + +Ajout de `FailurePolicy` au catalogue Chocolatey : + +- `Stop` : comportement historique, arrêt sur erreur. +- `Continue` : journalise l'échec et poursuit les packages suivants. + +Le contrôle de checksum Chocolatey reste obligatoire. Aucun `--ignore-checksums` n'est utilisé. + +`googlechrome` est configuré en `Continue` afin qu'une indisponibilité ou désynchronisation temporaire du package n'empêche pas les autres applications de s'installer. + +Les échecs sont conservés dans `ChocolateyResults`, `ChocolateyFailures` et `state.json`. + +## Validation VM du 02/09/2026 + +`googlechrome` a échoué sur un checksum Chocolatey, avec `FailurePolicy=Continue`. Le PostInstall a continué immédiatement avec `brave`, puis les autres packages ont poursuivi leur installation. Le `state.json` final est `Completed`, avec une seule entrée dans `ChocolateyFailures`. + +Cet échec est classé **connu et non bloquant**. Aucun `--ignore-checksums` n'est utilisé. diff --git a/Documentation/ChocolateyPackageMatrix.md b/Documentation/ChocolateyPackageMatrix.md new file mode 100644 index 0000000..3955324 --- /dev/null +++ b/Documentation/ChocolateyPackageMatrix.md @@ -0,0 +1,110 @@ +# PimsOS — Matrice des 29 packages Chocolatey + +**Date de référence : 2026-09-02** +**Objectif :** rendre le catalogue PimsOS reproductible et déterminer, package par package, s'il peut réellement être installé hors ligne. + +## Légende + +- **VALIDÉ** : informations de package vérifiées sur Chocolatey Community ; pas encore synonyme d'OfflineReady. +- **À AUDITER** : package identifié et version de référence connue, mais le contenu du `.nupkg` / script d'installation doit encore être inspecté avant de déclarer OfflineReady. +- **CONDITIONNEL** : dépend d'une ressource Windows/ISO ou d'un composant externe ; l'installation peut être rendue offline par PimsOS, mais pas avec le `.nupkg` seul. +- **EXCLU / À REVOIR** : package inadapté au profil de base, ancien, non officiel ou présentant un intérêt discutable. + +> **Règle PimsOS :** un package n'est jamais marqué `OfflineReady=true` simplement parce que son `.nupkg` existe. Il faut démontrer que toutes les charges utiles nécessaires à l'installation sont présentes dans le cache PimsOS ou fournies par Windows/ISO. + +## Matrice + +| # | Package | Catégorie | Version de référence | Source | OfflineReady | Statut PimsOS | Motif / prochaine vérification | +|---:|---|---|---|---|---|---|---| +| 1 | `vcredist140` | Prerequisite | `14.51.36247` | Chocolatey Community | À auditer | VALIDÉ | Vérifier si l'EXE VC++ est embarqué ou téléchargé par le script. | +| 2 | `DoNet3.5` → `dotnet3.5` | Prerequisite | `3.5.20260626` | Chocolatey Community | CONDITIONNEL | CONDITIONNEL | Sur Windows < 28000, s'appuie sur DISM/Windows Component Store ; le média source doit être disponible pour garantir l'offline. | +| 3 | `chocolatey` | Chocolatey | Bootstrap | Chocolatey | Oui* | VALIDÉ | Le bootstrap PimsOS doit embarquer le `.nupkg` de Chocolatey ; ne pas dépendre de Community au premier démarrage. | +| 4 | `chocolateygui` | Chocolatey | `3.2.0` | Chocolatey Community | À auditer | VALIDÉ | Vérifier dépendances et payload réellement embarqué. | +| 5 | `chocolatey-core.extension` | Chocolatey | `1.4.0` | Chocolatey Community | Oui probable | VALIDÉ | Extension de fonctions Chocolatey ; confirmer absence de téléchargement externe. | +| 6 | `choco-package-list-backup` | Chocolatey | `2023.6.28` | Community / unofficial | Non retenu | EXCLU / À REVOIR | Package explicitement présenté comme unofficial et ancien ; inutile au fonctionnement de PimsOS. | +| 7 | `googlechrome` | Browser | `153.0.8010.12` | Chocolatey Community | À auditer | **Échec checksum non bloquant** | Validation VM : checksum mismatch, `FailurePolicy=Continue`, poursuite normale. Ne pas utiliser `--ignore-checksums`. | +| 8 | `brave` | Browser | À figer | Chocolatey Community | À auditer | À REVOIR | La page actuelle renvoie une version Beta ; PimsOS doit imposer une version stable explicite. | +| 9 | `firefox` | Browser | `154.0.1`* | Chocolatey Community | À auditer | VALIDÉ | Une `155.0.0` existe depuis le 01/09/2026 mais était encore Pending Automated Review ; ne pas la figer tant qu'elle n'est pas approuvée. | +| 10 | `tor-browser` | Browser | `15.0.20` | Chocolatey Community | À auditer | VALIDÉ | Inspecter le script et le bundle téléchargé. | +| 11 | `winrar` | Utility | `7.23.0` | Chocolatey Community | À auditer | VALIDÉ | Vérifier payload et politique de licence ; version trial/nagware. | +| 12 | `keepass` | Utility | `2.61.1` | Chocolatey Community | À auditer | VALIDÉ | Le package principal dépend de `keepass.install`; les dépendances doivent être incluses dans le cache. | +| 13 | `notepadplusplus` | Utility | `8.9.8` | Chocolatey Community | À auditer | VALIDÉ | Inspecter package/installateur et checksum. | +| 14 | `vlc` | Multimedia | `3.0.23` | Chocolatey Community | À auditer | VALIDÉ | Version Community actuelle vérifiée ; inspecter le payload externe éventuel. | +| 15 | `teamviewer` | Remote | `15.81.5` | Chocolatey Community | À auditer | VALIDÉ | Vérifier installateur et conditions d'utilisation ; pas forcément adapté au profil Minimal. | +| 16 | `iCloud` | Cloud | `7.21.0.23` | Chocolatey Community | À auditer | À REVOIR | Package très ancien côté publication et installateur Apple externe ; intérêt à confirmer. | +| 17 | `everything` | Utility | `1.4.11032` | Chocolatey Community | À auditer | VALIDÉ | Package approuvé ; vérifier que les EXE présents dans le package/cache sont réellement conservés par PimsOS. | +| 18 | `rufus` | Utility | `4.15.0` | Chocolatey Community | À auditer | VALIDÉ | Package approuvé ; vérifier payload et éventuelles dépendances. | +| 19 | `filezilla` | Network | `3.71.1` | Community | À auditer | VALIDÉ | Version actuelle vérifiée ; inspecter URL/payload. | +| 20 | `treesizefree` | Utility | À figer | Community | À auditer | VALIDÉ | Version actuelle à relever automatiquement puis figer dans le catalogue. | +| 21 | `bluescreenview` | Diagnostic | `1.55` | Community | À auditer | À REVOIR | Package inchangé depuis 2015 ; utile en dépannage mais trop ancien pour un profil de base. | +| 22 | `powertoys` | Utility | `0.101.2362` | Chocolatey Community | À auditer | VALIDÉ | Package approuvé ; vérifier que l'installateur est bien disponible localement dans le cache. | +| 23 | `rainmeter` | Desktop | `4.5.26` | Community | Non avec `.nupkg` seul | VALIDÉ | Le script utilise directement un EXE GitHub : l'EXE doit être ajouté au cache PimsOS. | +| 24 | `XnViewMP` | Graphics | `1.11.5` | Community | À auditer | VALIDÉ | Package principal avec dépendance `xnviewmp.install`; résoudre la chaîne complète. | +| 25 | `yacreader` | Reading | À figer | Community | À auditer | VALIDÉ | Inspecter le script et les URLs de téléchargement. | +| 26 | `razer-synapse-4` | Hardware | `2.5.0.882` | Community | À auditer | VALIDÉ | Package tiers maintenu par un mainteneur Community ; vérifier installateur Razer et dépendances. | +| 27 | `autohotkey` | Utility | `2.0.26` | Chocolatey Community | À auditer | VALIDÉ | Package principal de type meta/install ; résoudre `autohotkey.install`. | +| 28 | `powershell-core` | Development | `7.6.5` | Community | À auditer | VALIDÉ | Package d'installation ; vérifier MSI/EXE et comportement si PowerShell est déjà présent. | +| 29 | `soundblaster-command` | Hardware | `3.5.10` | Community | À auditer | À REVOIR | Package ancien (2024) ; vérifier compatibilité avec le matériel ciblé avant inclusion par défaut. | + +\* **Firefox :** au 02/09/2026, `155.0.0` existe mais est encore en attente de revue automatisée ; `154.0.1` est la dernière version approuvée utilisée comme référence sûre. +\* **Chocolatey :** `OfflineReady` signifie « bootstrap local PimsOS », pas « package Community garanti offline ». + +## Priorités d'audit + +### P0 — bloquants pour le mode Offline + +1. `chocolatey` +2. `vcredist140` +3. `dotnet3.5` +4. `googlechrome` +5. `firefox` +6. `brave` +7. `notepadplusplus` +8. `vlc` + +### P1 — packages courants à rendre reproductibles + +`keepass`, `everything`, `powertoys`, `filezilla`, `rufus`, `winrar`, `tor-browser`, `teamviewer`, `rainmeter`, `XnViewMP`, `autohotkey`, `powershell-core`. + +### P2 — à revoir avant intégration au profil Default + +`choco-package-list-backup`, `iCloud`, `bluescreenview`, `soundblaster-command`, `brave` tant qu'une version stable n'est pas explicitement figée. + +## Procédure de validation d'un package + +Pour passer `OfflineReady` de `À auditer` à `true`, PimsOS doit : + +1. figer `Id` + `Version` ; +2. télécharger le `.nupkg` exact ; +3. calculer et conserver son SHA-256 ; +4. extraire le package dans un espace temporaire ; +5. inspecter `.nuspec`, dépendances et `tools\chocolateyInstall.ps1` ; +6. extraire toutes les URLs externes et fichiers attendus ; +7. télécharger les payloads nécessaires dans le cache PimsOS ; +8. vérifier les checksums ; +9. construire le manifeste du package ; +10. installer en VM **sans Internet** ; +11. vérifier le code de sortie et l'état final ; +12. seulement alors déclarer `OfflineReady=true`. + +## Règle de versionnement + +Le catalogue PimsOS ne doit plus contenir uniquement : + +```json +{"Id":"firefox","Enabled":true} +``` + +mais à terme : + +```json +{ + "Id": "firefox", + "Version": "154.0.1", + "Enabled": true, + "OfflineReady": false, + "Category": "Browser" +} +``` + +La matrice est la source de décision humaine ; le catalogue JSON reste la source d'exécution du build. diff --git a/Documentation/DeveloperGuide.md b/Documentation/DeveloperGuide.md index 7d1b822..0315711 100644 --- a/Documentation/DeveloperGuide.md +++ b/Documentation/DeveloperGuide.md @@ -1,106 +1,69 @@ -# PimsOS Builder - Guide du développeur +# PimsOS Builder - Structure du projet > Version technique : 3.0.0 > -> Statut : Référence +> Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- # Objectif -Ce document décrit les bonnes pratiques à suivre pour contribuer au projet **PimsOS Builder**. +Ce document décrit l'organisation réelle du projet **PimsOS Builder**. -Il s'adresse à toute personne souhaitant : +Le projet est structuré autour d'un module PowerShell unique : -- corriger un bug ; -- développer une nouvelle fonctionnalité ; -- ajouter un nouveau type d'Action ; -- améliorer l'architecture ; -- participer à la maintenance du framework. - ---- - -# Avant de commencer - -Avant toute modification, lire les documents suivants : - -- `Architecture.md` -- `ArchitectureRules.md` -- `BuildContext.md` -- `CodingStandards.md` -- `ModuleGuide.md` -- `ProjectStructure.md` -- `Testing.md` +```text +Modules\PimsOS.psm1 +``` -Ces documents constituent les références officielles du projet. +Le module centralise le chargement des composants internes et l'exposition de l'API publique du framework. --- -# Comprendre l'architecture - -PimsOS repose sur un module PowerShell unique et une architecture en couches. - -Chaque composant possède une responsabilité clairement définie. - -Le flux logique principal est : +# Structure générale ```text -Infrastructure / Core / Configuration - │ - ▼ - Workflow - │ - ▼ - Pipeline - │ - ▼ - ActionEngine - │ - ▼ - ActionRegistry - │ - ▼ - Engine spécialisé - │ - ▼ - Manager - │ - ▼ - Module technique - │ - ▼ - Windows +PimsOS +│ +├── Build +├── Config +├── Documentation +├── ISO +├── Logs +├── Modules +├── Output +├── Packages +├── Tests +├── Workspace +├── version.json +└── README.md ``` -Avant d'ajouter du code, toujours identifier la couche et le composant responsables du besoin. +Le projet est organisé de manière à séparer clairement : ---- +- le code du framework ; +- la configuration ; +- les ressources de Build ; +- les tests ; +- la documentation ; +- les artefacts et espaces de travail. -# Cycle de développement - -Toute nouvelle fonctionnalité suit le cycle suivant : +--- -1. Identifier le besoin. -2. Vérifier qu'un composant similaire n'existe pas déjà. -3. Déterminer la couche et le composant concernés. -4. Concevoir la solution. -5. Développer. -6. Écrire ou adapter les tests. -7. Mettre à jour la documentation. -8. Valider le fonctionnement. -9. Mettre à jour les ADR si nécessaire. -10. Effectuer le commit. +# Modules ---- +Le code du framework se trouve dans : -# Organisation des composants +```text +Modules\ +``` -Les composants internes sont répartis dans : +La structure principale est : ```text -Modules/ +Modules │ ├── Actions ├── Configuration @@ -109,463 +72,420 @@ Modules/ ├── Infrastructure ├── Managers ├── Package +├── PostInstall ├── Windows ├── PimsOS.psd1 └── PimsOS.psm1 ``` -Chaque dossier possède une responsabilité clairement définie. +Les composants internes appartiennent au module PowerShell unique **PimsOS**. -Les composants internes ne sont pas des modules PowerShell indépendants. +Ils ne constituent pas des modules PowerShell indépendants. --- -# Ajouter un nouvel Engine +# PostInstall -Les Engines sont placés dans : +Le projet contient désormais un sous-système dédié à l'exécution après l'installation de Windows : ```text -Modules\Actions +Modules\PostInstall\ ``` -Chaque Engine : - -- traite un domaine d'Action défini ; -- contient la logique métier de ce domaine ; -- ne réalise pas directement les appels aux API Windows ; -- reçoit le BuildContext et l'Action ; -- met à jour l'état relevant de sa responsabilité ; -- utilise le Logger officiel ; -- propage correctement les erreurs. - -Le traitement technique est délégué au Manager approprié. +Ce dossier contient le runtime PostInstall embarqué dans l'image Windows. -Exemples : +Les composants actuellement présents comprennent notamment : ```text -RegistryEngine -FeatureEngine -PackageEngine -DriverEngine +Bootstrap.ps1 +FirstBoot.ps1 +Installer.ps1 +Network.ps1 +PostInstall.ps1 +State.ps1 +UI.ps1 +Unattend.ps1 ``` ---- - -# Ajouter un nouveau Manager - -Les Managers sont placés dans : +Le runtime est préparé pendant le Build puis installé dans l'image Windows sous : ```text -Modules\Managers +C:\ProgramData\PimsOS\PostInstall\ ``` -Ils encapsulent les opérations techniques de leur domaine. - -Ils peuvent notamment interagir avec : +Le sous-système PostInstall prend notamment en charge : -- DISM ; -- le registre Windows ; -- le système de fichiers ; -- les fournisseurs de packages ; -- les fonctionnalités Windows ; -- les autres composants techniques nécessaires à leur domaine. +- l'initialisation du runtime ; +- la persistance de l'état ; +- la détection du réseau ; +- la détection de l'accès Internet ; +- l'attente de la disponibilité réseau ; +- l'interface console du premier démarrage ; +- la préparation FirstBoot ; +- la génération de `unattend.xml`. -Les Managers ne prennent pas les décisions métier relatives aux profils ou aux Tweaks. +Le PostInstall reste séparé de la logique du Build exécutée hors ligne. --- -# Ajouter un nouveau type d'Action +# Diagnostic avant tests -Pour ajouter un nouveau type d'Action : +Avant une campagne Pester, utiliser `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour vérifier que les fichiers sélectionnés ne sont pas susceptibles de lancer un Build réel. Les validations `BUILD-CAPABLE` nécessitent explicitement `-BuildValidation -AllowBuild`. -1. identifier son domaine fonctionnel ; -2. créer l'Engine spécialisé ; -3. créer ou adapter le Manager correspondant si nécessaire ; -4. enregistrer le type dans `ActionRegistry.ps1` ; -5. ajouter les validations nécessaires ; -6. ajouter les tests Pester ; -7. mettre à jour le BuildContext ou les statistiques si nécessaire ; -8. mettre à jour la documentation. +# Tests -Le traitement doit suivre : +Les tests du framework sont organisés dans : ```text -Action - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique +Tests\ ``` -Aucun composant ne doit appeler directement un Engine spécialisé en contournant le routage normal. - ---- - -# Ajouter un Tweak - -Les Tweaks sont des définitions de configuration. - -Ils doivent rester séparés de la logique PowerShell. - -Un Tweak peut notamment contenir : - -- un identifiant ; -- une catégorie ; -- une description ; -- des Actions ; -- des métadonnées ; -- des contraintes de compatibilité. - -Les Tweaks ne doivent pas contenir de logique PowerShell exécutable. - ---- - -# Profils - -Les profils déterminent les personnalisations sélectionnées pour un Build. - -Ils peuvent activer ou désactiver des Tweaks selon le scénario choisi. +La structure comprend notamment : -Le profil ne doit pas contenir de logique d'exécution. +```text +Tests +│ +├── Unit +├── Integration +├── Acceptance +└── Legacy +``` -Le moteur de configuration construit une configuration finale avant l'exécution des Actions. +Les tests Legacy sont conservés séparément et ne participent pas à la validation officielle du framework actif. --- -# BuildContext +# Tests PostInstall -Le BuildContext est le contrat central entre les composants. +Les tests dédiés au sous-système PostInstall sont regroupés dans : -Il ne doit jamais être remplacé par un nouvel objet au cours du Build. - -Chaque composant enrichit ou met à jour uniquement les informations relevant de sa responsabilité. - -Toute nouvelle donnée doit être ajoutée au BuildContext uniquement lorsqu'elle représente un état ou une information réellement partagée entre plusieurs composants. - -Les composants ne doivent pas utiliser un état global pour transporter les données du Build. - ---- +```text +Tests\Unit\Modules\PostInstall\ +``` -# BuildState +Ils couvrent notamment : -Le BuildState représente l'état courant de l'exécution. +```text +Bootstrap.Tests.ps1 +FirstBoot.Tests.ps1 +Installer.Tests.ps1 +Network.Tests.ps1 +PostInstall.Tests.ps1 +State.Tests.ps1 +UI.Tests.ps1 +Unattend.Tests.ps1 +``` -Il doit être utilisé pour les informations de progression et d'état du Build. +Les tests PostInstall vérifient les composants individuellement ainsi que leurs contrats fonctionnels. -Les nouveaux composants doivent mettre à jour le BuildState lorsque leur contrat l'exige. +L'intégration du PostInstall dans le pipeline est également couverte par les tests d'intégration du BuildPipeline. --- -# PostInstall - -Le PostInstall est le runtime exécuté après l'installation de Windows. +# Architecture des dépendances -Il est préparé pendant le Build puis embarqué dans l'image Windows. Le runtime installé doit être autonome et ne doit pas dépendre du chemin du dépôt PimsOS utilisé sur la machine de Build. +Le framework suit une architecture en couches. -Le runtime est installé dans : +Le flux principal est : ```text -C:\ProgramData\PimsOS\PostInstall\ +Infrastructure + │ + ▼ +Core + │ + ▼ +Configuration + │ + ▼ +Workflow + │ + ▼ +Pipeline + │ + ▼ +ActionEngine + │ + ▼ +ActionRegistry + │ + ▼ +Engine spécialisé + │ + ▼ +Manager + │ + ▼ +Module technique + │ + ▼ +Windows ``` -Les composants principaux sont : +Le PostInstall constitue un sous-système runtime distinct du chemin d'exécution principal du Build. -```text -State.ps1 -Network.ps1 -UI.ps1 -PostInstall.ps1 -Bootstrap.ps1 -FirstBoot.ps1 -Unattend.ps1 -Installer.ps1 -``` - -Le flux d'exécution est : +Sa préparation est intégrée au Pipeline : ```text -Build - │ - ▼ -Runtime PostInstall +MountWim │ ▼ -unattend.xml +ApplyDrivers │ ▼ -FirstLogonCommands +PreparePostInstall │ ▼ -Bootstrap.ps1 - │ - ▼ -PostInstall +MountSoftwareHive ``` -## Bootstrap - -`Bootstrap.ps1` constitue le point d'entrée du runtime. - -Il localise le runtime installé, vérifie les composants nécessaires, charge les scripts requis puis démarre le PostInstall. - -Le Bootstrap ne doit pas dépendre du chemin du dépôt utilisé pour construire l'image. +--- -## État +# Build -`State.ps1` fournit la gestion de l'état persistant du PostInstall. +Le dossier : -L'état permet notamment de suivre : +```text +Build\ +``` -- l'état courant ; -- la phase d'exécution ; -- les tâches terminées ; -- la disponibilité du réseau ; -- les erreurs ; -- le statut final. +contient les scripts utilisés pour lancer et orchestrer le processus de Build. -## Réseau +Le lanceur principal est : -`Network.ps1` fournit les vérifications réseau. +```text +Build\Build-PimsOS.ps1 +``` -Le runtime distingue : +Le Build prépare notamment : -- la présence d'un adaptateur ; -- la disponibilité du réseau local ; -- l'accès à Internet. +- l'environnement ; +- les ressources ISO et WIM ; +- les drivers ; +- le runtime PostInstall ; +- FirstBoot ; +- la configuration ; +- les personnalisations ; +- le nettoyage ; +- la finalisation. -Un adaptateur actif ne signifie donc pas nécessairement qu'Internet est disponible. +--- -Lorsque l'accès réseau est requis et indisponible, le PostInstall peut passer par l'état `WaitingForNetwork` puis reprendre automatiquement lorsque les conditions sont réunies. +# Configuration -## Interface utilisateur +La configuration du Builder se trouve dans : -`UI.ps1` fournit l'affichage console du premier démarrage. +```text +Config\ +``` -Les fonctions principales sont : +Elle contient notamment : -```powershell -Show-PostInstallNetworkStatus -Show-PostInstallNetworkHelp -Wait-PostInstallNetworkUI +```text +Categories.json +Profiles\ +Tweaks\ ``` -La couche UI présente l'état du réseau et les instructions nécessaires à l'utilisateur. Elle ne doit pas absorber la logique métier du PostInstall. +Les profils sélectionnent les personnalisations et les Tweaks définissent les Actions à exécuter. -## Installer +La configuration reste séparée du code PowerShell. -`Installer.ps1` prépare le runtime dans l'image Windows et installe la configuration FirstBoot. +--- -Il vérifie la présence des fichiers requis avant leur copie et installe `unattend.xml` dans : +# Documentation + +La documentation technique se trouve dans : ```text -C:\Windows\Panther\unattend.xml +Documentation\ ``` -## Tests PostInstall +Elle comprend notamment : -Les composants PostInstall doivent disposer de tests Pester adaptés. +```text +API.md +Architecture.md +ArchitectureRules.md +BuildContext.md +DeveloperGuide.md +GettingStarted.md +Legacy.md +Lifecycle.md +Milestones.md +ModuleGuide.md +PostInstall.md +Prerequisites.md +ProjectStatus.md +ProjectStructure.md +ReleaseNotes.md +Roadmap.md +Schema.md +TechnicalDecisions.md +Testing.md +``` -Les tests doivent couvrir, lorsque cela est pertinent : +Les décisions d'architecture sont documentées dans : -- l'initialisation de l'état ; -- les vérifications réseau ; -- la disponibilité d'Internet ; -- l'attente et la reprise réseau ; -- les erreurs ; -- le Bootstrap ; -- l'installation du runtime ; -- la génération de `unattend.xml` ; -- l'intégration au Pipeline. +```text +Documentation\ADR\ +``` --- -# Compatibilité Windows +# ISO -Le Builder n'est pas conçu pour une seule version de Windows. +Le dossier : -Les informations relatives à la cible doivent provenir : - -- de l'image Windows ; -- du BuildContext ; -- de la configuration ; -- des contraintes déclarées par les Tweaks. +```text +ISO\ +``` -Les composants ne doivent pas coder en dur une version telle que `24H2` ou `25H2` lorsqu'il s'agit d'une information de configuration ou de compatibilité. +contient les ressources et éléments nécessaires aux opérations liées aux médias Windows et à la génération de l'image. --- -# Journalisation +# Logs -Toute opération importante doit être journalisée via : +Le dossier : -```powershell -Write-Log +```text +Logs\ ``` -Les appels à : +contient les journaux générés pendant les opérations du Builder. -```powershell -Write-Host -``` +--- -sont interdits dans la logique métier. +# Output -`Write-Verbose` et `Write-Debug` peuvent être utilisés pour les informations de diagnostic appropriées. +Le dossier : -L'interface console du runtime PostInstall constitue une exception fonctionnelle : `UI.ps1` utilise volontairement `Write-Host` pour afficher les informations destinées à l'utilisateur lors du premier démarrage. Cette utilisation doit rester limitée à la couche UI. +```text +Output\ +``` ---- +est destiné aux artefacts produits par le Build. -# Gestion des erreurs +--- -Les erreurs doivent : +# Workspace -- être détectées ; -- être traitées au niveau approprié ; -- être journalisées lorsque nécessaire ; -- être propagées lorsqu'elles ne peuvent pas être traitées localement. +Le dossier : -Ne jamais masquer une exception sans justification. +```text +Workspace\ +``` -Dans un module, ne pas utiliser : +contient les ressources de travail temporaires utilisées pendant les opérations du Builder. -```powershell -exit -``` +Il peut notamment contenir : -pour interrompre arbitrairement le processus appelant. +- les copies de travail ; +- les montages WIM ; +- les fichiers temporaires ; +- les ressources intermédiaires. --- -# Tests - -Toute nouvelle fonctionnalité importante doit être accompagnée de tests Pester adaptés. +# Packages -Les tests doivent couvrir, lorsque cela est pertinent : +Le dossier : -- le fonctionnement nominal ; -- les paramètres invalides ; -- les cas d'erreur ; -- les cas limites ; -- les changements d'état ; -- les statistiques ; -- les régressions. +```text +Packages\ +``` -Pour une correction de bug, ajouter ou adapter un test de régression lorsque cela est pertinent. +contient les ressources liées aux packages ou aux fournisseurs utilisés par le projet. --- -# Documentation +# Classes -Toute évolution importante doit mettre à jour les documents concernés. +Le dossier historique : -Selon le changement, cela peut inclure : +```text +Classes\ +``` -- `API.md` -- `Architecture.md` -- `ArchitectureRules.md` -- `BuildContext.md` -- `CodingStandards.md` -- `ModuleGuide.md` -- `ProjectStatus.md` -- `ProjectStructure.md` -- `Roadmap.md` -- `Milestones.md` -- `ReleaseNotes.md` -- `Testing.md` +n'est plus utilisé comme couche de classes métier du Builder. -Si l'architecture évolue, une nouvelle ADR doit être créée lorsque la décision le justifie. +Les composants actifs suivent désormais l'architecture modulaire du framework. --- -# Revue de code +# Legacy -Avant un commit important, vérifier : +Les composants historiques peuvent être conservés dans : -- le code est syntaxiquement valide ; -- le module se charge correctement ; -- les tests concernés passent ; -- aucune erreur critique n'est introduite ; -- le BuildContext respecte son contrat ; -- les nouveaux composants respectent les Architecture Rules ; -- la documentation est à jour ; -- les ADR sont mises à jour si nécessaire. +```text +Tools\ +Tests\Legacy\ +``` -PowerShell n'étant pas compilé comme un langage classique, la validation doit notamment porter sur le parsing, le chargement du module et l'exécution des tests. +Ils ne sont pas chargés par : ---- +```text +Modules\PimsOS.psm1 +``` -# Workflow Git +et ne participent pas au fonctionnement normal du Builder. -Chaque évolution importante suit le processus suivant : +Aucune nouvelle fonctionnalité ne doit être développée dans ces emplacements. -1. Développement. -2. Validation locale. -3. Exécution des tests. -4. Mise à jour de la documentation. -5. Vérification des ADR si nécessaire. -6. Vérification de `git status`. -7. Commit Git. -8. Push vers le dépôt distant lorsque l'évolution est prête. +--- -Un commit doit représenter une évolution cohérente. +# Point d'entrée ---- +Le point d'entrée du framework est : -# Ajout d'un composant +```text +Modules\PimsOS.psm1 +``` -Avant de créer un nouveau composant : +L'API publique est centralisée dans ce module. -- vérifier qu'un composant existant ne répond pas déjà au besoin ; -- définir clairement sa responsabilité ; -- choisir la couche appropriée ; -- identifier ses dépendances ; -- prévoir ses tests ; -- documenter l'évolution lorsque nécessaire. +La fonction publique principale actuellement définie est : -Un composant ne doit pas cumuler plusieurs responsabilités indépendantes. +```powershell +Initialize-PimsOS +``` --- -# Philosophie +# Principe général -Le développement de PimsOS Builder repose sur les principes suivants : +L'organisation du projet doit préserver la séparation entre : -- simplicité ; -- lisibilité ; -- modularité ; -- réutilisabilité ; -- testabilité ; -- maintenabilité ; -- extensibilité. +```text +Configuration + ↓ +Framework + ↓ +Build + ↓ +Runtime PostInstall + ↓ +Windows installé +``` -Le respect de l'architecture est prioritaire sur la rapidité de développement. +Le Build prépare l'image. -Une nouvelle fonctionnalité doit, lorsque l'architecture le permet, avoir un impact limité sur les composants existants. +Le runtime PostInstall s'exécute ensuite dans Windows installé. -Les duplications doivent être évitées lorsqu'une solution réutilisable existe. +Cette séparation permet de maintenir une architecture claire, testable et évolutive. --- # Références +Consulter également : + - `Architecture.md` - `ArchitectureRules.md` - `BuildContext.md` -- `CodingStandards.md` +- `DeveloperGuide.md` - `ModuleGuide.md` -- `ProjectStructure.md` +- `PostInstall.md` - `Testing.md` -- `TechnicalDecisions.md` -- `Documentation\ADR\` +- `Legacy.md` diff --git a/Documentation/DocumentationSync-2026-09-01.md b/Documentation/DocumentationSync-2026-09-01.md new file mode 100644 index 0000000..87946a5 --- /dev/null +++ b/Documentation/DocumentationSync-2026-09-01.md @@ -0,0 +1,113 @@ +# PimsOS Builder — Synchronisation documentaire du 01/09/2026 + +## Objet + +Cette note fixe l’état de référence du projet après la reprise du 01/09/2026. Elle complète la note historique du 31/08 et ne réécrit pas les faits historiques. + +## Source de vérité + +L’ordre de priorité est désormais explicite : + +1. **Code réellement présent dans le dépôt** pour le comportement technique. +2. **Tests réellement exécutés et résultats observables** pour la validation. +3. **Documentation active** pour expliquer l’état et la méthode de travail. +4. **ADR et notes historiques** pour conserver les décisions et les états passés. + +Une documentation ancienne ne doit jamais être utilisée pour conclure qu'un comportement existe encore sans vérifier le code et les tests. + +## État technique vérifié + +- Version technique : **3.0.0**. +- PowerShell local : **7.6.5**. +- Pester local : **5.8.0**. +- Le pipeline réel complet a été exécuté avec succès le **01/09/2026**. +- Edition construite : **Windows 11 Professionnel, index 6**. +- 27 Tweaks ont été appliqués dans le Build réel. +- Le PostInstall a été préparé puis validé dans le WIM. +- Le WIM a été démonté et sauvegardé sans montage résiduel. +- La synchronisation `Workspace\Sources\install.wim` → `Workspace\ISO\Source\sources\install.wim` a été effectuée avec vérification SHA256. +- `oscdimg.exe` a été découvert automatiquement par `Get-PimsOSOsCdImgPath` depuis le Windows ADK installé ; aucune modification du pipeline n'a été nécessaire pour ce point. +- ISO produite : `Output\PimsOS_3.0.0_20260901_180342.iso`. +- Taille annoncée : **7,9 Go**. +- Code retour du Build réel : **0**. +- Etat final : **Completed**. +- Aucun montage WIM restant après le Build. + +## Validation des tests du 01/09 + +Le diagnostic statique du dépôt a été utilisé avant exécution : + +- Unit : **63 fichiers de tests** inventoriés. +- Integration : **4 fichiers de tests** inventoriés. +- Build-capable : **3 fichiers** classés comme potentiellement capables de lancer une opération de Build. +- Unknown : **0** après correction du test `Complete-Build`. + +Une campagne d'intégration ciblée a ensuite produit : + +```text +20 Passed +0 Failed +0 Skipped +``` + +Ce résultat concerne la campagne exécutée et ne doit pas être confondu avec le nombre de fichiers inventoriés. + +La dernière campagne complète communiquée avant cette reprise reste celle du 31/08 : **971 Passed / 0 Failed / 1 Skipped**. Elle doit être considérée comme historique tant qu'une nouvelle campagne complète n'a pas été exécutée et enregistrée. + +## Diagnostic sécurisé + +Le nouvel outil `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` est désormais la première étape de la méthode de validation. + +Il distingue : + +- `SAFE` : aucun appel Build dangereux non neutralisé détecté par l'analyse statique ; +- `BUILD-CAPABLE` : présence d'un appel pouvant lancer une opération réelle ; +- `UNKNOWN` : preuve insuffisante de neutralisation. + +Règles : + +- `-InventoryOnly` n'exécute aucun test ; +- `-Unit` et `-Integration` n'exécutent que les tests `SAFE` ; +- `-BuildValidation` exige explicitement `-AllowBuild` ; +- les tests `UNKNOWN` restent bloqués automatiquement ; +- un Build réel n'est jamais une conséquence implicite d'un diagnostic normal. + +## CI GitHub + +La connexion GitHub/ChatGPT a entraîné une succession de commits rapides et donc une accumulation de runs PimsOS CI. + +Le workflow `.github/workflows/pester.yml` a été durci le 01/09/2026 : + +- ajout d'un groupe `concurrency` par workflow/ref ; +- `cancel-in-progress: true` pour conserver le run le plus récent ; +- ajout de filtres `paths` afin que les modifications purement documentaires ne déclenchent pas inutilement la CI. + +Le but est de permettre les synchronisations rapides sans empiler des dizaines de runs identiques. + +## Synchronisation dépôt local / GitHub + +Le contrôle du 01/09 a montré que le principe « local = GitHub » ne doit pas être supposé : + +- le dépôt GitHub contenait la note historique `Documentation/DocumentationSync-2026-08-31.md` qui n'était pas dans l'archive fournie ; +- l'outil `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` présent dans l'archive locale n'était pas encore présent dans `main` sur GitHub au moment du contrôle ; +- le workflow CI a depuis été mis à jour directement dans GitHub. + +Cette divergence est précisément la raison pour laquelle le contrôle Git/GitHub doit précéder toute nouvelle séance. + +## Validation ISO restante + +Le Build réel est maintenant démontré. La prochaine validation est celle de l'artefact : + +1. vérifier le SHA256 de l'ISO produite ; +2. monter/tester l'ISO produite sans modifier le pipeline ; +3. vérifier `sources\install.wim` ; +4. vérifier l'image Windows et les modifications attendues ; +5. réaliser une installation Hyper-V ; +6. valider FirstBoot/PostInstall ; +7. valider ensuite Rufus/physique si nécessaire. + +## Règle de continuité + +Aucune nouvelle modification du pipeline ne doit être proposée simplement parce qu'un test manuel a été exécuté dans un contexte différent du Build réel. + +Exemple validé le 01/09 : après éjection de l'ISO, `I:` n'existait plus ; appeler manuellement la fonction de copie avec `I:` absent produisait logiquement une erreur. Le Build réel, lui, montait l'ISO sur `I:` puis copiait correctement son contenu. Le comportement réel du pipeline a donc été confirmé sans modification. diff --git a/Documentation/DocumentationSync-2026-09-02.md b/Documentation/DocumentationSync-2026-09-02.md new file mode 100644 index 0000000..efcdc1f --- /dev/null +++ b/Documentation/DocumentationSync-2026-09-02.md @@ -0,0 +1,126 @@ +# PimsOS Builder — Synchronisation documentaire du 02/09/2026 + +## Objet + +Cette note synchronise la documentation avec l’état réel du code, du Build et de la validation VM au 02/09/2026. Elle constitue la nouvelle référence de reprise avant les opérations Git. + +## État technique + +Version technique : **3.0.0** + +Le framework est architecturalement stabilisé. La chaîne de production est fonctionnelle de bout en bout sur Windows 11 25H2 avec le scénario de drivers `CurrentSystem`. + +## Build de référence + +```text +ISO : Output\PimsOS_3.0.0_20260902_141928.iso +Taille : 11,29 Go +Edition : Windows 11 Professionnel +Index : 6 +Build ID : 6302b96b-2cd1-4ba7-b66e-dc8b9980eebe +Code retour : 0 +WIM SHA256 : B6BA0B8E8474761380FCC26DB165DC786162EA916B8D35192A977C49E72E9941 +``` + +### Drivers + +- export `CurrentSystem` réussi ; +- préparation dans `Workspace\Drivers\CurrentSystem` ; +- injection DISM réussie ; +- aucune erreur de propriété `Success` sur les actions dynamiques ; +- phase Drivers terminée avec succès. + +## Tests + +Dernière campagne diagnostic sûre : + +```text +66 fichiers analysés +66 SAFE +0 BUILD-CAPABLE +0 UNKNOWN + +815 Passed +0 Failed +1 Skipped +0 Inconclusive +0 NotRun +816 Total +``` + +Rapport : `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md`. + +Le `Skipped` est conditionnel et intentionnel. + +## PostInstall / Finalization VM + +La VM de test confirme : + +- Network disponible ; +- DriverCheck réussi ; +- Chocolatey installé localement depuis le cache PimsOS ; +- packages Online installés ; +- `FailurePolicy=Continue` fonctionnelle ; +- PostInstall `Completed=true` et `Failed=false` ; +- `Verification.Verified=true` ; +- Cleanup programmé et sans erreur ; +- scripts de démarrage supprimés après sortie du Bootstrap ; +- `unattend.xml` supprimé ; +- `state.json`, `PostInstall.log` et cache Chocolatey conservés. + +## Chocolatey + +Le modèle retenu reste : + +```text +Build + ↓ +chocolatey.nupkg obligatoire Offline + ↓ +cache PimsOS + ↓ +ISO + ↓ +FirstLogon + ↓ +Network + ↓ +DriverCheck + ↓ +bootstrap Chocolatey local + ↓ +Offline local / Online Community +``` + +`googlechrome` est le seul échec fonctionnel observé sur la campagne VM. Il est enregistré avec `FailurePolicy=Continue`. L’échec de checksum n’est pas contourné. Les packages suivants continuent normalement. + +## Microsoft Store + +Store reste fourni par Windows. La VM confirme l’ouverture du Store, l’installation d’iCloud depuis Store et le fonctionnement des Widgets. Aucun changement de l’intégration Store PimsOS n’est nécessaire. + +## Documentation mise à jour + +Les documents centraux ont été synchronisés ou nettoyés : + +- `ProjectStatus.md` ; +- `PostInstall.md` ; +- `Testing.md` ; +- `ReleaseNotes.md` ; +- `Backlog.md` ; +- `Roadmap.md` ; +- `Lifecycle.md` ; +- `TechnicalDecisions.md` ; +- `Schema.md` ; +- `CurrentSprint.md` ; +- `ChocolateyArchitecture.md` ; +- `ChocolateyFailurePolicy-2026-09-02.md` ; +- `ChocolateyPackageMatrix.md` ; +- documentation générale dont les dates de mise à jour actives. + +Les doublons documentaires accidentels présents dans plusieurs fichiers ont été supprimés. + +## Prochaine étape + +**Git est maintenant la prochaine opération immédiate.** + +Avant tout commit : vérifier `git status`, le diff, le remote, la branche et les artefacts ignorés. diff --git a/Documentation/GettingStarted.md b/Documentation/GettingStarted.md index e172448..0315711 100644 --- a/Documentation/GettingStarted.md +++ b/Documentation/GettingStarted.md @@ -1,114 +1,28 @@ -# PimsOS Builder - Bien démarrer +# PimsOS Builder - Structure du projet > Version technique : 3.0.0 > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 - -Bienvenue dans **PimsOS Builder**. - -Ce document explique comment préparer un environnement de développement, vérifier le projet, exécuter les tests et démarrer un Build. +> Dernière mise à jour : 2026-09-02 --- -# Prérequis - -Avant de commencer, disposer au minimum des éléments suivants : - -| Logiciel | Version / recommandation | -|-----------|---------------------------| -| Windows | Windows 11 | -| PowerShell | 7.6.x | -| Git | Version compatible avec le dépôt | -| Visual Studio Code | Version récente | -| Extension PowerShell | Version récente | -| Pester | 5.x | -| DISM | Disponible dans Windows | +# Objectif -Consultez également : +Ce document décrit l'organisation réelle du projet **PimsOS Builder**. -- `Prerequisites.md` - ---- - -# Cloner le dépôt - -```powershell -git clone https://github.com/Pims/PimsOS.git - -cd PimsOS -``` - ---- - -# Vérifier PowerShell - -```powershell -$PSVersionTable.PSVersion -``` - -Version de référence du développement actuel : +Le projet est structuré autour d'un module PowerShell unique : ```text -7.6.x -``` - ---- - -# Vérifier Git - -```powershell -git --version -``` - ---- - -# Vérifier DISM - -```powershell -dism /? -``` - -DISM doit être disponible et fonctionnel dans l'environnement Windows. - ---- - -# Vérifier Pester - -Afficher les versions installées : - -```powershell -Get-InstalledModule Pester -ErrorAction SilentlyContinue -``` - -Installer Pester 5.x si nécessaire : - -```powershell -Install-Module Pester -Scope CurrentUser -``` - -Vérifier ensuite : - -```powershell -Get-Module Pester -ListAvailable +Modules\PimsOS.psm1 ``` ---- - -# Ouvrir le projet - -Depuis la racine du dépôt : - -```powershell -code . -``` +Le module centralise le chargement des composants internes et l'exposition de l'API publique du framework. --- -# Vérifier l'arborescence - -La structure principale du projet est notamment organisée ainsi : +# Structure générale ```text PimsOS @@ -119,448 +33,459 @@ PimsOS ├── ISO ├── Logs ├── Modules -│ ├── Actions -│ ├── Configuration -│ ├── Core -│ ├── Image -│ ├── Infrastructure -│ ├── Managers -│ ├── Package -│ ├── Windows -│ ├── PimsOS.psd1 -│ └── PimsOS.psm1 ├── Output +├── Packages ├── Tests ├── Workspace -└── version.json +├── version.json +└── README.md ``` -Le répertoire `Classes` n'est plus utilisé comme couche de classes métier du Builder. +Le projet est organisé de manière à séparer clairement : + +- le code du framework ; +- la configuration ; +- les ressources de Build ; +- les tests ; +- la documentation ; +- les artefacts et espaces de travail. --- -# Comprendre l'architecture +# Modules + +Le code du framework se trouve dans : -Le framework repose sur un module PowerShell unique et une architecture en couches. +```text +Modules\ +``` -Le flux logique principal est : +La structure principale est : ```text -Infrastructure / Core / Configuration - │ - ▼ - Workflow - │ - ▼ - Pipeline - │ - ▼ - ActionEngine - │ - ▼ - ActionRegistry - │ - ▼ - Engine spécialisé - │ - ▼ - Manager - │ - ▼ - Module technique - │ - ▼ - Windows +Modules +│ +├── Actions +├── Configuration +├── Core +├── Image +├── Infrastructure +├── Managers +├── Package +├── PostInstall +├── Windows +├── PimsOS.psd1 +└── PimsOS.psm1 ``` -Avant de modifier le code, prendre connaissance des documents d'architecture. +Les composants internes appartiennent au module PowerShell unique **PimsOS**. + +Ils ne constituent pas des modules PowerShell indépendants. --- -# Vérifier le projet +# PostInstall -Avant toute modification importante, exécuter les tests : +Le projet contient désormais un sous-système dédié à l'exécution après l'installation de Windows : -```powershell -Invoke-Pester +```text +Modules\PostInstall\ ``` -Pour exécuter explicitement les tests du Builder : +Ce dossier contient le runtime PostInstall embarqué dans l'image Windows. -```powershell -Invoke-Pester -Path .\Tests\Unit -Invoke-Pester -Path .\Tests\Integration +Les composants actuellement présents comprennent notamment : + +```text +Bootstrap.ps1 +FirstBoot.ps1 +Installer.ps1 +Network.ps1 +PostInstall.ps1 +State.ps1 +UI.ps1 +Unattend.ps1 ``` -Pour exécuter les tests d'acceptance : +Le runtime est préparé pendant le Build puis installé dans l'image Windows sous : -```powershell -Invoke-Pester -Path .\Tests\Acceptance +```text +C:\ProgramData\PimsOS\PostInstall\ ``` -Les tests obligatoires doivent être validés avant de considérer une évolution comme stable. +Le sous-système PostInstall prend notamment en charge : + +- l'initialisation du runtime ; +- la persistance de l'état ; +- la détection du réseau ; +- la détection de l'accès Internet ; +- l'attente de la disponibilité réseau ; +- l'interface console du premier démarrage ; +- la préparation FirstBoot ; +- la génération de `unattend.xml`. + +Le PostInstall reste séparé de la logique du Build exécutée hors ligne. --- -# Charger le module +# Diagnostic avant tests + +Avant une campagne Pester, utiliser `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour vérifier que les fichiers sélectionnés ne sont pas susceptibles de lancer un Build réel. Les validations `BUILD-CAPABLE` nécessitent explicitement `-BuildValidation -AllowBuild`. + +# Tests -Le module PimsOS est défini par : +Les tests du framework sont organisés dans : ```text -Modules\PimsOS.psd1 -Modules\PimsOS.psm1 +Tests\ ``` -Charger le module : +La structure comprend notamment : -```powershell -Import-Module .\Modules\PimsOS.psd1 +```text +Tests +│ +├── Unit +├── Integration +├── Acceptance +└── Legacy ``` -Vérifier son chargement : +Les tests Legacy sont conservés séparément et ne participent pas à la validation officielle du framework actif. -```powershell -Get-Module PimsOS -``` +--- -Vérifier la fonction publique : +# Tests PostInstall -```powershell -Get-Command Initialize-PimsOS +Les tests dédiés au sous-système PostInstall sont regroupés dans : + +```text +Tests\Unit\Modules\PostInstall\ ``` ---- +Ils couvrent notamment : -# Point d'entrée public +```text +Bootstrap.Tests.ps1 +FirstBoot.Tests.ps1 +Installer.Tests.ps1 +Network.Tests.ps1 +PostInstall.Tests.ps1 +State.Tests.ps1 +UI.Tests.ps1 +Unattend.Tests.ps1 +``` -L'API publique actuelle du module est : +Les tests PostInstall vérifient les composants individuellement ainsi que leurs contrats fonctionnels. -```powershell -Initialize-PimsOS -``` +L'intégration du PostInstall dans le pipeline est également couverte par les tests d'intégration du BuildPipeline. -Exemple : +--- -```powershell -Import-Module .\Modules\PimsOS.psd1 +# Architecture des dépendances -$Context = Initialize-PimsOS -``` +Le framework suit une architecture en couches. -Le BuildContext retourné permet ensuite d'inspecter l'état du Build : +Le flux principal est : -```powershell -$Context.BuildState +```text +Infrastructure + │ + ▼ +Core + │ + ▼ +Configuration + │ + ▼ +Workflow + │ + ▼ +Pipeline + │ + ▼ +ActionEngine + │ + ▼ +ActionRegistry + │ + ▼ +Engine spécialisé + │ + ▼ +Manager + │ + ▼ +Module technique + │ + ▼ +Windows ``` -ou le rapport : +Le PostInstall constitue un sous-système runtime distinct du chemin d'exécution principal du Build. -```powershell -$Context.Report +Sa préparation est intégrée au Pipeline : + +```text +MountWim + │ + ▼ +ApplyDrivers + │ + ▼ +PreparePostInstall + │ + ▼ +MountSoftwareHive ``` --- -# Premier Build +# Build -Le script de lancement du projet est : +Le dossier : ```text -Build\Build-PimsOS.ps1 +Build\ ``` -Lancer le Builder depuis la racine du projet : +contient les scripts utilisés pour lancer et orchestrer le processus de Build. -```powershell -.\Build\Build-PimsOS.ps1 +Le lanceur principal est : + +```text +Build\Build-PimsOS.ps1 ``` -Le Builder prépare notamment : +Le Build prépare notamment : - l'environnement ; -- le Recovery ; -- les vérifications des prérequis ; - les ressources ISO et WIM ; +- les drivers ; +- le runtime PostInstall ; +- FirstBoot ; - la configuration ; - les personnalisations ; -- le nettoyage et la finalisation. - -Le traitement exact dépend de l'état du projet et de la configuration utilisée. +- le nettoyage ; +- la finalisation. --- -# Premier lancement +# Configuration -Pour tester uniquement le chargement et le point d'entrée du module : +La configuration du Builder se trouve dans : -```powershell -Import-Module .\Modules\PimsOS.psd1 - -$Context = Initialize-PimsOS +```text +Config\ ``` -Pour lancer ensuite le processus de Build complet : +Elle contient notamment : -```powershell -.\Build\Build-PimsOS.ps1 +```text +Categories.json +Profiles\ +Tweaks\ ``` -`Initialize-PimsOS` et `Build-PimsOS.ps1` ne doivent pas être considérés comme deux étapes obligatoires à exécuter successivement dans le cadre d'un même Build. +Les profils sélectionnent les personnalisations et les Tweaks définissent les Actions à exécuter. -Le script `Build-PimsOS.ps1` constitue le lanceur du processus de Build, tandis que `Initialize-PimsOS` est l'entrée fonctionnelle publique du module. +La configuration reste séparée du code PowerShell. --- -# PostInstall - -Le PostInstall est exécuté après l'installation de Windows lors du premier démarrage. - -Le Build prépare et embarque le runtime dans l'image Windows. Le runtime installé est autonome et ne dépend pas du chemin du dépôt PimsOS utilisé pendant le Build. +# Documentation -Le runtime est installé dans : +La documentation technique se trouve dans : ```text -C:\ProgramData\PimsOS\PostInstall\ +Documentation\ ``` -Les composants principaux sont : +Elle comprend notamment : ```text -State.ps1 -Network.ps1 -UI.ps1 -PostInstall.ps1 -Bootstrap.ps1 -FirstBoot.ps1 -Unattend.ps1 -Installer.ps1 +API.md +Architecture.md +ArchitectureRules.md +BuildContext.md +DeveloperGuide.md +GettingStarted.md +Legacy.md +Lifecycle.md +Milestones.md +ModuleGuide.md +PostInstall.md +Prerequisites.md +ProjectStatus.md +ProjectStructure.md +ReleaseNotes.md +Roadmap.md +Schema.md +TechnicalDecisions.md +Testing.md +``` + +Les décisions d'architecture sont documentées dans : + +```text +Documentation\ADR\ ``` -Le flux général est : +--- + +# ISO + +Le dossier : ```text -Build - │ - ▼ -Installation du runtime - │ - ▼ -Génération unattend.xml - │ - ▼ -Installation de Windows - │ - ▼ -FirstLogonCommands - │ - ▼ -Bootstrap.ps1 - │ - ▼ -PostInstall +ISO\ ``` -## Vérification réseau +contient les ressources et éléments nécessaires aux opérations liées aux médias Windows et à la génération de l'image. + +--- -Le PostInstall distingue : +# Logs -- la présence d'un adaptateur réseau ; -- la disponibilité du réseau local ; -- l'accès à Internet. +Le dossier : -Un adaptateur actif ne signifie donc pas nécessairement qu'Internet est disponible. +```text +Logs\ +``` -Lorsque le réseau ou Internet est indisponible, le runtime peut entrer dans l'état `WaitingForNetwork`, afficher l'état courant à l'utilisateur et reprendre automatiquement lorsque les conditions sont réunies. +contient les journaux générés pendant les opérations du Builder. -## Interface utilisateur +--- -L'interface console du premier démarrage est fournie par `UI.ps1`. +# Output -Les fonctions principales sont : +Le dossier : -```powershell -Show-PostInstallNetworkStatus -Show-PostInstallNetworkHelp -Wait-PostInstallNetworkUI +```text +Output\ ``` -La couche UI reste séparée de la logique métier du PostInstall. +est destiné aux artefacts produits par le Build. + +--- -## État +# Workspace -`State.ps1` permet de conserver l'état persistant du PostInstall, notamment : +Le dossier : -- le statut ; -- la phase courante ; -- les tâches terminées ; -- la disponibilité réseau ; -- les erreurs. +```text +Workspace\ +``` -## Tests +contient les ressources de travail temporaires utilisées pendant les opérations du Builder. -Le PostInstall dispose de tests unitaires couvrant notamment : +Il peut notamment contenir : -- l'état ; -- la détection réseau ; -- l'accès Internet ; -- l'attente réseau ; -- la reprise ; -- l'interface utilisateur ; -- le Bootstrap ; -- l'installation du runtime ; -- la génération de `unattend.xml`. +- les copies de travail ; +- les montages WIM ; +- les fichiers temporaires ; +- les ressources intermédiaires. --- -# Premier Tweak - -Les personnalisations sont définies dans les ressources de configuration du projet. +# Packages -Les Tweaks sont séparés des profils. +Le dossier : -Un Tweak contient notamment : +```text +Packages\ +``` -- son identifiant ; -- sa catégorie ; -- sa description ; -- ses Actions ; -- ses métadonnées ; -- ses éventuelles contraintes de compatibilité. +contient les ressources liées aux packages ou aux fournisseurs utilisés par le projet. -Les profils déterminent quelles personnalisations sont sélectionnées pour un Build. +--- -Les Tweaks ne contiennent pas de logique PowerShell exécutable. +# Classes ---- +Le dossier historique : -# Ajouter une fonctionnalité +```text +Classes\ +``` -Avant d'écrire du code : +n'est plus utilisé comme couche de classes métier du Builder. -1. identifier la responsabilité concernée ; -2. vérifier qu'un composant similaire n'existe pas déjà ; -3. déterminer la couche concernée ; -4. définir le contrat du nouveau composant ; -5. développer ; -6. ajouter les tests ; -7. mettre à jour la documentation ; -8. vérifier les ADR si nécessaire. +Les composants actifs suivent désormais l'architecture modulaire du framework. --- -# Ajouter un nouveau type d'Action +# Legacy -Les étapes sont les suivantes : +Les composants historiques peuvent être conservés dans : -1. créer l'Engine spécialisé dans `Modules\Actions` ; -2. créer ou adapter le Manager dans `Modules\Managers` si nécessaire ; -3. enregistrer le nouveau type dans `ActionRegistry.ps1` ; -4. ajouter les validations nécessaires ; -5. mettre à jour le BuildContext ou les statistiques si nécessaire ; -6. créer les tests Pester ; -7. mettre à jour la documentation. +```text +Tools\ +Tests\Legacy\ +``` -Le flux doit respecter : +Ils ne sont pas chargés par : ```text -Action - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Module technique +Modules\PimsOS.psm1 ``` +et ne participent pas au fonctionnement normal du Builder. + +Aucune nouvelle fonctionnalité ne doit être développée dans ces emplacements. + --- -# Documentation +# Point d'entrée -Avant toute contribution, il est recommandé de lire : +Le point d'entrée du framework est : -- `API.md` -- `Architecture.md` -- `ArchitectureRules.md` -- `BuildContext.md` -- `CodingStandards.md` -- `DeveloperGuide.md` -- `ModuleGuide.md` -- `ProjectStructure.md` -- `Testing.md` +```text +Modules\PimsOS.psm1 +``` -Ces documents décrivent l'architecture, les conventions et la stratégie de tests du framework. +L'API publique est centralisée dans ce module. + +La fonction publique principale actuellement définie est : + +```powershell +Initialize-PimsOS +``` --- -# Workflow recommandé +# Principe général -Chaque évolution importante suit le processus suivant : +L'organisation du projet doit préserver la séparation entre : ```text -Besoin - ↓ -Analyse - ↓ -Conception - ↓ -Développement - ↓ -Tests +Configuration ↓ -Validation +Framework ↓ -Documentation +Build ↓ -Revue +Runtime PostInstall ↓ -Commit Git +Windows installé ``` -Il n'y a pas de phase de compilation classique du framework PowerShell. +Le Build prépare l'image. -La validation repose notamment sur : +Le runtime PostInstall s'exécute ensuite dans Windows installé. -- le parsing PowerShell ; -- le chargement du module ; -- les tests Pester ; -- les validations fonctionnelles nécessaires. +Cette séparation permet de maintenir une architecture claire, testable et évolutive. --- -# Bonnes pratiques - -Avant un commit important : +# Références -- exécuter les tests ; -- vérifier le chargement du module ; -- vérifier les journaux ; -- vérifier le Build si le changement le concerne ; -- mettre à jour la documentation ; -- vérifier les ADR si l'architecture a évolué ; -- vérifier `git status`. - ---- +Consulter également : -# Besoin d'aide ? - -Les principales références du projet sont : - -- `API.md` - `Architecture.md` - `ArchitectureRules.md` - `BuildContext.md` - `DeveloperGuide.md` - `ModuleGuide.md` +- `PostInstall.md` - `Testing.md` -- `ProjectStatus.md` - -Ces documents constituent la référence du fonctionnement et du développement de **PimsOS Builder**. +- `Legacy.md` diff --git a/Documentation/Lifecycle.md b/Documentation/Lifecycle.md index fd7922f..64e0f9a 100644 --- a/Documentation/Lifecycle.md +++ b/Documentation/Lifecycle.md @@ -1,302 +1,543 @@ -# PimsOS Builder - Cycle de vie +# PimsOS Builder - Feuille de route > Version technique : 3.0.0 > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- +> Les références antérieures au 02/09/2026 sont historiques ; l’état courant est celui du 02/09/2026. + + # Objectif -Ce document décrit le cycle de vie d'une évolution au sein de **PimsOS Builder**. +Cette feuille de route présente les grandes orientations du projet **PimsOS Builder**. -Il couvre les principales étapes, depuis l'identification d'un besoin jusqu'à son intégration dans le projet. +Elle décrit les évolutions prévues pour le framework, le moteur de Build et les fonctionnalités permettant de construire des images Windows personnalisées. -Ce cycle garantit : +Elle présente les objectifs à moyen et long terme sans remplacer le backlog technique détaillé. -- la cohérence de l'architecture ; -- la qualité du code ; -- la stabilité du Builder ; -- la traçabilité des évolutions ; -- la reproductibilité des validations. +Les évolutions importantes de l'architecture sont documentées dans les **Architecture Decision Records (ADR)**. --- -# Vue d'ensemble - -```text -Besoin - │ - ▼ -Analyse - │ - ▼ -Conception - │ - ▼ -Développement - │ - ▼ -Tests - │ - ▼ -Validation - │ - ▼ -Documentation - │ - ▼ -Revue - │ - ▼ -Commit Git - │ - ▼ -Jalon / Publication -``` +# Vision + +PimsOS Builder a pour objectif de devenir un framework capable de construire automatiquement des images Windows personnalisées à partir d'images compatibles. + +Le moteur doit rester indépendant d'une version spécifique de Windows et pouvoir évoluer avec les versions compatibles avec les mécanismes de déploiement utilisés. + +Le projet repose notamment sur les principes suivants : + +* modularité ; +* automatisation ; +* reproductibilité ; +* maintenabilité ; +* testabilité ; +* séparation claire des responsabilités. + +À terme, la création d'une image PimsOS complète doit pouvoir être réalisée à partir d'un processus de Build automatisé et reproductible. --- -# 1. Identification du besoin +# État actuel + +## Architecture -Toute évolution débute par un besoin clairement identifié. +✅ **Stabilisée** -Il peut s'agir : +L'architecture 3.0.0 repose notamment sur : -- d'une nouvelle fonctionnalité ; -- d'une correction de bug ; -- d'une optimisation ; -- d'une évolution de l'architecture ; -- d'une amélioration du pipeline ; -- de l'ajout d'un nouveau type d'action. +* un module PowerShell unique ; +* un BuildContext centralisé ; +* un BuildState ; +* un Workflow ; +* un Pipeline ; +* un ActionRegistry ; +* un ActionEngine ; +* des Engines spécialisés ; +* des Managers spécialisés ; +* des composants techniques organisés par domaine ; +* une configuration pilotée par les données ; +* une API publique centralisée ; +* une couverture Pester importante. -Le besoin doit être compris avant toute modification du code. +Le point d'entrée public principal est : + +```text id="v6d0zp" +Initialize-PimsOS +``` + +--- + +## Développement + +🚧 **En cours** + +Les principales fondations du framework sont maintenant en place : + +* Recovery ; +* vérification de l'environnement ; +* vérification des prérequis ; +* gestion des ISO ; +* gestion des WIM ; +* sélection des images Windows ; +* gestion des ruches du registre ; +* chargement des catégories ; +* chargement des Tweaks ; +* chargement des profils ; +* fusion de la configuration ; +* validation ; +* routage des Actions ; +* Engines spécialisés ; +* Managers spécialisés ; +* Wizard ; +* configuration des drivers ; +* préparation PostInstall ; +* préparation FirstBoot ; +* reporting ; +* nettoyage et finalisation du Build. + +Le Build réel de bout en bout et la validation VM de FirstBoot/PostInstall sont désormais démontrés. Le développement se concentre sur la synchronisation Git, la validation physique/Rufus, l’audit Offline des packages et les fonctions encore au backlog. --- -# 2. Analyse +# Phases du projet + +## Phase 1 — Fondations -Cette étape consiste à déterminer : +### Objectifs -- les composants concernés ; -- les impacts sur le BuildContext ; -- les dépendances ; -- les risques de régression ; -- les besoins en documentation. +* [x] Définir l'architecture générale. +* [x] Mettre en place la documentation. +* [x] Définir les conventions de développement. +* [x] Mettre en place les ADR. +* [x] Construire les premiers composants techniques. +* [x] Définir le BuildContext. +* [x] Définir le BuildState. -Les évolutions majeures peuvent nécessiter la création d'une ADR. +### Statut + +✅ **Terminée** --- -# 3. Conception +## Phase 2 — Module PowerShell unique + +### Objectifs -Avant toute implémentation : +* [x] Créer `PimsOS.psm1`. +* [x] Créer `PimsOS.psd1`. +* [x] Centraliser le chargement des composants. +* [x] Centraliser l'API publique. +* [x] Introduire `Initialize-PimsOS`. +* [x] Supprimer le modèle à plusieurs modules indépendants. +* [x] Valider le module PowerShell unique. +* [x] Valider l'exposition de l'API publique. -- identifier les modules concernés ; -- vérifier qu'aucun composant existant ne répond déjà au besoin ; -- définir les nouvelles structures de données si nécessaire ; -- préserver la séparation des responsabilités. +### Statut -Les nouvelles fonctionnalités doivent respecter les Architecture Rules. +✅ **Terminée** --- -# 4. Développement +## Phase 3 — Framework de Build + +### Objectifs + +* [x] Finaliser le BuildContext. +* [x] Développer le BuildState. +* [x] Développer le Pipeline. +* [x] Développer le Workflow. +* [x] Mettre en place Recovery. +* [x] Vérifier les prérequis de l'environnement. +* [x] Gérer les images WIM. +* [x] Gérer les ISO. +* [x] Détecter les images Windows. +* [x] Permettre la sélection de l'image à personnaliser. +* [x] Gérer les ruches du registre. +* [x] Charger les définitions de Tweaks. +* [x] Charger les profils. +* [x] Fusionner profils et Tweaks. +* [x] Valider la configuration. +* [x] Mettre en place ActionRegistry. +* [x] Mettre en place ActionEngine. +* [x] Développer les Engines spécialisés. +* [x] Développer les Managers spécialisés. +* [x] Intégrer le Wizard. +* [x] Intégrer la configuration des drivers. +* [x] Intégrer la préparation PostInstall au pipeline. + +### Statut + +✅ **Stabilisée** + +Le moteur d'orchestration est suffisamment structuré et testé pour poursuivre la finalisation de la production d'image. -Le développement est réalisé en respectant : +--- -- CodingStandards.md ; -- DeveloperGuide.md ; -- ModuleGuide.md ; -- Architecture.md. +## Phase 4 — Génération d'images Windows -Les composants doivent respecter l'architecture en couches. +### Objectifs -Toute nouvelle logique métier doit passer par le BuildContext. +* [x] Préparer les images ISO. +* [x] Manipuler les images WIM. +* [x] Effectuer les opérations DISM nécessaires. +* [x] Préparer les drivers dans le pipeline. +* [x] Préparer le runtime PostInstall dans le WIM. +* [x] Générer `unattend.xml`. +* [ ] Finaliser la génération automatique de l'ISO. +* [ ] Valider automatiquement l'ISO générée. +* [ ] Valider un Build complet de bout en bout. +* [ ] Améliorer la gestion des erreurs de production. +* [ ] Optimiser les performances. +* [ ] Valider l'artefact ISO final. ---- +### Statut -# 5. Validation +🟡 **En cours** -Avant d'être exécutée, toute évolution est validée. +--- -La validation vérifie notamment : +## Phase 5 — Personnalisation + +### Objectifs + +* [x] Profils. +* [x] Tweaks. +* [x] Catégories. +* [x] RegistryEngine. +* [x] ServiceEngine. +* [x] FeatureEngine. +* [x] CapabilityEngine. +* [x] PackageEngine. +* [x] DriverEngine. +* [x] FileEngine. +* [x] FolderEngine. +* [x] EnvironmentEngine. +* [x] ScheduledTaskEngine. +* [x] ShortcutEngine. +* [x] PackageManager. +* [x] DriverManager. +* [x] Managers spécialisés. +* [ ] Implémenter le provider Chocolatey. +* [ ] Implémenter le provider Winget. +* [ ] Intégrer Microsoft Store. +* [ ] Compléter les fonctionnalités de personnalisation restantes. + +### Statut + +🟡 **En cours** -- la structure des fichiers JSON ; -- les propriétés obligatoires ; -- les identifiants ; -- les catégories ; -- les niveaux ; -- les tags ; -- les groupes ; -- les actions ; -- les versions supportées ; -- les scores. +--- -Aucune configuration invalide ne doit atteindre le Pipeline. +## Phase 6 — PostInstall / FirstBoot + +### Objectifs + +* [x] Implémenter State. +* [x] Implémenter Network. +* [x] Implémenter le moteur PostInstall. +* [x] Implémenter Bootstrap. +* [x] Implémenter FirstBoot. +* [x] Implémenter Unattend. +* [x] Implémenter Installer. +* [x] Implémenter UI PostInstall. +* [x] Intégrer `PreparePostInstall` au BuildPipeline. +* [x] Valider l'injection du runtime dans un WIM temporaire. +* [x] Valider la génération de `unattend.xml`. +* [x] Valider le namespace `urn:schemas-microsoft-com:unattend`. +* [x] Valider `wcm:action="add"`. +* [x] Valider la commande vers `Bootstrap.ps1`. +* [x] Ajouter l'affichage réseau du premier démarrage. +* [x] Ajouter l'attente réseau avec interface console. +* [ ] Valider l'exécution réelle de `FirstLogonCommands`. +* [ ] Valider le premier démarrage réel de Windows. +* [ ] Valider la reprise réseau réelle. +* [x] Intégrer le provider Chocolatey. +* [ ] Intégrer Winget. +* [ ] Intégrer Microsoft Store. + +### Statut + +🟡 **Implémenté et testé — validation réelle FirstBoot restante** + +Le sous-système PostInstall est fonctionnel au niveau de la préparation et de l'intégration au Build. + +La validation réelle de `FirstLogonCommands`, Bootstrap, PostInstall et Finalization est maintenant effectuée en VM. --- -# 6. Tests +## Phase 7 — Stabilisation et qualité + +### Objectifs + +* [x] Mettre en place Pester 5.x. +* [x] Mettre en place une couverture importante des composants. +* [x] Tester les Engines spécialisés. +* [x] Tester les Managers. +* [x] Tester Configuration. +* [x] Tester Registry. +* [x] Tester Workflow et composants Core. +* [x] Tester Wizard. +* [x] Tester les drivers. +* [x] Tester PostInstall. +* [x] Tester FirstBoot. +* [x] Tester Network. +* [x] Tester l'intégration du BuildPipeline. +* [x] Séparer les tests officiels des tests Legacy. +* [ ] Compléter les tests Recovery. +* [ ] Compléter les tests Security. +* [ ] Étendre les tests d'intégration. +* [ ] Valider les Builds complets. +* [ ] Finaliser la documentation technique. + +### Résultat actuel + +La dernière campagne officielle de tests donne : + +```text id="fny8oe" +815 Passed / 0 Failed / 1 Skipped (reference — 02/09/2026) +0 Failed +1 Skipped +0 Inconclusive +0 NotRun +``` -Les tests permettent de vérifier : +Le seul test ignoré est conditionnel et concerne le cas d'une catégorie sans groupes alors que toutes les catégories actuellement définies possèdent des groupes. -- le fonctionnement nominal ; -- les erreurs ; -- les cas limites ; -- les régressions. +Les tests historiques présents dans : -Les nouveaux composants doivent disposer de tests adaptés. +```text id="pydg7a" +Tests\Legacy +``` + +sont conservés séparément et ne font pas partie de la campagne officielle. -Les tests Legacy ne participent pas à cette validation. +### Statut + +🟡 **En cours** --- -# 7. Documentation +## Phase 8 — Première version stable -Toute évolution importante doit être documentée. +### Objectifs -Les documents concernés peuvent être : +* [ ] Pipeline validé de bout en bout. +* [ ] Génération ISO stable. +* [ ] Composants nécessaires finalisés. +* [ ] PostInstall validé sur un environnement Windows réel. +* [ ] FirstBoot validé. +* [ ] Tests validés. +* [ ] Documentation synchronisée. +* [ ] API publique stabilisée. +* [ ] Build reproductible. +* [ ] Absence d'anomalie bloquante. +* [ ] Artefact ISO final validé. +* [ ] Publication d'une première version stable. -- API.md -- Architecture.md -- ArchitectureRules.md -- BuildContext.md -- DeveloperGuide.md -- ModuleGuide.md -- ReleaseNotes.md +### Statut -La documentation évolue en même temps que le code. +⏳ **À venir** --- -# 8. Gestion du code source - -Une fois la fonctionnalité validée : +# Composants restant à développer ou compléter -- vérifier le formatage ; -- exécuter les tests ; -- mettre à jour la documentation ; -- effectuer un commit Git. +Les principaux éléments identifiés sont : -Chaque commit doit représenter une évolution cohérente et fonctionnelle. +* finalisation de la génération ISO ; +* validation complète du Build de bout en bout ; +* validation réelle FirstBoot ; +* validation de la reprise réseau réelle ; +* `Converters.ps1` ; +* provider Chocolatey ; +* provider Winget ; +* intégration Microsoft Store ; +* couverture complémentaire de `Recovery.ps1` ; +* couverture complémentaire de `Security.ps1` ; +* enrichissement du Reporting ; +* validation de l'artefact ISO final. --- -# 9. Publication +# Tests -Une nouvelle version peut être publiée lorsque : +Les objectifs actuels sont : -- le pipeline est valide ; -- les tests sont réussis ; -- la documentation est à jour ; -- les évolutions prévues sont terminées. +* maintenir la couverture des composants existants ; +* compléter les tests des composants encore partiellement couverts ; +* étendre les tests d'intégration ; +* ajouter des tests de régression ; +* automatiser progressivement l'exécution des tests ; +* conserver une séparation stricte entre les tests actifs et les tests historiques. -Les informations de version sont centralisées dans : +Les tests Pester constituent la base de validation du framework. -```text -version.json +## Campagne officielle + +La campagne officielle utilise : + +```text id="7kyx7n" +Tests\Unit +Tests\Integration ``` -Le numéro de version est automatiquement utilisé par le Builder. +Les tests historiques sont conservés dans : ---- +```text id="j31c2d" +Tests\Legacy +``` -# Correction d'un bug +Ils ne sont pas inclus dans la campagne officielle. -Une correction suit le même cycle de vie qu'une nouvelle fonctionnalité. +--- -Le processus recommandé est : +# Documentation -1. reproduire le problème ; -2. identifier le composant concerné ; -3. écrire ou adapter un test ; -4. corriger le code ; -5. exécuter le pipeline ; -6. mettre à jour la documentation ; -7. réaliser un commit Git. +Les objectifs actuels sont : -Cette méthode limite les régressions. +* maintenir la documentation synchronisée avec le code ; +* documenter l'API publique ; +* documenter l'architecture ; +* maintenir les règles d'architecture ; +* maintenir le statut du projet ; +* maintenir le backlog et les jalons ; +* maintenir la feuille de route ; +* documenter les décisions architecturales dans les ADR ; +* documenter le fonctionnement du PostInstall et de FirstBoot. --- -# Évolutions d'architecture - -Toute évolution importante de l'architecture doit : +# Priorités actuelles -- respecter les Architecture Rules ; -- préserver la compatibilité du BuildContext ; -- documenter les changements ; -- être accompagnée de tests. +## Priorité 1 — Génération ISO -Si nécessaire, une nouvelle ADR est créée. +Finaliser la chaîne permettant de produire une ISO PimsOS complète. --- -# Amélioration continue +## Priorité 2 — Validation de bout en bout -Le cycle de développement de PimsOS Builder évolue avec le projet. +Réaliser et valider un Build complet depuis l'ISO source jusqu'à +l'artefact final. -Toute amélioration doit viser à : +Cette validation doit notamment vérifier : -- simplifier le code ; -- renforcer la modularité ; -- améliorer les performances ; -- réduire les duplications ; -- améliorer la qualité des tests ; -- faciliter la maintenance. +* la préparation du WIM ; +* l'application des Tweaks ; +* l'application des drivers ; +* la préparation PostInstall ; +* la reconstruction de l'ISO ; +* la génération de l'artefact final ; +* la cohérence du résultat. --- -# Résumé +## Priorité 3 — Validation FirstBoot -Chaque évolution suit le processus suivant : +Valider le comportement réel de : -```text -Besoin - ↓ -Analyse - ↓ -Conception +```text id="cprqdr" +unattend.xml ↓ -Développement +FirstLogonCommands ↓ -Validation +Bootstrap.ps1 ↓ -Tests - ↓ -Documentation - ↓ -Commit Git - ↓ -Publication +PostInstall ``` -Aucune fonctionnalité ne doit être intégrée sans avoir suivi ce cycle. -## PostInstall +Cette validation doit être effectuée sur un environnement Windows réel. -Après préparation du WIM : +--- -MountWim - ↓ -ApplyDrivers - ↓ -PreparePostInstall - ↓ -MountSoftwareHive +## Priorité 4 — Providers packages + +Implémenter les providers : + +* Chocolatey ; +* Winget ; +* Microsoft Store. + +--- + +## Priorité 5 — Couverture et stabilité + +Compléter : + +* Recovery ; +* Security ; +* Reporting ; +* tests d'intégration ; +* tests de régression ; +* validation des Builds complets. + +--- + +## Priorité 6 — Documentation et release -`PreparePostInstall` installe le runtime dans : +Maintenir la documentation synchronisée et préparer les conditions nécessaires à une première release stable. -C:\ProgramData\PimsOS\PostInstall +--- + +# Prochain objectif technique + +Le prochain objectif technique majeur est la **finalisation de la chaîne de production de l'image PimsOS**. + +Les travaux prioritaires sont : + +1. finaliser le traitement du WIM ; +2. finaliser la reconstruction de l'ISO ; +3. valider le Build complet ; +4. vérifier les artefacts générés ; +5. valider le cycle FirstBoot réel ; +6. compléter les rapports ; +7. vérifier le nettoyage final ; +8. documenter le processus de production. + +--- + +# Hors périmètre actuel + +À ce stade, les éléments suivants ne constituent pas une priorité du développement : -et génère : +* interface graphique complète ; +* support d'autres systèmes d'exploitation ; +* déploiement distribué ; +* versions de Windows incompatibles avec les mécanismes techniques utilisés par le Builder. -C:\Windows\Panther\unattend.xml +Ces éléments pourront être réévalués ultérieurement. -Le premier démarrage Windows doit ensuite lancer le Bootstrap -via `FirstLogonCommands`. +--- + +# Suivi + +La feuille de route est revue à chaque jalon majeur. + +Les fonctionnalités terminées sont reportées dans : + +* `ReleaseNotes.md` ; +* `Milestones.md` ; +* `ProjectStatus.md`. + +Les évolutions architecturales importantes sont documentées dans les ADR. + +--- -Cette dernière étape reste à valider dans un environnement Windows -réel. +# Documents associés + +* `Architecture.md` +* `ArchitectureRules.md` +* `ProjectStatus.md` +* `ProjectStructure.md` +* `Lifecycle.md` +* `Milestones.md` +* `ReleaseNotes.md` +* `Testing.md` +* `PostInstall.md` +* `Prerequisites.md` +* `Documentation\ADR\` diff --git a/Documentation/Milestones.md b/Documentation/Milestones.md index 8aad8b9..f08b544 100644 --- a/Documentation/Milestones.md +++ b/Documentation/Milestones.md @@ -4,10 +4,13 @@ > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- +> Les références antérieures au 02/09/2026 sont historiques ; l’état courant est celui du 02/09/2026. + + # Objectif Ce document présente les principaux jalons du projet **PimsOS Builder**. @@ -26,7 +29,7 @@ Un jalon est considéré comme atteint lorsque les objectifs définis pour celui | Milestone 2 | Module PowerShell unique | ✅ Terminé | | Milestone 3 | Moteur de Build | ✅ Terminé | | Milestone 4 | Framework de configuration | ✅ Stabilisé | -| Milestone 5 | Génération d'image | 🟡 En cours | +| Milestone 5 | Génération d'image | 🟢 Validé techniquement | | Milestone 6 | Première version stable | ⏳ À venir | --- @@ -379,7 +382,7 @@ Ils ne font pas partie de la campagne officielle. La dernière campagne de référence donne : ```text -971 Passed +815 Passed / 0 Failed / 1 Skipped (reference — 02/09/2026) 0 Failed 1 Skipped 0 Inconclusive @@ -391,7 +394,7 @@ Le seul test ignoré est conditionnel et concerne le cas d'une catégorie sans g La campagne complète s'exécute actuellement en environ : ```text -5,69 secondes +durée variable selon les tests et l’environnement ``` --- diff --git a/Documentation/ModuleGuide.md b/Documentation/ModuleGuide.md index 31ca8fa..0315711 100644 --- a/Documentation/ModuleGuide.md +++ b/Documentation/ModuleGuide.md @@ -1,51 +1,66 @@ -# PimsOS Builder - Guide des modules +# PimsOS Builder - Structure du projet > Version technique : 3.0.0 > -> Statut : Référence +> Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- # Objectif -Ce document décrit l'organisation des composants internes du module **PimsOS Builder**. +Ce document décrit l'organisation réelle du projet **PimsOS Builder**. -PimsOS repose sur un module PowerShell unique. Les sous-répertoires de `Modules` regroupent les composants internes par responsabilité. +Le projet est structuré autour d'un module PowerShell unique : -L'objectif est de garantir : +```text +Modules\PimsOS.psm1 +``` -- une architecture cohérente ; -- une séparation claire des responsabilités ; -- une maintenance simplifiée ; -- un faible couplage ; -- une bonne testabilité ; -- une évolution localisée des fonctionnalités. +Le module centralise le chargement des composants internes et l'exposition de l'API publique du framework. --- -# Architecture générale - -PimsOS Builder est distribué sous la forme d'un module PowerShell unique : +# Structure générale ```text -Modules -├── PimsOS.psd1 -└── PimsOS.psm1 +PimsOS +│ +├── Build +├── Config +├── Documentation +├── ISO +├── Logs +├── Modules +├── Output +├── Packages +├── Tests +├── Workspace +├── version.json +└── README.md ``` -Le fichier `PimsOS.psm1` constitue le point d'entrée du module et charge les composants internes. - -Les fichiers situés dans les sous-répertoires de `Modules` sont des composants internes du même module. +Le projet est organisé de manière à séparer clairement : -Aucun sous-module PowerShell indépendant n'est utilisé pour les composants internes. +- le code du framework ; +- la configuration ; +- les ressources de Build ; +- les tests ; +- la documentation ; +- les artefacts et espaces de travail. --- -# Organisation réelle des composants +# Modules + +Le code du framework se trouve dans : -L'organisation actuelle est : +```text +Modules\ +``` + +La structure principale est : ```text Modules @@ -57,534 +72,420 @@ Modules ├── Infrastructure ├── Managers ├── Package +├── PostInstall ├── Windows -│ ├── PimsOS.psd1 └── PimsOS.psm1 ``` -Chaque répertoire correspond à une responsabilité fonctionnelle ou technique. +Les composants internes appartiennent au module PowerShell unique **PimsOS**. ---- +Ils ne constituent pas des modules PowerShell indépendants. -# Core +--- -Le dossier `Core` contient le cœur du framework. +# PostInstall -Structure principale : +Le projet contient désormais un sous-système dédié à l'exécution après l'installation de Windows : ```text -Core -├── ActionRegistry.ps1 -├── BuildContext.ps1 -├── Complete-Build.ps1 -├── Core.ps1 -├── Engine.ps1 -├── Pipeline.ps1 -├── Report.ps1 -└── Workflow.ps1 +Modules\PostInstall\ ``` -Responsabilités : - -- création et initialisation du BuildContext ; -- gestion du BuildState ; -- orchestration du Workflow ; -- orchestration du Pipeline ; -- routage des Actions ; -- reporting ; -- finalisation du Build. - -Le Core contient la logique centrale du framework mais ne doit pas remplacer les Engines spécialisés ou les Managers techniques. - ---- +Ce dossier contient le runtime PostInstall embarqué dans l'image Windows. -# Infrastructure - -Le dossier `Infrastructure` contient les services transverses utilisés par plusieurs composants. - -Structure actuelle : +Les composants actuellement présents comprennent notamment : ```text -Infrastructure -├── Check.ps1 -├── Converters.ps1 -├── Logger.ps1 -├── Recovery.ps1 -├── Security.ps1 -├── Service.ps1 -└── Validation.ps1 +Bootstrap.ps1 +FirstBoot.ps1 +Installer.ps1 +Network.ps1 +PostInstall.ps1 +State.ps1 +UI.ps1 +Unattend.ps1 ``` -## Check +Le runtime est préparé pendant le Build puis installé dans l'image Windows sous : -Fournit les vérifications nécessaires à l'environnement du Builder. +```text +C:\ProgramData\PimsOS\PostInstall\ +``` -## Logger +Le sous-système PostInstall prend notamment en charge : -Fournit le système de journalisation centralisé. +- l'initialisation du runtime ; +- la persistance de l'état ; +- la détection du réseau ; +- la détection de l'accès Internet ; +- l'attente de la disponibilité réseau ; +- l'interface console du premier démarrage ; +- la préparation FirstBoot ; +- la génération de `unattend.xml`. -## Recovery +Le PostInstall reste séparé de la logique du Build exécutée hors ligne. -Prépare et nettoie l'environnement lorsqu'un Build précédent a laissé des ressources exploitables ou invalides. +--- -## Security +# Diagnostic avant tests -Contient les fonctions techniques liées aux contrôles de sécurité du framework. +Avant une campagne Pester, utiliser `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` pour vérifier que les fichiers sélectionnés ne sont pas susceptibles de lancer un Build réel. Les validations `BUILD-CAPABLE` nécessitent explicitement `-BuildValidation -AllowBuild`. -## Service +# Tests -Contient les fonctions techniques relatives aux services Windows utilisées par les composants concernés. +Les tests du framework sont organisés dans : -## Validation +```text +Tests\ +``` -Centralise les validations communes. +La structure comprend notamment : -## Converters +```text +Tests +│ +├── Unit +├── Integration +├── Acceptance +└── Legacy +``` -Le fichier existe dans l'architecture mais son implémentation n'est pas encore réalisée. +Les tests Legacy sont conservés séparément et ne participent pas à la validation officielle du framework actif. --- -# Configuration - -Le dossier `Configuration` contient les composants responsables de la construction de la configuration du Build. +# Tests PostInstall -Structure actuelle : +Les tests dédiés au sous-système PostInstall sont regroupés dans : ```text -Configuration -├── Categories.ps1 -├── Configuration.ps1 -├── Profile.ps1 -└── Tweak.ps1 +Tests\Unit\Modules\PostInstall\ ``` -Responsabilités : +Ils couvrent notamment : + +```text +Bootstrap.Tests.ps1 +FirstBoot.Tests.ps1 +Installer.Tests.ps1 +Network.Tests.ps1 +PostInstall.Tests.ps1 +State.Tests.ps1 +UI.Tests.ps1 +Unattend.Tests.ps1 +``` -- chargement des catégories ; -- chargement des Tweaks ; -- chargement des profils ; -- validation des définitions ; -- fusion Profil + Tweaks ; -- construction de la configuration finale ; -- création des Actions à exécuter. +Les tests PostInstall vérifient les composants individuellement ainsi que leurs contrats fonctionnels. -Les données de configuration restent séparées de la logique d'exécution. +L'intégration du PostInstall dans le pipeline est également couverte par les tests d'intégration du BuildPipeline. --- -# Actions +# Architecture des dépendances -Le dossier `Actions` contient les Engines spécialisés. +Le framework suit une architecture en couches. -Structure actuelle : +Le flux principal est : ```text -Actions -├── ActionEngine.ps1 -├── CapabilityEngine.ps1 -├── CommandEngine.ps1 -├── DriverEngine.ps1 -├── EnvironmentEngine.ps1 -├── FeatureEngine.ps1 -├── FileEngine.ps1 -├── FolderEngine.ps1 -├── PackageEngine.ps1 -├── RegistryEngine.ps1 -├── ScheduledTaskEngine.ps1 -├── ServiceEngine.ps1 -└── ShortcutEngine.ps1 +Infrastructure + │ + ▼ +Core + │ + ▼ +Configuration + │ + ▼ +Workflow + │ + ▼ +Pipeline + │ + ▼ +ActionEngine + │ + ▼ +ActionRegistry + │ + ▼ +Engine spécialisé + │ + ▼ +Manager + │ + ▼ +Module technique + │ + ▼ +Windows ``` -`ActionEngine` constitue le point central de routage. +Le PostInstall constitue un sous-système runtime distinct du chemin d'exécution principal du Build. -Les autres Engines prennent en charge les domaines spécialisés. - -La chaîne d'exécution est : +Sa préparation est intégrée au Pipeline : ```text -Action +MountWim │ ▼ -ActionEngine +ApplyDrivers │ ▼ -ActionRegistry +PreparePostInstall │ ▼ -Engine spécialisé +MountSoftwareHive ``` -Un nouveau type d'Action doit être enregistré dans l'ActionRegistry. - --- -# Managers - -Le dossier `Managers` contient les composants qui encapsulent les opérations techniques des différents domaines fonctionnels. +# Build -Structure actuelle : +Le dossier : ```text -Managers -├── CapabilityManager.ps1 -├── CommandManager.ps1 -├── DriverManager.ps1 -├── EnvironmentManager.ps1 -├── FeatureManager.ps1 -├── FileManager.ps1 -├── FolderManager.ps1 -├── PackageManager.ps1 -├── ScheduledTaskManager.ps1 -└── ShortcutManager.ps1 +Build\ ``` -Les Managers sont utilisés par les Engines spécialisés. +contient les scripts utilisés pour lancer et orchestrer le processus de Build. -La chaîne de traitement est : +Le lanceur principal est : ```text -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Provider ou module technique +Build\Build-PimsOS.ps1 ``` -Les Managers ne doivent pas contenir la logique générale du Workflow ou du Pipeline. +Le Build prépare notamment : ---- +- l'environnement ; +- les ressources ISO et WIM ; +- les drivers ; +- le runtime PostInstall ; +- FirstBoot ; +- la configuration ; +- les personnalisations ; +- le nettoyage ; +- la finalisation. -# Package +--- -Le dossier `Package` contient les providers techniques destinés aux gestionnaires de packages. +# Configuration -Structure actuelle : +La configuration du Builder se trouve dans : ```text -Package -├── Chocolatey.ps1 -└── Winget.ps1 +Config\ ``` -Les providers prévus sont : +Elle contient notamment : -- Chocolatey ; -- Winget. +```text +Categories.json +Profiles\ +Tweaks\ +``` -Les fichiers existent dans l'architecture, mais leurs implémentations techniques ne sont pas encore disponibles. +Les profils sélectionnent les personnalisations et les Tweaks définissent les Actions à exécuter. -Le routage vers les providers est déjà prévu au niveau de `PackageManager`. +La configuration reste séparée du code PowerShell. --- -# Windows +# Documentation -Le dossier `Windows` contient les composants spécifiques aux technologies Windows. - -Structure actuelle : +La documentation technique se trouve dans : ```text -Windows -└── Registry.ps1 +Documentation\ ``` -## Registry +Elle comprend notamment : -Le composant Registry prend notamment en charge : +```text +API.md +Architecture.md +ArchitectureRules.md +BuildContext.md +DeveloperGuide.md +GettingStarted.md +Legacy.md +Lifecycle.md +Milestones.md +ModuleGuide.md +PostInstall.md +Prerequisites.md +ProjectStatus.md +ProjectStructure.md +ReleaseNotes.md +Roadmap.md +Schema.md +TechnicalDecisions.md +Testing.md +``` -- le registre Windows offline ; -- les ruches ; -- les clés ; -- les valeurs ; -- les types de données ; -- le montage des ruches ; -- le démontage des ruches. +Les décisions d'architecture sont documentées dans : -Registry est un composant technique utilisé par les Actions concernées. +```text +Documentation\ADR\ +``` --- -# Image +# ISO -Le dossier `Image` contient les composants responsables de la manipulation des images Windows. - -Structure actuelle : +Le dossier : ```text -Image -├── Dism.ps1 -├── Iso.ps1 -└── Wim.ps1 +ISO\ ``` -## Dism - -Encapsule les opérations DISM utilisées pendant le Build. - -## Iso - -Gère les opérations liées aux images ISO. - -## Wim - -Gère les images WIM et leur cycle de montage, modification et démontage. - -Les opérations d'image sont des opérations techniques et ne doivent pas contenir la logique métier des Tweaks. +contient les ressources et éléments nécessaires aux opérations liées aux médias Windows et à la génération de l'image. --- -# Chargement des composants +# Logs -Les composants sont chargés par : +Le dossier : ```text -Modules\PimsOS.psm1 +Logs\ ``` -Le module charge notamment les domaines dans l'ordre suivant : - -```text -Infrastructure - │ - ▼ -Core - │ - ▼ -Configuration - │ - ▼ -Managers - │ - ▼ -Package - │ - ▼ -Windows - │ - ▼ -Image - │ - ▼ -Actions -``` - -Cet ordre correspond au chargement des composants internes dans le module. - -Il ne doit pas être confondu avec le flux logique d'exécution d'une Action. +contient les journaux générés pendant les opérations du Builder. --- -# Flux logique d'une Action +# Output -Le flux d'exécution d'une Action est : +Le dossier : ```text -Configuration - │ - ▼ -Action - │ - ▼ -ActionEngine - │ - ▼ -ActionRegistry - │ - ▼ -Engine spécialisé - │ - ▼ -Manager - │ - ▼ -Provider ou module technique - │ - ▼ -Windows +Output\ ``` -Cette séparation permet de maintenir les responsabilités à leur niveau approprié. +est destiné aux artefacts produits par le Build. --- -# BuildContext +# Workspace -Tous les composants concernés par le Build utilisent le même BuildContext. +Le dossier : -Le BuildContext constitue le contrat central entre les couches. - -Il contient notamment : +```text +Workspace\ +``` -- les informations du projet ; -- le BuildState ; -- la configuration ; -- les ressources ; -- les résultats ; -- les statistiques ; -- les rapports. +contient les ressources de travail temporaires utilisées pendant les opérations du Builder. -Les composants ne doivent pas utiliser une variable globale pour transporter l'état du Build. +Il peut notamment contenir : -Les variables de portée `script:` peuvent être utilisées pour l'état interne limité d'un composant, par exemple une table de providers, mais elles ne doivent pas servir à transporter l'état du Build entre les couches. +- les copies de travail ; +- les montages WIM ; +- les fichiers temporaires ; +- les ressources intermédiaires. --- -# ActionRegistry - -L'ActionRegistry centralise l'association entre les types d'Actions et leurs Engines. - -Son rôle est de permettre au moteur de résoudre un Engine spécialisé sans coder directement cette association dans chaque appelant. +# Packages -Pour ajouter un nouveau type d'Action : +Le dossier : -1. créer l'Engine spécialisé ; -2. définir son contrat ; -3. enregistrer le type dans l'ActionRegistry ; -4. ajouter les tests correspondants ; -5. mettre à jour la documentation si nécessaire. +```text +Packages\ +``` -L'ActionEngine ne doit pas contenir une série de conditions spécifiques à chaque type d'Action lorsque cette logique peut être gérée par l'ActionRegistry. +contient les ressources liées aux packages ou aux fournisseurs utilisés par le projet. --- -# Validation - -La couche de validation vérifie les données avant leur utilisation par les composants d'exécution. +# Classes -Selon le composant concerné, les contrôles peuvent porter notamment sur : +Le dossier historique : -- les catégories ; -- les identifiants ; -- les groupes ; -- les tags ; -- les niveaux ; -- les versions supportées ; -- les scores ; -- les Actions ; -- les propriétés obligatoires. +```text +Classes\ +``` -Les composants doivent conserver leurs propres validations nécessaires au respect de leur contrat. +n'est plus utilisé comme couche de classes métier du Builder. -La validation globale ne dispense donc pas les Engines et Managers de vérifier les paramètres obligatoires dont ils dépendent. +Les composants actifs suivent désormais l'architecture modulaire du framework. --- -# Ajout d'un nouveau composant +# Legacy -Avant de créer un nouveau composant : +Les composants historiques peuvent être conservés dans : -- vérifier qu'un composant existant ne répond pas déjà au besoin ; -- identifier clairement sa responsabilité ; -- choisir la couche appropriée ; -- vérifier ses dépendances ; -- prévoir les tests nécessaires ; -- documenter l'évolution lorsque cela est nécessaire. +```text +Tools\ +Tests\Legacy\ +``` -Un composant ne doit pas cumuler plusieurs responsabilités indépendantes. +Ils ne sont pas chargés par : ---- +```text +Modules\PimsOS.psm1 +``` -# Bonnes pratiques +et ne participent pas au fonctionnement normal du Builder. -Les composants internes doivent : +Aucune nouvelle fonctionnalité ne doit être développée dans ces emplacements. -- respecter une responsabilité unique ; -- utiliser le BuildContext pour les données partagées du Build ; -- utiliser le Logger officiel ; -- propager correctement les erreurs ; -- éviter les dépendances inutiles ; -- respecter les dépendances descendantes ; -- rester testables ; -- ne pas devenir automatiquement des API publiques. +--- -Les composants techniques ne doivent pas contenir la logique métier des Tweaks ou des profils. +# Point d'entrée ---- +Le point d'entrée du framework est : -# API publique +```text +Modules\PimsOS.psm1 +``` -Le module PimsOS expose volontairement une API publique minimale. +L'API publique est centralisée dans ce module. -La fonction actuellement exportée est : +La fonction publique principale actuellement définie est : ```powershell Initialize-PimsOS ``` -Les composants internes ne doivent pas être considérés comme des API publiques simplement parce qu'ils sont chargés dans `PimsOS.psm1`. - -Toute nouvelle fonction publique doit être explicitement exportée et documentée. - ---- - -# Tests - -Les nouveaux composants importants doivent disposer de tests Pester adaptés. - -Les tests doivent couvrir, lorsque cela est pertinent : - -- le fonctionnement nominal ; -- les paramètres invalides ; -- les erreurs attendues ; -- les changements d'état ; -- les statistiques ; -- les interactions avec les dépendances. - -Les Engines et Managers actuellement stabilisés disposent de tests unitaires dédiés. - --- -# Évolution +# Principe général -L'architecture des composants est conçue pour permettre une évolution progressive du Builder. - -L'ajout d'un nouveau type d'Action doit rester localisé autant que possible : +L'organisation du projet doit préserver la séparation entre : ```text -Nouveau type d'Action - │ - ├── Engine - ├── Manager / Provider si nécessaire - ├── ActionRegistry - └── Tests +Configuration + ↓ +Framework + ↓ +Build + ↓ +Runtime PostInstall + ↓ +Windows installé ``` -L'évolution d'un composant ne doit pas entraîner de modification inutile du cœur du framework. +Le Build prépare l'image. -Toute évolution architecturale importante doit être documentée dans `Architecture.md` et, lorsque nécessaire, dans une nouvelle ADR. +Le runtime PostInstall s'exécute ensuite dans Windows installé. + +Cette séparation permet de maintenir une architecture claire, testable et évolutive. --- # Références -- `API.md` +Consulter également : + - `Architecture.md` - `ArchitectureRules.md` - `BuildContext.md` -- `ProjectStructure.md` +- `DeveloperGuide.md` +- `ModuleGuide.md` +- `PostInstall.md` - `Testing.md` -- `TechnicalDecisions.md` -- `Documentation\ADR\` - -## PostInstall - -Le dossier `Modules/PostInstall/` contient : - -| Fichier | Rôle | -|---|---| -| `State.ps1` | état persistant du PostInstall | -| `Network.ps1` | détection et attente réseau | -| `PostInstall.ps1` | moteur d'exécution | -| `Bootstrap.ps1` | point d'entrée FirstBoot | -| `FirstBoot.ps1` | construction des commandes FirstLogon | -| `Unattend.ps1` | génération de `unattend.xml` | -| `Installer.ps1` | installation du runtime dans le WIM | -| `UI.ps1` | interface console du premier démarrage et attente réseau | - -Les tests sont regroupés dans : - -`Tests/Unit/Modules/PostInstall/` +- `Legacy.md` diff --git a/Documentation/PostInstall.md b/Documentation/PostInstall.md index a87d89c..a30bdcf 100644 --- a/Documentation/PostInstall.md +++ b/Documentation/PostInstall.md @@ -1,18 +1,18 @@ -# État de validation au 31/08/2026 +# État de validation au 02/09/2026 -Une validation réelle du cycle FirstBoot/PostInstall a déjà été réalisée dans Hyper-V avec une ISO PimsOS. L’installation a fonctionné, RunOnce a été présent puis consommé, et `C:\ProgramData\PimsOS\PostInstall\state.json` a été créé avec un état `Completed`. +Le cycle PostInstall/Chocolatey a été validé en VM sur une ISO PimsOS 3.0.0 générée le 02/09/2026. Le réseau et le contrôle des pilotes fonctionnent, Chocolatey est installé localement depuis le cache embarqué et les packages Online peuvent être installés. -`CompletedTasks` contenait une seule entrée `Local`. Une protection contre une seconde exécution lorsque l’état est déjà `Completed` a été ajoutée. +La politique `FailurePolicy=Continue` a également été validée en conditions réelles : l'échec de Google Chrome sur son checksum est signalé sans bloquer le PostInstall et le package suivant est exécuté. Aucun `--ignore-checksums` n'est utilisé. -Une correction concernant la disponibilité de `Write-Log` dans l’environnement PostInstall installé a ensuite été apportée au source. **Cette correction doit encore être validée dans l’ISO générée le 31/08/2026.** +Le Microsoft Store est également validé en VM : il s'ouvre normalement, iCloud peut être installé depuis le Store et les Widgets peuvent être ouverts, installés et utilisés. -La prochaine validation réelle doit donc reconstruire une ISO depuis le code actuel, installer cette ISO dans Hyper-V, puis vérifier Bootstrap, `Write-Log`, `state.json`, RunOnce et l’absence de double exécution. +La finalisation après Bootstrap est maintenant implémentée dans `Finalize.ps1`. Elle vérifie l'état final avant de programmer un nettoyage différé. La validation VM de cette nouvelle étape est maintenant réussie : les scripts temporaires et `C:\Windows\Panther\unattend.xml` sont supprimés après la fin du Bootstrap, tandis que `state.json`, `PostInstall.log` et le cache Chocolatey sont conservés. --- # PostInstall PimsOS -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 ## Objectif @@ -50,6 +50,7 @@ Network.ps1 UI.ps1 PostInstall.ps1 State.ps1 +Finalize.ps1 ``` Le mécanisme FirstBoot génère : @@ -291,6 +292,7 @@ State.ps1 Network.ps1 UI.ps1 PostInstall.ps1 +Finalize.ps1 ``` Le mécanisme FirstBoot s'appuie sur ce Bootstrap pour démarrer le @@ -521,9 +523,6 @@ La préparation du runtime dans le WIM, la génération de l'interface réseau du premier démarrage, la préparation FirstBoot et l'intégration au BuildPipeline sont couvertes par les tests actuels. -La prochaine validation fonctionnelle importante est l'exécution réelle -de `FirstLogonCommands` lors de la première connexion Windows, ainsi que -la validation du comportement complet lorsque le réseau devient -disponible après le démarrage. +La chaîne `FirstLogonCommands → Bootstrap → Network → DriverCheck → Chocolatey → Finalize` est validée en VM. La reprise après perte puis disponibilité du réseau reste un scénario de validation dédié à compléter, distinct de la validation d’un démarrage avec réseau disponible. --- diff --git a/Documentation/PostInstallFinalization-2026-09-02.md b/Documentation/PostInstallFinalization-2026-09-02.md new file mode 100644 index 0000000..f5228c1 --- /dev/null +++ b/Documentation/PostInstallFinalization-2026-09-02.md @@ -0,0 +1,101 @@ +# Finalisation PostInstall — 2026-09-02 + +## Objectif + +Le cycle PostInstall dispose désormais d'une étape de vérification finale suivie d'un nettoyage différé des artefacts de démarrage. + +La finalisation intervient **après le retour de `Invoke-PostInstall` dans `Bootstrap.ps1`**. Elle ne remplace pas les contrôles de chaque phase. + +## Vérification finale + +`Test-PimsOSPostInstallCompletion` vérifie : + +- `Status = Completed` ; +- `Completed = true` ; +- `Failed = false` ; +- aucune phase courante résiduelle ; +- présence des tâches obligatoires : + - `Initialize` ; + - `Network` ; + - `DriverCheck` ; + - `Chocolatey` ; + - `Applications` ; + - `MicrosoftStore` ; + - `Configuration` ; + - `Cleanup`. + +Le résultat est enregistré dans `state.json` sous `Verification`. + +## Nettoyage différé + +Le nettoyage est lancé dans un processus PowerShell séparé avec un délai par défaut de 10 secondes. Cette séparation est volontaire : elle permet de supprimer `Bootstrap.ps1` après la fin du processus qui l'exécute. + +Les artefacts de lancement supprimés sont : + +- `Bootstrap.ps1` ; +- `Finalize.ps1` ; +- `Logger.ps1` ; +- `Network.ps1` ; +- `UI.ps1` ; +- `DriverCheck.ps1` ; +- `Chocolatey.ps1` ; +- `PostInstall.ps1` ; +- `State.ps1` ; +- `C:\Windows\Panther\unattend.xml` lorsqu'il existe encore. + +### Ressources conservées + +Le nettoyage ne supprime volontairement pas : + +- `state.json` ; +- `PostInstall.log` ; +- le dossier `Chocolatey`, notamment son cache. + +Cette décision conserve les éléments utiles au diagnostic et aux installations Chocolatey déjà effectuées. + +## Échec du nettoyage + +La vérification finale est bloquante. En revanche, l'impossibilité de programmer le nettoyage ne transforme pas un PostInstall déjà terminé en échec. + +Dans ce cas, `state.json` conserve : + +- `Cleanup.Status = Failed` ; +- l'erreur rencontrée ; +- les ressources conservées. + +Le Bootstrap journalise alors un avertissement. + +## Séquence + +```text +FirstLogon + ↓ +Bootstrap.ps1 + ↓ +Invoke-PostInstall + ↓ +Status = Completed + ↓ +Vérification finale + ↓ +state.json sauvegardé + ↓ +processus de nettoyage différé + ↓ +Bootstrap / scripts runtime supprimés + ↓ +state.json + PostInstall.log + cache Chocolatey conservés +``` + +## Validation automatisée et VM — 02/09/2026 + +Les tests automatisés couvrent : + +1. état complet accepté ; +2. tâche obligatoire manquante détectée ; +3. état non terminé refusé ; +4. lancement du processus de nettoyage ; +5. conservation de l'état, du journal et du cache ; +6. chargement de `Finalize.ps1` par le Bootstrap. + +La validation réelle en VM est également réussie sur l'ISO `PimsOS_3.0.0_20260902_141928.iso`. Le `state.json` final indique `Status=Completed`, `Verification.Verified=true`, `Cleanup.Scheduled=true` et aucune erreur. Les scripts runtime et `C:\Windows\Panther\unattend.xml` ont effectivement disparu après le délai de nettoyage. diff --git a/Documentation/Prerequisites.md b/Documentation/Prerequisites.md index 146e4a1..4488d44 100644 --- a/Documentation/Prerequisites.md +++ b/Documentation/Prerequisites.md @@ -261,7 +261,7 @@ Show-PostInstallNetworkHelp Wait-PostInstallNetworkUI ``` -La validation réelle de la reprise après perte puis disponibilité du réseau reste à effectuer dans un environnement Windows réel. +La reprise après perte puis disponibilité du réseau reste un scénario de validation dédié à réaliser. Le démarrage avec réseau disponible est déjà validé en VM. --- diff --git a/Documentation/ProjectStatus.md b/Documentation/ProjectStatus.md index 70f8e2b..5630d70 100644 --- a/Documentation/ProjectStatus.md +++ b/Documentation/ProjectStatus.md @@ -4,10 +4,13 @@ > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- +> Les références antérieures au 02/09/2026 sont historiques ; l’état courant est celui du 02/09/2026. + + # Informations générales ## Projet @@ -40,80 +43,144 @@ L'objectif final est de produire automatiquement une image Windows personnalisé --- -# État global +## Référence actuelle — 02/09/2026 -| Domaine | État | -| --------------------- | --------------------------------------------------------------- | -| Architecture | ✅ Stabilisée | -| Module PimsOS unique | ✅ Implémenté | -| BuildContext | ✅ Implémenté | -| BuildState | ✅ Implémenté | -| Logger | ✅ Implémenté | -| Validation | ✅ Implémentée | -| Recovery | 🟡 Implémenté, couverture à compléter | -| Workflow | ✅ Implémenté et testé | -| Wizard | ✅ Implémenté et testé | -| Pipeline | ✅ Implémenté et testé | -| Configuration | ✅ Implémentée et testée | -| Catégories | ✅ Implémentées et testées | -| Tweaks | ✅ Implémentés et testés | -| Profils | ✅ Implémentés et testés | -| Drivers | ✅ Implémentés et testés | -| ActionRegistry | ✅ Implémenté et testé | -| ActionEngine | ✅ Implémenté et testé | -| Engines spécialisés | ✅ Implémentés et testés | -| Managers | ✅ Implémentés et testés | -| Registry | ✅ Implémenté et testé | -| Image ISO | ✅ Implémentée | -| Image WIM | ✅ Implémentée | -| DISM | ✅ Implémenté | -| PostInstall | 🟡 Implémenté et testé, validation FirstBoot réelle à compléter | -| FirstBoot | 🟡 Préparé et testé, validation réelle à compléter | -| Reporting | 🟡 Implémenté, à enrichir | -| Security | 🟡 Implémenté, couverture à compléter | -| Converters | ⬜ Non implémenté | -| Chocolatey | ⬜ Non implémenté | -| Winget | ⬜ Non implémenté | -| Microsoft Store | ⬜ Non intégré | -| Génération ISO | ✅ Build réel réussi ; validation de l’artefact restante | -| Tests Pester | 🟡 971 Passed / 0 Failed / 1 Skipped (dernier résultat communiqué) | -| Documentation | 🟢 Synchronisée au 31/08/2026 | +Le Build réel complet et sa validation fonctionnelle sont maintenant démontrés. ---- +## Build réel validé -# Validation actuelle +- Édition : Windows 11 Professionnel, index 6 ; +- 27 Tweaks appliqués ; +- source de drivers : `CurrentSystem` ; +- export des drivers du système hôte réussi ; +- injection DISM des drivers réussie ; +- aucun résidu de montage WIM ; +- cache Chocolatey Offline préparé ; +- bootstrap `chocolatey.nupkg` présent et validé ; +- PostInstall et FirstBoot préparés dans le WIM ; +- WIM sauvegardé et synchronisé vers la source ISO ; +- SHA256 WIM : `B6BA0B8E8474761380FCC26DB165DC786162EA916B8D35192A977C49E72E9941` ; +- ISO : `Output\PimsOS_3.0.0_20260902_141928.iso` ; +- taille : 11,29 Go ; +- code retour : `0` ; +- état final du Build : `Completed`. -La campagne officielle Pester utilise les répertoires : +La taille supérieure aux ISO précédentes est cohérente avec l'intégration réelle des drivers `CurrentSystem`. + +## Diagnostic Pester sécurisé + +Dernière campagne officielle sûre : ```text -Tests -├── Unit -└── Integration +Tests analysés : 66 +Passed : 815 +Failed : 0 +Skipped : 1 +Inconclusive : 0 +NotRun : 0 +Total : 816 ``` -Les tests historiques présents dans : +Rapport : `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md` + +Le test `Skipped` est conditionnel et reste intentionnel. Il ne constitue pas un échec fonctionnel. + +## Validation VM du runtime + +La nouvelle ISO a été exécutée dans la VM `PimsOS-Test sur localhost`. La chaîne suivante est validée : ```text -Tests\Legacy +FirstLogon + ↓ +Bootstrap + ↓ +Network + ↓ +DriverCheck + ↓ +Chocolatey local + ↓ +Catalogue Chocolatey + ↓ +Applications + ↓ +Verification + ↓ +Cleanup différé ``` -sont conservés séparément et ne font pas partie de la campagne officielle. +Le `state.json` final indique `Status=Completed`, `Completed=true`, `Failed=false`, `Verification.Verified=true` et aucune tâche manquante. + +Le nettoyage a supprimé les scripts temporaires ainsi que `C:\Windows\Panther\unattend.xml`, tout en conservant `state.json`, `PostInstall.log` et le cache Chocolatey. + +## Chocolatey + +Le bootstrap Chocolatey est installé localement depuis le cache embarqué. Les packages `Online` sont exécutés après Network et DriverCheck. + +La politique `FailurePolicy=Continue` est validée en VM : `googlechrome` échoue sur un problème de checksum externe, l'échec est conservé dans l'état, puis `brave` et les packages suivants continuent normalement. Aucun `--ignore-checksums` n'est utilisé. + +Cet échec Chrome est actuellement considéré comme **non bloquant** pour PimsOS. Le package n'est pas un composant architectural obligatoire et le catalogue installe Firefox avec succès. + +## Microsoft Store / Widgets + +La base Windows conserve Microsoft Store et ses composants associés. En VM : + +- Microsoft Store s'ouvre ; +- iCloud peut être installé depuis le Store ; +- Widgets (`Win+W`) fonctionnent ; +- le catalogue de Widgets est accessible ; +- un widget Météo a été installé et utilisé avec succès. -Les derniers résultats communiqués pendant la session sont : +Aucune modification supplémentaire de l'intégration Microsoft Store n'est requise à ce stade. + +# État global + +| Domaine | État | +|---|---| +| Architecture | ✅ Stabilisée | +| Module PimsOS unique | ✅ Implémenté et export public limité à `Initialize-PimsOS` | +| BuildContext / BuildState | ✅ Implémentés | +| Workflow / Pipeline | ✅ Implémentés et testés | +| Configuration / Profils / Tweaks | ✅ Implémentés et testés | +| Drivers | ✅ Export et injection DISM validés en Build réel | +| Image WIM / ISO | ✅ Build réel validé | +| PostInstall / FirstBoot | ✅ Validés en VM | +| Finalization / Cleanup | ✅ Validés en VM | +| Chocolatey bootstrap | ✅ Validé en VM | +| Chocolatey FailurePolicy | ✅ `Continue` validé en VM | +| Microsoft Store / Widgets | ✅ Fonctionnels en VM, sans intégration PimsOS spécifique | +| Reporting | 🟡 À enrichir | +| Recovery | 🟡 Couverture à compléter | +| Security | 🟡 Couverture à compléter | +| Winget | ⬜ Non implémenté | +| Converters | ⬜ Non implémentés | +| Release produit | 🟡 À préparer | + +# Validation actuelle + +La campagne officielle couvre : ```text -971 Passed -0 Failed -1 Skipped +Tests\Unit +Tests\Integration ``` -La campagne ciblée du Wizard est à `15 Passed / 0 Failed / 0 Skipped`. La campagne PostInstall/Unattend communiquée est à `744 Passed / 0 Failed / 1 Skipped`. +`Tests\Legacy` est conservé séparément et exclu de la campagne officielle. -Le seul test ignoré signalé dans ces campagnes est conditionnel. Le fichier `Tests\testResults.xml` présent dans l’archive reste historique et doit être régénéré. +Dernier résultat de référence : -Ce `Skipped` est intentionnel et ne correspond pas à un échec fonctionnel. +```text +815 Passed +0 Failed +1 Skipped +0 Inconclusive +0 NotRun +816 Total +``` ---- +Le diagnostic sécurisé a analysé 66 fichiers Unit : 66 SAFE, 0 BUILD-CAPABLE, 0 UNKNOWN. + +Rapport : `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md`. # Architecture actuelle @@ -307,7 +374,7 @@ State.ps1 Une validation réelle permet de vérifier l'injection du runtime dans un WIM temporaire ainsi que la présence et la structure de `unattend.xml`. -La validation du lancement réel du runtime lors de la première connexion Windows reste à compléter. +Le lancement réel du runtime lors de la première connexion Windows est validé en VM, y compris la finalisation et le nettoyage différé. --- @@ -429,7 +496,7 @@ Les composants Image prennent en charge : * la gestion du cycle de vie de l'image ; * la génération de l'ISO. -Un Build réel a généré une ISO PimsOS 3.0.0 le 31/08/2026. La validation fonctionnelle de cet artefact reste à effectuer. +Un Build réel a généré une ISO PimsOS 3.0.0 le 02/09/2026. Cet artefact a été validé en VM sur le flux FirstBoot/PostInstall/Finalization. --- @@ -471,12 +538,12 @@ Les domaines suivants ne sont pas encore implémentés ou finalisés : ```text Converters -Chocolatey Winget -Microsoft Store ``` -La génération de l’ISO est démontrée par le Build réel du 31/08 ; la validation Hyper-V de l’artefact reste à effectuer. +Chocolatey est désormais fonctionnel dans le runtime PostInstall. Son audit Offline applicatif reste à poursuivre. Microsoft Store est fourni par Windows et a été validé en VM ; aucun provider PimsOS dédié n'est actuellement requis. + +La génération de l’ISO est démontrée par le Build réel du 02/09 et l’artefact a été validé en VM. --- @@ -487,7 +554,7 @@ La suite officielle Pester est exécutée avec Pester 5.8.0. Résultat de référence actuel : ```text -971 Passed +815 Passed 0 Failed 1 Skipped 0 Inconclusive @@ -528,19 +595,17 @@ La documentation couvre notamment : * le cycle de vie ; * les composants Legacy. -La documentation active a été synchronisée avec l’implémentation et l’état du Build au 31/08/2026. +La documentation active a été resynchronisée avec l’implémentation, le Build et la validation VM au 02/09/2026. Les références antérieures restent historiques. --- # Prochaine étape -Les prochaines étapes prioritaires sont : +La prochaine étape n'est plus de corriger le pipeline de base. Elle consiste à **figer l'état 3.0.0, synchroniser Git et préparer la suite du développement**. -1. valider l’ISO générée le 31/08/2026 ; -2. compléter la validation réelle du cycle FirstBoot ; -3. valider la reprise réelle du PostInstall après perte puis disponibilité du réseau ; -4. compléter la couverture Recovery et Security ; -5. poursuivre l'implémentation des fonctionnalités de gestion des packages ; -6. enrichir le reporting ; -7. maintenir la documentation synchronisée avec l'implémentation ; -8. préparer une première release fonctionnelle du framework. +1. Mettre à jour et relire la documentation synchronisée au 02/09/2026. +2. Vérifier l'état Git local, les fichiers modifiés et les éventuels artefacts à exclure. +3. Régénérer `Tests\testResults.xml` si une preuve XML officielle est souhaitée. +4. Créer le commit de synchronisation documentaire et technique. +5. Vérifier le push vers `origin/main`. +6. Ensuite seulement, reprendre le backlog : Winget, couverture Recovery/Security, reporting, audit Offline Chocolatey et validation physique/Rufus. diff --git a/Documentation/ProjectStructure.md b/Documentation/ProjectStructure.md index 366c3c3..c171dba 100644 --- a/Documentation/ProjectStructure.md +++ b/Documentation/ProjectStructure.md @@ -4,7 +4,7 @@ > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- @@ -37,6 +37,10 @@ PimsOS ├── Packages ├── Tests ├── Workspace +├── Drivers +├── Profiles +├── Resources +├── Tweaks ├── version.json └── README.md ``` diff --git a/Documentation/ReleaseNotes.md b/Documentation/ReleaseNotes.md index 5035f1f..4329c74 100644 --- a/Documentation/ReleaseNotes.md +++ b/Documentation/ReleaseNotes.md @@ -1,428 +1,59 @@ -# Mise à jour de validation — 31/08/2026 +# PimsOS Builder — Notes de version -Depuis la rédaction initiale des notes 3.0.0, une validation réelle Hyper-V du cycle PostInstall/FirstBoot a été obtenue. Une correction de disponibilité de `Write-Log` dans le runtime installé a ensuite été apportée et doit encore être validée dans une nouvelle ISO. +> Version technique : 3.0.0 +> +> Dernière mise à jour : 2026-09-02 -Le dépôt courant contient également un catalogue Tweaks partiellement rempli : 19 définitions JSON actives et 9 placeholders vides. Le raccordement `TweakCatalog` ↔ Wizard est désormais intégré au module central et le menu Tweaks est couvert par 15 tests verts. +## Validation du 02/09/2026 -Le dernier résultat global Pester communiqué pendant la session est 971 Passed / 0 Failed / 1 Skipped ; le XML présent dans le ZIP est plus ancien et doit être régénéré. +- Build réel complet validé avec Windows 11 Professionnel index 6. +- Drivers `CurrentSystem` exportés et injectés avec DISM. +- 27 Tweaks appliqués. +- WIM sauvegardé, démonté et synchronisé avec SHA256 vérifié. +- ISO générée : `Output\PimsOS_3.0.0_20260902_141928.iso`. +- Taille ISO : 11,29 Go. +- Code retour Build : 0. +- Diagnostic sécurisé : 815 Passed / 0 Failed / 1 Skipped. +- PostInstall validé en VM. +- Chocolatey bootstrap local validé. +- `FailurePolicy=Continue` validé avec poursuite après échec Chrome. +- Microsoft Store, iCloud depuis le Store et Widgets validés en VM. +- Finalization et nettoyage différé validés en VM. ---- - -# PimsOS Builder - Notes de version - -## Objectif - -Ce document présente les principales évolutions de chaque version de **PimsOS Builder**. - -Les notes de version mettent en avant les changements importants pour les utilisateurs et les développeurs. - -Contrairement au `CHANGELOG.md`, qui recense les modifications techniques détaillées, ce document présente les évolutions majeures, les améliorations, les corrections et les changements importants. - ---- - -# Format - -Chaque version documente, lorsque cela est pertinent : - -* nouveautés ; -* améliorations ; -* corrections ; -* changements incompatibles (Breaking Changes) ; -* problèmes connus. - ---- - -# Version 3.0.0 - -## État - -🚧 Développement / architecture stabilisée - -La version 3.0.0 représente l'état technique actuel du framework. - -Une ISO 3.0.0 a été générée avec succès le 31/08/2026. La version ne constitue toutefois pas encore une release finale stable tant que la nouvelle ISO n’a pas passé la validation réelle FirstBoot/PostInstall. - ---- - -## Nouveautés - -### Architecture - -* Stabilisation du modèle de module PowerShell unique. -* Centralisation du chargement des composants dans `PimsOS.psm1`. -* Centralisation de l'API publique. -* Renforcement du `BuildContext` comme contrat central. -* Mise en place et stabilisation de l'`ActionRegistry`. -* Routage centralisé des Actions par `ActionEngine`. -* Séparation explicite entre les tests actifs et les tests historiques `Legacy`. - -### Configuration - -* Stabilisation du chargement des catégories. -* Stabilisation du chargement des Tweaks. -* Stabilisation du chargement des profils. -* Fusion des profils et des Tweaks. -* Validation des définitions de configuration. -* Construction de la configuration finale dans le `BuildContext`. -* Intégration de la sélection du profil dans le Wizard. -* Intégration des options du Build dans le Wizard. - -### Wizard - -Le Wizard de configuration est désormais intégré au flux principal. - -Il permet notamment : - -* de sélectionner un profil ; -* de modifier les options du Build ; -* de configurer les drivers ; -* d'afficher le résumé ; -* de valider ou d'annuler la configuration. - -Les informations configurées dans le Wizard sont transmises au `BuildContext`, puis au pipeline. - -### Drivers - -Le pipeline prend désormais en charge la préparation des drivers. - -Les sources actuellement supportées sont : - -* `None` ; -* `CurrentSystem` ; -* `Folder`. - -Le Wizard permet de sélectionner la source des drivers. - -Les actions DISM correspondantes sont construites et enregistrées dans le contexte du Build. - -### PostInstall / FirstBoot - -Le sous-système PostInstall et sa préparation FirstBoot sont désormais intégrés au BuildPipeline. - -Les composants concernés comprennent : - -* `State.ps1` ; -* `Network.ps1` ; -* `PostInstall.ps1` ; -* `Bootstrap.ps1` ; -* `FirstBoot.ps1` ; -* `Unattend.ps1` ; -* `Installer.ps1` ; -* `UI.ps1`. - -Le runtime PostInstall est installé dans : - -```text -C:\ProgramData\PimsOS\PostInstall\ -``` - -La préparation du runtime est exécutée après l'application des drivers et avant le montage de la ruche `SOFTWARE`. - -La couche UI fournit notamment : - -* l'affichage de l'état réseau ; -* l'aide utilisateur lorsque le réseau est indisponible ; -* l'attente avec affichage de l'état ; -* la reprise automatique lorsque la connexion devient disponible. - -### Tests - -Extension importante de la couverture Pester avec : - -* tests dédiés aux Engines ; -* tests dédiés aux Managers ; -* tests du système de configuration ; -* tests du module Registry ; -* tests du Wizard ; -* tests des drivers ; -* tests du pipeline ; -* tests PostInstall ; -* tests FirstBoot ; -* tests réseau ; -* tests UI PostInstall ; -* tests d'intégration de l'API publique ; -* tests d'intégration du BuildPipeline. - ---- - -## Améliorations - -### Core - -* Stabilisation du `BuildContext`. -* Stabilisation du `BuildState`. -* Stabilisation du Workflow. -* Stabilisation du Pipeline. -* Amélioration de la finalisation du Build. -* Meilleure propagation des informations entre les différentes phases. - -### Actions - -* Routage centralisé via `ActionRegistry`. -* Séparation plus stricte entre Engines et Managers. -* Gestion homogène des états `Success`, `Duration` et `Error`. -* Amélioration de la gestion des erreurs des Actions. - -### Managers - -* Normalisation des mécanismes de sélection des providers. -* Validation systématique des paramètres. -* Amélioration de la gestion des handlers. -* Correction de plusieurs problèmes liés aux dictionnaires ordonnés PowerShell. -* Validation du comportement lorsqu'un handler inexistant est demandé. - -### Configuration - -* Meilleure propagation de l'état dans le `BuildContext`. -* Mise à jour des indicateurs de chargement de la configuration. -* Validation renforcée des définitions. -* Intégration du profil sélectionné dans le contexte. -* Tests de régression ajoutés. - -### PostInstall - -* Détection du réseau avec `Get-NetConnectionProfile`. -* Repli vers `Get-NetAdapter` lorsque nécessaire. -* Vérification distincte de la connectivité Internet. -* Attente configurable de la disponibilité réseau. -* Interface console dédiée au premier démarrage. -* Affichage de l'état du réseau et de l'accès Internet. -* Aide utilisateur lorsque le réseau est indisponible. -* Reprise automatique après disponibilité du réseau. -* Préparation du runtime dans le WIM. -* Génération de `unattend.xml`. -* Préparation des commandes `FirstLogonCommands`. -* Intégration de `PreparePostInstall` dans le pipeline. - -### Tests et qualité - -La campagne officielle utilise désormais exclusivement : - -```text -Tests\Unit -Tests\Integration -``` - -Les tests historiques sont conservés dans : - -```text -Tests\Legacy -``` - -et ne font pas partie de la campagne officielle. - -Les résultats communiqués à la fin de la session sont : +### Artefacts de référence -```text -971 Passed -0 Failed -1 Skipped -``` +- WIM SHA256 : `B6BA0B8E8474761380FCC26DB165DC786162EA916B8D35192A977C49E72E9941` +- Diagnostic : `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md` +- État VM : `state.json` avec `Completed=true`, `Failed=false`, `Verification.Verified=true`. -La campagne ciblée `Wizard.Tests.ps1` est à `15 Passed / 0 Failed / 0 Skipped`. La campagne PostInstall/Unattend communiquée est à `744 Passed / 0 Failed / 1 Skipped`. Le fichier `Tests\testResults.xml` présent dans l’archive reste plus ancien et doit être régénéré. +### Anomalie connue non bloquante ---- - -## Corrections - -Les travaux de stabilisation de la version 3.0.0 ont notamment permis de corriger : - -* la propagation incorrecte du `BuildContext` ; -* la mise à jour de l'état de chargement de la configuration ; -* la propagation du profil sélectionné ; -* la propagation des options du Build ; -* la propagation de la configuration des drivers ; -* l'ordre des étapes du BuildPipeline ; -* la préparation PostInstall dans le pipeline ; -* la gestion des sources de drivers ; -* la construction des actions DISM pour les drivers ; -* plusieurs problèmes de détection des providers ; -* l'utilisation incorrecte de `ContainsKey()` avec des dictionnaires ordonnés ; -* la propagation des erreurs dans les Engines ; -* plusieurs incohérences de gestion des statistiques ; -* des incohérences entre les contrats des Managers et leurs tests ; -* le comportement du test réseau expirant qui attendait réellement une minute avant de terminer ; -* la gestion d'un handler inexistant dans `CommandManager` ; -* l'intégration de la couche UI PostInstall avec la logique réseau existante. - ---- - -## Breaking Changes - -La version 3.0.0 poursuit et stabilise les changements introduits par l'architecture du module unique. - -Principes importants : - -* `PimsOS.psm1` constitue le module central ; -* `Initialize-PimsOS` constitue le point d'entrée public fonctionnel ; -* les composants internes ne sont pas des modules PowerShell indépendants ; -* les fonctions internes ne constituent pas automatiquement une API publique ; -* le `BuildContext` constitue le contrat central entre les composants ; -* le Wizard transmet sa configuration au contexte puis au pipeline ; -* les tests `Legacy` sont séparés de la campagne officielle. - -Les anciennes architectures basées sur plusieurs modules indépendants ne constituent plus le modèle de référence. - ---- - -## Problèmes connus - -Les éléments suivants restent en développement : - -* finalisation complète de la génération de l'ISO ; -* validation complète d'un Build de bout en bout ; -* validation réelle de l'exécution de `FirstLogonCommands` lors de la première connexion Windows ; -* validation complète de la reprise après perte puis disponibilité du réseau ; -* implémentation du provider Chocolatey ; -* implémentation du provider Winget ; -* intégration Microsoft Store ; -* implémentation de `Converters.ps1` ; -* couverture complémentaire de `Recovery.ps1` ; -* couverture complémentaire de `Security.ps1` ; -* enrichissement du Reporting. - -La version 3.0.0 ne doit donc pas encore être considérée comme une release stable finale. - ---- - -# Historique - -## Version 0.3.0-dev - -### État - -🚧 Historique - -Cette version correspond à une étape précédente du développement du Builder. - -### Principales évolutions - -* migration vers un module PowerShell unique ; -* introduction de `Initialize-PimsOS` ; -* restructuration du Pipeline ; -* introduction du mécanisme Recovery ; -* gestion des images WIM ; -* gestion des images ISO ; -* gestion des ruches du registre ; -* chargement de la configuration ; -* chargement des profils ; -* fusion des profils et des Tweaks ; -* validation de la configuration ; -* sélection interactive de l'image Windows ; -* introduction de `Test-WimMountState()` ; -* premiers tests Pester ; -* centralisation des informations du projet dans `version.json`. - -### Corrections historiques - -* correction de la gestion des montages DISM invalides ; -* amélioration de la logique de reprise ; -* amélioration de la copie du WIM ; -* amélioration de la gestion des erreurs du Pipeline. - ---- - -# Versions futures - -Les prochaines versions seront documentées selon le modèle suivant. - ---- - -# Version X.Y.Z - -## État - -* En développement -* Publiée -* Maintenance - ---- - -## Nouveautés - -... - ---- - -## Améliorations - -... - ---- - -## Corrections - -... - ---- - -## Breaking Changes - -... +`googlechrome` peut échouer sur un checksum lorsque l’installateur servi par Google évolue avant la mise à jour du package Chocolatey. `FailurePolicy=Continue` permet de poursuivre. Aucun contournement de checksum n’est utilisé. --- -## Problèmes connus - -... - ---- +# Mise à jour de validation — 01/09/2026 -# Politique de version +## Build réel -Le projet suit le principe du **Semantic Versioning (SemVer)**. +- Build complet PimsOS 3.0.0 validé avec code retour 0. +- Windows 11 Professionnel, index 6. +- 27 Tweaks appliqués. +- PostInstall préparé et validé. +- WIM synchronisé vers la source ISO avec SHA256 vérifié. +- ISO `Output\PimsOS_3.0.0_20260901_180342.iso` créée, 7,9 Go annoncés. -Format : +## Diagnostic sécurisé -```text -MAJOR.MINOR.PATCH -``` +- Ajout de `Tests\Tools\Invoke-PimsOSDiagnostics.ps1`. +- Séparation `SAFE` / `BUILD-CAPABLE` / `UNKNOWN`. +- Validation Build séparée par `-BuildValidation -AllowBuild`. +- Inventaire sans exécution avec `-InventoryOnly`. -* **MAJOR** : changements incompatibles. -* **MINOR** : nouvelles fonctionnalités compatibles. -* **PATCH** : corrections de bugs. +## CI -Exemple : - -```text -3.0.1 -``` +- Ajout d'une concurrence par branche/ref dans PimsOS CI. +- Annulation des runs obsolètes lors des synchronisations rapides. +- Déclenchement limité aux chemins techniques pertinents afin que les changements purement documentaires ne déclenchent pas inutilement Pester. --- - -# Références - -Consulter également : - -* `CHANGELOG.md` -* `Milestones.md` -* `Roadmap.md` -* `ProjectStatus.md` -* `Testing.md` -* `PostInstall.md` -* `Legacy.md` - - -## 3.0.0 — Catalogue Tweaks enrichi — 2026-09-01 - -### Ajouts - -- affichage des secondes dans l'horloge ; -- désactivation des surbrillances de recherche ; -- contrôle des recommandations du menu Démarrer ; -- désactivation des expériences personnalisées basées sur les diagnostics ; -- contrôle des suggestions tierces de Windows Spotlight ; -- contrôle de l'enregistrement des instantanés Recall ; -- désactivation complète de Recall. - -### Corrections documentaires - -- clarification de la différence entre fichiers cachés et fichiers système - protégés ; -- précision du comportement réel du Tweak Xbox/Game DVR ; -- documentation du choix `DEFAULT` vs `SOFTWARE`. - -### Tests - -Ajout de tests de contrat pour les nouveaux fichiers JSON et leurs Actions -registre. diff --git a/Documentation/Roadmap.md b/Documentation/Roadmap.md index 6459ed8..64e0f9a 100644 --- a/Documentation/Roadmap.md +++ b/Documentation/Roadmap.md @@ -4,10 +4,13 @@ > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- +> Les références antérieures au 02/09/2026 sont historiques ; l’état courant est celui du 02/09/2026. + + # Objectif Cette feuille de route présente les grandes orientations du projet **PimsOS Builder**. @@ -97,7 +100,7 @@ Les principales fondations du framework sont maintenant en place : * reporting ; * nettoyage et finalisation du Build. -Le développement se concentre désormais sur la finalisation de la chaîne de production et sur la validation complète du Build de bout en bout. +Le Build réel de bout en bout et la validation VM de FirstBoot/PostInstall sont désormais démontrés. Le développement se concentre sur la synchronisation Git, la validation physique/Rufus, l’audit Offline des packages et les fonctions encore au backlog. --- @@ -253,7 +256,7 @@ Le moteur d'orchestration est suffisamment structuré et testé pour poursuivre * [ ] Valider l'exécution réelle de `FirstLogonCommands`. * [ ] Valider le premier démarrage réel de Windows. * [ ] Valider la reprise réseau réelle. -* [ ] Intégrer Chocolatey. +* [x] Intégrer le provider Chocolatey. * [ ] Intégrer Winget. * [ ] Intégrer Microsoft Store. @@ -263,7 +266,7 @@ Le moteur d'orchestration est suffisamment structuré et testé pour poursuivre Le sous-système PostInstall est fonctionnel au niveau de la préparation et de l'intégration au Build. -La validation du comportement réel lors de la première connexion Windows reste à effectuer. +La validation réelle de `FirstLogonCommands`, Bootstrap, PostInstall et Finalization est maintenant effectuée en VM. --- @@ -296,7 +299,7 @@ La validation du comportement réel lors de la première connexion Windows reste La dernière campagne officielle de tests donne : ```text id="fny8oe" -971 Passed +815 Passed / 0 Failed / 1 Skipped (reference — 02/09/2026) 0 Failed 1 Skipped 0 Inconclusive diff --git a/Documentation/Schema.md b/Documentation/Schema.md index 35aa561..358356c 100644 --- a/Documentation/Schema.md +++ b/Documentation/Schema.md @@ -1,600 +1,139 @@ -# PimsOS Builder - Feuille de route +# PimsOS Builder — Schémas de configuration > Version technique : 3.0.0 > -> Statut : Développement / architecture stabilisée -> -> Dernière mise à jour : 2026-08-31 - ---- - -# Objectif - -Cette feuille de route présente les grandes orientations du projet **PimsOS Builder**. - -Elle décrit les évolutions prévues pour le framework, le moteur de Build et les fonctionnalités permettant de construire des images Windows personnalisées. - -Elle présente les objectifs à moyen et long terme sans remplacer le backlog technique détaillé. - -Les évolutions importantes de l'architecture sont documentées dans les **Architecture Decision Records (ADR)**. - ---- - -# Vision - -PimsOS Builder a pour objectif de devenir un framework capable de construire automatiquement des images Windows personnalisées à partir d'images compatibles. - -Le moteur doit rester indépendant d'une version spécifique de Windows et pouvoir évoluer avec les versions compatibles avec les mécanismes de déploiement utilisés. - -Le projet repose notamment sur les principes suivants : - -* modularité ; -* automatisation ; -* reproductibilité ; -* maintenabilité ; -* testabilité ; -* séparation claire des responsabilités. +> Dernière mise à jour : 2026-09-02 -À terme, la création d'une image PimsOS complète doit pouvoir être réalisée à partir d'un processus de Build automatisé et reproductible. +Ce document décrit les structures de données réellement utilisées par PimsOS Builder. Les fichiers JSON restent la source de vérité opérationnelle. ---- +## `Config/config.json` -# État actuel +### Logging -## Architecture - -✅ **Stabilisée** - -L'architecture 3.0.0 repose notamment sur : - -* un module PowerShell unique ; -* un BuildContext centralisé ; -* un BuildState ; -* un Workflow ; -* un Pipeline ; -* un ActionRegistry ; -* un ActionEngine ; -* des Engines spécialisés ; -* des Managers spécialisés ; -* des composants techniques organisés par domaine ; -* une configuration pilotée par les données ; -* une API publique centralisée ; -* une couverture Pester importante. - -Le point d'entrée public principal est : - -```text id="v6d0zp" -Initialize-PimsOS +```json +{ + "Logging": { + "KeepLogs": 20, + "Level": "INFO", + "Encoding": "UTF8" + } +} ``` ---- - -## Développement - -🚧 **En cours** +### Paths / Workspace -Les principales fondations du framework sont maintenant en place : +Les chemins de projet sont relatifs à la racine PimsOS. Le Workspace contient les copies de travail et les caches Build. -* Recovery ; -* vérification de l'environnement ; -* vérification des prérequis ; -* gestion des ISO ; -* gestion des WIM ; -* sélection des images Windows ; -* gestion des ruches du registre ; -* chargement des catégories ; -* chargement des Tweaks ; -* chargement des profils ; -* fusion de la configuration ; -* validation ; -* routage des Actions ; -* Engines spécialisés ; -* Managers spécialisés ; -* Wizard ; -* configuration des drivers ; -* préparation PostInstall ; -* préparation FirstBoot ; -* reporting ; -* nettoyage et finalisation du Build. +Principaux chemins : `Workspace`, `Workspace\ISO`, `Workspace\ISOSource`, `Workspace\Sources`, `Workspace\Mount\WIM`, `Workspace\Drivers`, `Workspace\Packages`, `Workspace\Packages\Chocolatey`. -Le développement se concentre désormais sur la finalisation de la chaîne de production et sur la validation complète du Build de bout en bout. +### Requirements ---- +- `MinimumFreeSpaceGB` : espace disque minimal. +- `PowerShellMajor` : version minimale du moteur Build. +- `WindowsADK` : présence, version, chemin, fonctionnalité et paramètres de téléchargement de l’ADK. -# Phases du projet +Le Build actuel attend PowerShell 7 côté environnement de construction. Le runtime PostInstall est explicitement compatible PowerShell 5.1+. -## Phase 1 — Fondations +### Image -### Objectifs - -* [x] Définir l'architecture générale. -* [x] Mettre en place la documentation. -* [x] Définir les conventions de développement. -* [x] Mettre en place les ADR. -* [x] Construire les premiers composants techniques. -* [x] Définir le BuildContext. -* [x] Définir le BuildState. - -### Statut - -✅ **Terminée** - ---- - -## Phase 2 — Module PowerShell unique - -### Objectifs - -* [x] Créer `PimsOS.psm1`. -* [x] Créer `PimsOS.psd1`. -* [x] Centraliser le chargement des composants. -* [x] Centraliser l'API publique. -* [x] Introduire `Initialize-PimsOS`. -* [x] Supprimer le modèle à plusieurs modules indépendants. -* [x] Valider le module PowerShell unique. -* [x] Valider l'exposition de l'API publique. - -### Statut - -✅ **Terminée** - ---- - -## Phase 3 — Framework de Build - -### Objectifs - -* [x] Finaliser le BuildContext. -* [x] Développer le BuildState. -* [x] Développer le Pipeline. -* [x] Développer le Workflow. -* [x] Mettre en place Recovery. -* [x] Vérifier les prérequis de l'environnement. -* [x] Gérer les images WIM. -* [x] Gérer les ISO. -* [x] Détecter les images Windows. -* [x] Permettre la sélection de l'image à personnaliser. -* [x] Gérer les ruches du registre. -* [x] Charger les définitions de Tweaks. -* [x] Charger les profils. -* [x] Fusionner profils et Tweaks. -* [x] Valider la configuration. -* [x] Mettre en place ActionRegistry. -* [x] Mettre en place ActionEngine. -* [x] Développer les Engines spécialisés. -* [x] Développer les Managers spécialisés. -* [x] Intégrer le Wizard. -* [x] Intégrer la configuration des drivers. -* [x] Intégrer la préparation PostInstall au pipeline. - -### Statut - -✅ **Stabilisée** - -Le moteur d'orchestration est suffisamment structuré et testé pour poursuivre la finalisation de la production d'image. - ---- - -## Phase 4 — Génération d'images Windows - -### Objectifs - -* [x] Préparer les images ISO. -* [x] Manipuler les images WIM. -* [x] Effectuer les opérations DISM nécessaires. -* [x] Préparer les drivers dans le pipeline. -* [x] Préparer le runtime PostInstall dans le WIM. -* [x] Générer `unattend.xml`. -* [ ] Finaliser la génération automatique de l'ISO. -* [ ] Valider automatiquement l'ISO générée. -* [ ] Valider un Build complet de bout en bout. -* [ ] Améliorer la gestion des erreurs de production. -* [ ] Optimiser les performances. -* [ ] Valider l'artefact ISO final. - -### Statut - -🟡 **En cours** - ---- - -## Phase 5 — Personnalisation - -### Objectifs - -* [x] Profils. -* [x] Tweaks. -* [x] Catégories. -* [x] RegistryEngine. -* [x] ServiceEngine. -* [x] FeatureEngine. -* [x] CapabilityEngine. -* [x] PackageEngine. -* [x] DriverEngine. -* [x] FileEngine. -* [x] FolderEngine. -* [x] EnvironmentEngine. -* [x] ScheduledTaskEngine. -* [x] ShortcutEngine. -* [x] PackageManager. -* [x] DriverManager. -* [x] Managers spécialisés. -* [ ] Implémenter le provider Chocolatey. -* [ ] Implémenter le provider Winget. -* [ ] Intégrer Microsoft Store. -* [ ] Compléter les fonctionnalités de personnalisation restantes. - -### Statut - -🟡 **En cours** - ---- - -## Phase 6 — PostInstall / FirstBoot - -### Objectifs - -* [x] Implémenter State. -* [x] Implémenter Network. -* [x] Implémenter le moteur PostInstall. -* [x] Implémenter Bootstrap. -* [x] Implémenter FirstBoot. -* [x] Implémenter Unattend. -* [x] Implémenter Installer. -* [x] Intégrer `PreparePostInstall` au BuildPipeline. -* [x] Valider l'injection du runtime dans un WIM temporaire. -* [x] Valider la génération de `unattend.xml`. -* [x] Valider le namespace `urn:schemas-microsoft-com:unattend`. -* [x] Valider `wcm:action="add"`. -* [x] Valider la commande vers `Bootstrap.ps1`. -* [ ] Valider l'exécution réelle de `FirstLogonCommands`. -* [ ] Valider le premier démarrage réel de Windows. -* [ ] Valider la reprise réseau réelle. -* [ ] Intégrer Chocolatey. -* [ ] Intégrer Winget. -* [ ] Intégrer Microsoft Store. - -### Statut - -🟡 **Implémenté et testé — validation réelle FirstBoot restante** - -Le sous-système PostInstall est fonctionnel au niveau de la préparation et de l'intégration au Build. - -La validation du comportement réel lors de la première connexion Windows reste à effectuer. - ---- - -## Phase 7 — Stabilisation et qualité - -### Objectifs - -* [x] Mettre en place Pester 5.x. -* [x] Mettre en place une couverture importante des composants. -* [x] Tester les Engines spécialisés. -* [x] Tester les Managers. -* [x] Tester Configuration. -* [x] Tester Registry. -* [x] Tester Workflow et composants Core. -* [x] Tester Wizard. -* [x] Tester les drivers. -* [x] Tester PostInstall. -* [x] Tester FirstBoot. -* [x] Tester Network. -* [x] Tester l'intégration du BuildPipeline. -* [x] Séparer les tests officiels des tests Legacy. -* [ ] Compléter les tests Recovery. -* [ ] Compléter les tests Security. -* [ ] Étendre les tests d'intégration. -* [ ] Valider les Builds complets. -* [ ] Finaliser la documentation technique. - -### Résultat actuel - -La dernière campagne officielle de tests donne : - -```text id="fny8oe" -971 Passed -0 Failed -1 Skipped -0 Inconclusive -0 NotRun +```json +{ + "Edition": null, + "Language": "fr-FR" +} ``` -Le seul test ignoré est conditionnel et concerne le cas d'une catégorie sans groupes alors que toutes les catégories actuellement définies possèdent des groupes. +`Edition=null` permet la sélection de l’image lors du Build. -Les tests historiques présents dans : +### Drivers -```text id="pydg7a" -Tests\Legacy +```json +{ + "Source": "None|CurrentSystem|Folder", + "Path": null, + "Recurse": true, + "ForceUnsigned": false +} ``` -sont conservés séparément et ne font pas partie de la campagne officielle. - -### Statut - -🟡 **En cours** - ---- +`CurrentSystem` exporte les pilotes du poste hôte avant injection DISM. `Folder` utilise le dossier indiqué et sa recherche récursive si `Recurse=true`. -## Phase 8 — Première version stable +### Build -### Objectifs - -* [ ] Pipeline validé de bout en bout. -* [ ] Génération ISO stable. -* [ ] Composants nécessaires finalisés. -* [ ] PostInstall validé sur un environnement Windows réel. -* [ ] FirstBoot validé. -* [ ] Tests validés. -* [ ] Documentation synchronisée. -* [ ] API publique stabilisée. -* [ ] Build reproductible. -* [ ] Absence d'anomalie bloquante. -* [ ] Artefact ISO final validé. -* [ ] Publication d'une première version stable. - -### Statut - -⏳ **À venir** - ---- - -# Composants restant à développer ou compléter - -Les principaux éléments identifiés sont : - -* finalisation de la génération ISO ; -* validation complète du Build de bout en bout ; -* validation réelle FirstBoot ; -* validation de la reprise réseau réelle ; -* `Converters.ps1` ; -* provider Chocolatey ; -* provider Winget ; -* intégration Microsoft Store ; -* couverture complémentaire de `Recovery.ps1` ; -* couverture complémentaire de `Security.ps1` ; -* enrichissement du Reporting ; -* validation de l'artefact ISO final. - ---- - -# Tests - -Les objectifs actuels sont : - -* maintenir la couverture des composants existants ; -* compléter les tests des composants encore partiellement couverts ; -* étendre les tests d'intégration ; -* ajouter des tests de régression ; -* automatiser progressivement l'exécution des tests ; -* conserver une séparation stricte entre les tests actifs et les tests historiques. - -Les tests Pester constituent la base de validation du framework. - -## Campagne officielle - -La campagne officielle utilise : - -```text id="7kyx7n" -Tests\Unit -Tests\Integration +```json +{ + "Id": "Development", + "CreateISO": true, + "CreateReport": true +} ``` -Les tests historiques sont conservés dans : - -```text id="j31c2d" -Tests\Legacy -``` - -Ils ne sont pas inclus dans la campagne officielle. - ---- - -# Documentation - -Les objectifs actuels sont : - -* maintenir la documentation synchronisée avec le code ; -* documenter l'API publique ; -* documenter l'architecture ; -* maintenir les règles d'architecture ; -* maintenir le statut du projet ; -* maintenir le backlog et les jalons ; -* maintenir la feuille de route ; -* documenter les décisions architecturales dans les ADR ; -* documenter le fonctionnement du PostInstall et de FirstBoot. - ---- - -# Priorités actuelles - -## Priorité 1 — Génération ISO - -Finaliser la chaîne permettant de produire une ISO PimsOS complète. +## `Config/PackageProviders.json` ---- +Les providers déclarent leur activation, handler et cache. Le moteur générique reste séparé des providers spécialisés. -## Priorité 2 — Validation de bout en bout +Providers actuellement présents : `Chocolatey`, `Winget`, `MicrosoftStore`. Chocolatey est le seul provider réellement opérationnel dans la chaîne actuelle. -Réaliser et valider un Build complet depuis l'ISO source jusqu'à -l'artefact final. +## `Config/Packages/Chocolatey.json` -Cette validation doit notamment vérifier : +Chaque entrée contient notamment : -* la préparation du WIM ; -* l'application des Tweaks ; -* l'application des drivers ; -* la préparation PostInstall ; -* la reconstruction de l'ISO ; -* la génération de l'artefact final ; -* la cohérence du résultat. - ---- - -## Priorité 3 — Validation FirstBoot - -Valider le comportement réel de : - -```text id="cprqdr" -unattend.xml - ↓ -FirstLogonCommands - ↓ -Bootstrap.ps1 - ↓ -PostInstall +```json +{ + "Id": "firefox", + "Enabled": true, + "Category": "Browser", + "Mode": "Online", + "Version": "154.0.1", + "FailurePolicy": "Stop" +} ``` -Cette validation doit être effectuée sur un environnement Windows réel. - ---- - -## Priorité 4 — Providers packages - -Implémenter les providers : - -* Chocolatey ; -* Winget ; -* Microsoft Store. - ---- - -## Priorité 5 — Couverture et stabilité - -Compléter : - -* Recovery ; -* Security ; -* Reporting ; -* tests d'intégration ; -* tests de régression ; -* validation des Builds complets. - ---- - -## Priorité 6 — Documentation et release - -Maintenir la documentation synchronisée et préparer les conditions nécessaires à une première release stable. - ---- - -# Prochain objectif technique - -Le prochain objectif technique majeur est la **finalisation de la chaîne de production de l'image PimsOS**. - -Les travaux prioritaires sont : - -1. finaliser le traitement du WIM ; -2. finaliser la reconstruction de l'ISO ; -3. valider le Build complet ; -4. vérifier les artefacts générés ; -5. valider le cycle FirstBoot réel ; -6. compléter les rapports ; -7. vérifier le nettoyage final ; -8. documenter le processus de production. - ---- - -# Hors périmètre actuel - -À ce stade, les éléments suivants ne constituent pas une priorité du développement : - -* interface graphique complète ; -* support d'autres systèmes d'exploitation ; -* déploiement distribué ; -* versions de Windows incompatibles avec les mécanismes techniques utilisés par le Builder. - -Ces éléments pourront être réévalués ultérieurement. - ---- +Champs : -# Suivi +- `Id` : identifiant Chocolatey ; +- `Enabled` : activation ; +- `Category` : catégorie fonctionnelle ; +- `Mode` : `Offline`, `Online` ou `Disabled` ; +- `Version` : version demandée ou `null` ; +- `FailurePolicy` : `Stop` ou `Continue`, avec `Stop` par défaut. -La feuille de route est revue à chaque jalon majeur. +### Règle spéciale `chocolatey` -Les fonctionnalités terminées sont reportées dans : +Le package `chocolatey` est obligatoire en `Offline` : il constitue le bootstrap local du moteur Chocolatey. Son `.nupkg` doit être présent et exploitable dans `Workspace\Packages\Chocolatey` avant la génération de l’image. -* `ReleaseNotes.md` ; -* `Milestones.md` ; -* `ProjectStatus.md`. +## `Config/ActionTemplates` -Les évolutions architecturales importantes sont documentées dans les ADR. +Les templates disponibles couvrent : ---- +`Capability`, `Command`, `Driver`, `Environment`, `Feature`, `File`, `Folder`, `Package`, `PowerShell`, `Registry`, `ScheduledTask`, `Service`, `Shortcut`. -# Documents associés - -* `Architecture.md` -* `ArchitectureRules.md` -* `ProjectStatus.md` -* `ProjectStructure.md` -* `Lifecycle.md` -* `Milestones.md` -* `ReleaseNotes.md` -* `Testing.md` -* `PostInstall.md` -* `Prerequisites.md` -* `Documentation\ADR\` - - ---- - -# PostInstall - -Le sous-système PostInstall constitue une phase d'exécution distincte du Build. - -Le Build prépare les composants nécessaires dans l'image Windows, tandis que le runtime PostInstall exécute les opérations après l'installation du système. - -Le runtime utilise notamment : - -```text -C:\ProgramData\PimsOS\PostInstall\ -``` - -avec les composants : - -```text -Bootstrap.ps1 -Network.ps1 -PostInstall.ps1 -State.ps1 -UI.ps1 -``` - -Le Build prépare également : - -```text -C:\Windows\Panther\unattend.xml -``` - -Le premier démarrage s'appuie sur `FirstLogonCommands` pour lancer le Bootstrap. +Ils décrivent les formes attendues des Actions et servent de référence documentaire pour la configuration. ## États PostInstall -Les états fonctionnels documentés sont : - -```text -Pending -Running -WaitingForNetwork -Completed -Failed -``` - -## Réseau - -La vérification réseau distingue : - -```text -Adaptateur réseau - ↓ -Connexion réseau - ↓ -Accès Internet -``` - -Un adaptateur détecté ou une connexion locale disponible ne garantit donc pas l'accès à Internet. - -L'interface PostInstall peut afficher l'état réseau et fournir une aide lorsque la connexion Internet est indisponible. - -La reprise après disponibilité du réseau fait partie du comportement prévu du runtime, mais sa validation réelle lors d'un premier démarrage Windows reste à effectuer. +Le `state.json` runtime contient notamment : + +- `Status` ; +- `Started` ; +- `Completed` ; +- `Failed` ; +- `WaitingForNetwork` ; +- `CurrentPhase` ; +- `CompletedTasks` ; +- `PendingTasks` ; +- `Errors` ; +- `Verification` ; +- `Cleanup` ; +- `ChocolateyResults` ; +- `ChocolateyFailures`. + +La validation finale exige `Status=Completed`, `Completed=true`, `Failed=false`, aucune tâche manquante et `Verification.Verified=true`. + +## Références + +- `Documentation/Architecture.md` pour l’architecture ; +- `Documentation/ChocolateyArchitecture.md` pour le provider ; +- `Documentation/PostInstall.md` pour le runtime ; +- `Documentation/Testing.md` pour les preuves de validation ; +- `Documentation/ADR/` pour les décisions normatives. diff --git a/Documentation/SessionChecklist.md b/Documentation/SessionChecklist.md new file mode 100644 index 0000000..2a45e5a --- /dev/null +++ b/Documentation/SessionChecklist.md @@ -0,0 +1,37 @@ +# PimsOS Builder — Checklist de synchronisation dépôt + +## Avant séance + +- [ ] Lire la note `DocumentationSync-*` la plus récente. +- [ ] Lire `ProjectStatus.md`. +- [ ] Lire `CurrentSprint.md`. +- [ ] Vérifier `git status`. +- [ ] Vérifier `git log --oneline -5`. +- [ ] Vérifier le dernier état GitHub si la connexion est disponible. +- [ ] Vérifier les derniers tests et les outils ajoutés. +- [ ] Lancer l'inventaire Diagnostics adapté. + +## Avant modification + +- [ ] Preuve du problème obtenue. +- [ ] Cause distinguée de l'hypothèse. +- [ ] Code réel consulté. +- [ ] Tests existants consultés. +- [ ] ADR/architecture consultés si nécessaire. + +## Avant Build réel + +- [ ] Diagnostic terminé. +- [ ] Aucun `UNKNOWN` pertinent ignoré sans justification. +- [ ] Build explicitement autorisé. +- [ ] Environnement et Workspace vérifiés. +- [ ] ISO source disponible. + +## Fin de séance + +- [ ] Tests exécutés et résultats notés. +- [ ] Documentation synchronisée. +- [ ] Changelog mis à jour si nécessaire. +- [ ] Git status compris. +- [ ] Commit cohérent prévu/realise. +- [ ] Note `DocumentationSync-*` mise à jour si l'état de référence change. diff --git a/Documentation/TechnicalDecisions.md b/Documentation/TechnicalDecisions.md index c4a0cd4..29145ce 100644 --- a/Documentation/TechnicalDecisions.md +++ b/Documentation/TechnicalDecisions.md @@ -1,670 +1,66 @@ -# PimsOS Builder - Décisions techniques +# PimsOS Builder — Décisions techniques -> Version : 3.0.0 +> Version technique : 3.0.0 > -> Statut : Référence -> -> Dernière mise à jour : 2026-08-31 - ---- - -# Objectif - -Ce document recense les principales décisions techniques prises au cours du développement de **PimsOS Builder**. - -Contrairement aux **Architecture Decision Records (ADR)**, ces décisions concernent exclusivement les choix d'implémentation, les conventions de développement et les bonnes pratiques PowerShell. - -Les décisions ayant un impact sur l'architecture générale du projet sont documentées dans le dossier **Documentation/ADR**. - ---- - -# Principes - -Les décisions documentées dans ce fichier doivent : - -- concerner uniquement l'implémentation ; -- ne pas modifier l'architecture du projet ; -- être datées ; -- expliquer le contexte ; -- justifier le choix retenu ; -- préciser les composants concernés. - -Toute décision ayant un impact sur l'architecture doit faire l'objet d'une ADR. - ---- - -# Historique - -## 2026-07-21 - -### Normalisation des collections PowerShell - -#### Contexte - -Certaines fonctions PowerShell peuvent retourner : - -- aucun objet ; -- un objet ; -- plusieurs objets. - -Lorsqu'un seul objet est retourné, la propriété `.Count` n'est plus disponible. - -#### Décision - -Toutes les collections manipulées avec `.Count` sont systématiquement encapsulées avec : - -```powershell -@(...) -``` - -Cette règle garantit un comportement identique quel que soit le nombre d'éléments retournés. - -#### Composants concernés - -- Backup - ---- - -## 2026-07-21 - -### Génération des identifiants de session - -#### Contexte - -Deux sauvegardes créées durant la même seconde pouvaient produire un identifiant identique. - -#### Décision - -Le format retenu est : - -```powershell -Get-Date -Format "yyyy-MM-dd_HH-mm-ss-fff" -``` - -L'ajout des millisecondes garantit l'unicité. - -#### Composants concernés - -- Backup - ---- - -## 2026-07-23 - -### Tests Pester sur les collections vides - -#### Contexte - -Une collection correctement initialisée mais vide provoquait des faux positifs avec : - -```powershell -$Collection | Should -Not -BeNull -``` - -#### Décision - -Les collections sont désormais validées explicitement : - -```powershell -($null -eq $Collection) | Should -BeFalse -$Collection.Count | Should -Be 0 -``` - -Cette règle est utilisée dans l'ensemble des nouveaux tests. - -#### Composants concernés - -- Migration - ---- - -## 2026-07-24 - -### Abandon des classes PowerShell - -#### Contexte - -Les premières versions utilisaient plusieurs classes PowerShell. - -Les tests ont montré qu'elles compliquaient : - -- le rechargement des modules ; -- les tests Pester ; -- le développement itératif ; -- le chargement dynamique. - -#### Décision - -Les classes métier sont remplacées par des `PSCustomObject` créés par des fonctions constructeur (`New-*`). - -Cette approche simplifie considérablement le développement et les tests. - -#### Composants concernés - -- Migration - ---- - -## 2026-07-25 - -### Contrat commun des objets métier - -#### Contexte - -L'abandon des classes supprimait la possibilité d'utiliser : - -```powershell -Should -BeOfType -``` - -#### Décision - -Tous les objets métier possèdent désormais une propriété : - -```text -ObjectType -``` - -Cette propriété constitue le contrat d'identification commun à tous les objets du projet. - -#### Composants concernés - -- Ensemble du projet - ---- - -## 2026-07-26 - -### Recovery centralisé - -#### Contexte - -Le Builder devait être capable de reprendre un build interrompu. - -Un simple indicateur de reprise ne permettait pas de distinguer : - -- un montage existant ; -- un montage réellement exploitable. - -#### Décision - -La préparation de l'environnement est confiée au composant **Recovery**. - -La validation d'un montage WIM est centralisée dans : - -```powershell -Test-WimMountState() -``` - -Le Pipeline ne prend jamais lui-même la décision de réutiliser un montage. - -#### Évolutions - -Recovery assure désormais : - -- la détection des montages DISM ; -- la validation des montages ; -- le démontage des montages invalides ; -- le nettoyage du Workspace ; -- la préparation de l'environnement. - -#### Composants concernés - -- Recovery -- Pipeline -- WIM -- Registry - ---- - -## 2026-08-02 - -### Introduction du BuildState - -#### Contexte - -Le BuildContext regroupait progressivement : - -- les informations du projet ; -- les paramètres du build ; -- l'état d'avancement du moteur. - -Cette approche mélangeait les données permanentes et l'état d'exécution. - -#### Décision - -Un objet dédié : - -```text -BuildState -``` - -est introduit afin de centraliser exclusivement l'état du moteur. - -Le BuildContext conserve les données du projet tandis que BuildState décrit l'exécution du Builder. - -Le BuildState regroupe notamment : - -- l'initialisation ; -- le Recovery ; -- les vérifications de l'environnement ; -- la progression du Pipeline ; -- l'état des montages ; -- le chargement de la configuration ; -- l'application des personnalisations. - -Cette séparation simplifie le développement, les tests et le suivi d'exécution. - -#### Composants concernés - -- BuildContext -- Pipeline -- Recovery -- Engine - ---- - -## 2026-08-02 - -### Séparation des métadonnées du projet - -#### Contexte - -Les informations du projet étaient réparties entre plusieurs fichiers. - -Certaines étaient codées en dur. - -#### Décision - -Toutes les métadonnées sont désormais centralisées dans : - -```text -version.json -``` - -Le Builder charge automatiquement : - -- le nom du projet ; -- la version ; -- la version de Windows cible ; -- le numéro de build ; -- l'auteur ; -- la société ; -- le dépôt Git. - -Le code ne contient plus ces informations en dur. - -#### Composants concernés - -- BuildContext -- Configuration - ---- - -## 2026-08-03 - -### Support de plusieurs versions de Windows - -#### Contexte - -Le projet ne doit pas être limité à une unique version de Windows. - -À terme, il devra être capable de personnaliser plusieurs versions officielles de Windows. - -#### Décision - -La version cible de Windows est désormais décrite dans le BuildContext sous la forme : - -```text -Project.Windows.Release -Project.Windows.Build -``` - -Le moteur de build sélectionne automatiquement les personnalisations compatibles selon la version choisie. - -Les Tweaks pourront déclarer les versions Windows qu'ils supportent. - -Cette architecture prépare le support de plusieurs versions de Windows sans modifier le moteur. - -#### Composants concernés - -- BuildContext -- Configuration -- Profiles -- Tweaks -- Engine - ---- - -## 2026-08-03 - -### Configuration pilotée par les profils - -#### Contexte - -Le moteur devait permettre à un utilisateur de choisir les personnalisations à appliquer sans modifier les fichiers de définition. - -#### Décision - -Les définitions de Tweaks constituent désormais un catalogue de fonctionnalités. - -Les profils déterminent quelles personnalisations sont activées. - -Le moteur construit ensuite une configuration finale fusionnée. - -Les objets de configuration sont indépendants des définitions originales afin de préserver leur intégrité. - -#### Composants concernés - -- Profiles -- Configuration -- Tweaks -- Engine - ---- - -## 2026-08-16 - -### Stabilisation des Engines spécialisés - -#### Contexte - -Les différents types d'Actions nécessitaient des Engines dédiés afin d'éviter de concentrer toute la logique dans l'ActionEngine principal. - -#### Décision - -Chaque type d'Action important possède désormais un Engine spécialisé. - -Les Engines suivent un contrat commun : - -```text -Context + Action - ↓ -traitement - ↓ -Context -``` - -Les Engines assurent la logique métier de leur domaine et délèguent les opérations techniques aux Managers. - -#### Composants concernés - -- ActionEngine -- ActionRegistry -- RegistryEngine -- ServiceEngine -- PackageEngine -- DriverEngine -- FeatureEngine -- CapabilityEngine -- CommandEngine -- FileEngine -- FolderEngine -- EnvironmentEngine -- ScheduledTaskEngine -- ShortcutEngine - ---- - -## 2026-08-16 - -### Standardisation du cycle de vie des Actions - -#### Contexte - -Les Engines spécialisés devaient avoir un comportement homogène concernant l'état d'une Action et l'état du Build. - -#### Décision - -Les Engines spécialisés suivent désormais un cycle de traitement commun : - -```text -Application - │ - ▼ -Traitement - │ - ▼ -Succès -``` - -En cas d'erreur : - -```text -Application - │ - ▼ -Erreur - │ - ▼ -Échec -``` - -Lorsque les propriétés correspondantes existent sur l'Action, le traitement met également à jour : - -- `Success` -- `Duration` -- `Error` - -Les statistiques correspondantes sont mises à jour lorsque le compteur existe dans le BuildContext. - -#### Composants concernés - -- ActionEngine -- RegistryEngine -- ServiceEngine -- PackageEngine -- DriverEngine -- FeatureEngine -- CapabilityEngine -- CommandEngine -- FileEngine -- FolderEngine -- EnvironmentEngine -- ScheduledTaskEngine -- ShortcutEngine - ---- - -## 2026-08-16 - -### Standardisation des providers des Managers - -#### Contexte - -Les Managers doivent pouvoir sélectionner un fournisseur technique sans intégrer directement toute la logique d'exécution dans leur propre implémentation. - -#### Décision - -Les Managers utilisent une table de correspondance permettant d'associer : - -```text -Provider - │ - ▼ -Handler -``` - -Le traitement d'un provider suit le principe : - -1. validation du provider ; -2. validation des paramètres nécessaires ; -3. résolution du handler ; -4. vérification de l'existence du handler ; -5. exécution du handler ; -6. retour du BuildContext. - -Les Managers qui le prévoient peuvent également enregistrer et réinitialiser leurs providers. - -#### Composants concernés - -- CapabilityManager -- CommandManager -- DriverManager -- EnvironmentManager -- FeatureManager -- FileManager -- FolderManager -- PackageManager -- ScheduledTaskManager -- ShortcutManager - ---- - -## 2026-08-16 - -### Correction de l'utilisation des dictionnaires ordonnés PowerShell - -#### Contexte - -Les tests des Managers ont révélé une incompatibilité entre certaines tables de providers définies comme dictionnaires ordonnés et l'utilisation de : - -```powershell -.ContainsKey() -``` - -Un `OrderedDictionary` ne fournit pas cette méthode sous la forme utilisée dans l'implémentation initiale. - -#### Décision - -Les recherches de providers doivent utiliser une méthode compatible avec le type réel de collection utilisé. - -Cette règle est protégée par les tests unitaires des Managers concernés. - -#### Composants concernés - -- CommandManager -- EnvironmentManager -- FileManager -- FolderManager -- ScheduledTaskManager -- ShortcutManager - ---- - -## 2026-08-16 - -### API publique minimale - -#### Contexte - -Le module PimsOS contient de nombreux composants internes qui ne doivent pas automatiquement devenir des éléments de l'API publique. - -#### Décision - -L'API publique reste volontairement minimale. - -La fonction actuellement exportée est : - -```powershell -Initialize-PimsOS -``` - -Les Engines, Managers, composants Core, Configuration, Infrastructure, Image et Windows restent internes au module : - -```text -PimsOS.psm1 -``` - -Cette séparation permet de faire évoluer l'implémentation interne sans créer de contrat public pour chaque fonction interne. - -#### Composants concernés - -- PimsOS.psd1 -- PimsOS.psm1 -- API publique -- Core -- Configuration -- Engines -- Managers - ---- - -## 2026-08-16 - -### Couverture de tests des Engines et Managers - -#### Contexte - -La stabilisation des Engines et Managers nécessitait une validation homogène de leurs contrats et de leurs comportements. - -#### Décision - -Les composants importants doivent disposer de tests unitaires couvrant notamment : - -- le fonctionnement nominal ; -- les paramètres obligatoires ; -- les erreurs attendues ; -- les changements d'état ; -- les statistiques lorsqu'elles sont concernées ; -- la propagation des erreurs ; -- la transmission du contexte et de l'Action. - -Les dépendances techniques peuvent être simulées lorsque l'exécution réelle n'est pas nécessaire au test du contrat. - -#### Composants concernés - -- Engines spécialisés -- Managers -- Configuration -- Registry -- Core - ---- - -# Références - -- `Architecture.md` -- `ArchitectureRules.md` -- `BuildContext.md` -- `CodingStandards.md` -- `Lifecycle.md` -- `Documentation/ADR/` - ---- +> Dernière mise à jour : 2026-09-02 -# Conclusion +Ce document est l’index des décisions techniques actuellement retenues. Les décisions architecturales normatives sont conservées dans `Documentation/ADR/`. -Ce document constitue la mémoire des principaux choix d'implémentation réalisés au cours du développement de **PimsOS Builder**. +## Décisions structurantes -Il complète les ADR en documentant les décisions techniques qui influencent le développement quotidien du projet, sans modifier son architecture. +| Décision | État | Référence | +|---|---|---| +| Module PowerShell unique | ✅ Retenue | ADR-0001, ADR-0012 | +| BuildContext centralisé | ✅ Retenue | ADR-0002, ADR-0010 | +| Pipeline séquentiel et explicite | ✅ Retenue | ADR-0004 | +| Logging centralisé | ✅ Retenue | ADR-0005 | +| Configuration JSON | ✅ Retenue | ADR-0006 | +| Tests officiels séparés des tests Legacy | ✅ Retenue | ADR-0007 | +| Gestion des erreurs centralisée | ✅ Retenue | ADR-0008 | +| Dépendances du framework explicites | ✅ Retenue | ADR-0009 | +| Contrats Engines/Managers/Actions | ✅ Retenue | ADR-0011 | +| Diagnostic sécurisé avant Pester | ✅ Retenue | ADR-0013, ADR-0014 | ---- +## Décisions récentes — 02/09/2026 -## PostInstall +### Drivers -Le sous-système PostInstall constitue une phase d'exécution distincte du Build. +- `Drivers` est la source utilisateur. +- `Workspace\Drivers` est l’espace de préparation Build. +- `CurrentSystem` exporte les pilotes du système hôte puis les injecte dans le WIM via DISM. +- Une action Driver créée dynamiquement doit être normalisée avant exécution afin de fournir `Success`, `Duration` et `Error`. -Le Build prépare les composants nécessaires dans l'image Windows, tandis que le runtime PostInstall exécute les opérations après l'installation du système. +### Chocolatey -Le runtime utilise notamment : +- Un seul moteur Chocolatey est utilisé. +- `Mode=Offline` signifie préparation Build et cache local runtime. +- `Mode=Online` signifie téléchargement runtime après `Network` et `DriverCheck`. +- `chocolatey.nupkg` est un bootstrap Offline obligatoire du Build. +- Aucun package applicatif n’est déclaré Offline sans audit de ses dépendances et payloads. +- `FailurePolicy=Stop` est la valeur par défaut. `Continue` permet de poursuivre après un échec tout en conservant l’échec dans l’état. +- Les contrôles de checksum restent obligatoires ; aucun `--ignore-checksums`. -```text -C:\ProgramData\PimsOS\PostInstall\ -``` +### PostInstall / Finalization -Les composants du runtime comprennent notamment : +- Le runtime est installé dans `C:\ProgramData\PimsOS\PostInstall`. +- `FirstLogonCommands` lance `Bootstrap.ps1`. +- Après succès de toutes les tâches, `Finalize.ps1` vérifie l’état puis programme un nettoyage différé dans un processus séparé. +- Les scripts temporaires et `unattend.xml` sont supprimés ; `state.json`, `PostInstall.log` et le cache Chocolatey sont conservés. -```text -Bootstrap.ps1 -Network.ps1 -PostInstall.ps1 -State.ps1 -UI.ps1 -``` +### Microsoft Store -`UI.ps1` fournit l'interface console du premier démarrage pour afficher l'état du réseau, fournir une aide à l'utilisateur et attendre la disponibilité réseau. +- Microsoft Store reste fourni par l’image Windows source. +- PimsOS ne remplace pas Store par un provider applicatif. +- La validation VM du 02/09/2026 confirme Store, iCloud installé depuis Store et Widgets fonctionnels. -Les états fonctionnels du runtime sont notamment : +## Preuves de validation -```text -Pending -Running -WaitingForNetwork -Completed -Failed -``` +- Build ISO : `Output\PimsOS_3.0.0_20260902_141928.iso` ; code retour 0. +- WIM SHA256 : `B6BA0B8E8474761380FCC26DB165DC786162EA916B8D35192A977C49E72E9941`. +- Pester sécurisé : 815 Passed / 0 Failed / 1 Skipped / 0 Inconclusive / 0 NotRun. +- Rapport : `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md`. +- Validation VM : `state.json` final `Completed`, `Verification.Verified=true`, Cleanup programmé et sans erreur. -La vérification réseau distingue l'état de l'adaptateur, la connexion réseau et l'accès Internet. +## Règle de maintenance -La validation automatisée du sous-système PostInstall couvre également l'interface UI et l'attente réseau avec des tests Pester dédiés. +Toute modification de l’architecture doit être ajoutée à un ADR lorsqu’elle change un contrat, une responsabilité ou une règle structurante. Ce document reste un index de décisions et ne doit pas devenir un second système de spécification. diff --git a/Documentation/Testing.md b/Documentation/Testing.md index 99c3303..6e815a6 100644 --- a/Documentation/Testing.md +++ b/Documentation/Testing.md @@ -4,10 +4,13 @@ > > Statut : Développement / architecture stabilisée > -> Dernière mise à jour : 2026-08-31 +> Dernière mise à jour : 2026-09-02 --- +> Les références antérieures au 02/09/2026 sont historiques ; l’état courant est celui du 02/09/2026. + + # Objectif Ce document décrit la stratégie de tests utilisée dans le projet **PimsOS Builder**. @@ -116,6 +119,32 @@ La présence de tests dans `Tests\Legacy` ne signifie donc pas qu'ils doivent --- +# Diagnostic sécurisé avant Pester + +Depuis le 02/09/2026, `Tests\Tools\Invoke-PimsOSDiagnostics.ps1` constitue le garde-fou recommandé avant une campagne ciblée. + +Il analyse statiquement les fichiers de tests et distingue : + +| Classe | Signification | Exécution normale | +|---|---|---| +| `SAFE` | Aucun appel Build dangereux non neutralisé détecté | Oui | +| `BUILD-CAPABLE` | Opération de Build/WIM/ISO potentiellement réelle | Non | +| `UNKNOWN` | Neutralisation impossible à prouver statiquement | Non | + +Commandes de référence : + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Unit -InventoryOnly -ExplainFailures +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Integration -InventoryOnly -ExplainFailures +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -InventoryOnly -ExplainFailures +``` + +Le mode `-InventoryOnly` n'exécute aucun test. `-BuildValidation` exige explicitement `-AllowBuild` et reste réservé aux validations réelles volontairement autorisées. + +L'analyse est volontairement conservatrice : un faux positif `BUILD-CAPABLE` est acceptable ; un faux négatif pouvant lancer un Build réel ne l'est pas. + +--- + # Campagne de validation officielle La configuration Pester du projet est définie dans : @@ -138,31 +167,51 @@ avec les tests correspondant à l'architecture actuelle de PimsOS Builder. --- -# Résultat de la dernière validation +# Résultats de référence -La dernière exécution complète de la campagne officielle a produit : +La campagne officielle de référence du 02/09 est : ```text -Tests Passed: 971 (dernier résultat communiqué) -Tests Failed: 0 -Tests Skipped: 1 -Tests Inconclusive: 0 -Tests NotRun: 0 - -> Le fichier `Tests\testResults.xml` de l’archive reste historique et correspond à une campagne du 28/08/2026. Il doit être régénéré pour refléter la campagne finale communiquée le 31/08. +815 Passed +0 Failed +1 Skipped +0 Inconclusive +0 NotRun +816 Total ``` +Le rapport associé est `Tests\Reports\Diagnostics\Diagnostics-20260902-141259.md`. Le diagnostic a analysé 66 fichiers Unit : 66 SAFE, 0 BUILD-CAPABLE et 0 UNKNOWN. + +Les campagnes précédentes, dont `971 Passed / 0 Failed / 1 Skipped`, restent historiques. Le `Skipped` actuel est conditionnel et intentionnel. + +Le fichier XML historique doit être régénéré si une preuve machine-readable de la campagne 02/09 est souhaitée. + Durée totale : ```text -5,69 secondes +durée variable selon les tests et l’environnement ``` -## Interprétation +## Validation fonctionnelle VM — 02/09/2026 + +La campagne Pester ne constitue pas à elle seule la preuve du runtime. Une validation Hyper-V complémentaire a confirmé : + +- démarrage de la nouvelle ISO ; +- disponibilité réseau ; +- DriverCheck ; +- installation locale du bootstrap Chocolatey ; +- exécution du catalogue ; +- `FailurePolicy=Continue` sur Google Chrome ; +- poursuite sur les packages suivants ; +- état final `Completed` ; +- `Verification.Verified=true` ; +- nettoyage différé des scripts ; +- suppression de `unattend.xml` ; +- conservation du journal, de l'état et du cache Chocolatey. -### Tests réussis +## Interprétation -**701 tests** ont été exécutés avec succès. +Les anciens sous-résultats détaillés conservés plus bas dans ce document sont historiques. Pour la référence actuelle, utiliser la section « Résultats de référence » ci-dessus et les rapports Pester générés par la CI. ### Tests échoués @@ -429,15 +478,35 @@ automatisée lorsque : L'état de référence actuellement validé est : ```text -971 Passed +815 Passed 0 Failed 1 Skipped 0 Inconclusive 0 NotRun +816 Total ``` --- +# Validation du Build réel + +Le diagnostic et les tests Pester ne remplacent pas la validation de production de l'image. + +Le 02/09/2026, un Build réel complet a été exécuté avec succès : + +- Windows 11 Professionnel, index 6 ; +- 27 Tweaks appliqués ; +- drivers `CurrentSystem` exportés et injectés par DISM ; +- PostInstall préparé ; +- WIM démonté et synchronisé vers la source ISO avec SHA256 vérifié ; +- ISO créée avec `oscdimg.exe` détecté via le Windows ADK ; +- code retour `0` ; +- aucun montage WIM résiduel. + +L'artefact a ensuite été validé en VM sur FirstBoot/PostInstall/Finalization. + +--- + # Maintenance de la stratégie de tests Toute nouvelle fonctionnalité importante doit être accompagnée des tests diff --git a/Documentation/Tweaks.md b/Documentation/Tweaks.md index d960655..db6b980 100644 --- a/Documentation/Tweaks.md +++ b/Documentation/Tweaks.md @@ -3,7 +3,7 @@ > **27 Tweaks fonctionnels** dans le catalogue actuel. > Version technique : 3.0.0 -> Mise à jour : 2026-09-01 +> Mise à jour : 2026-09-02 ## Objectif diff --git a/Modules/Actions/DriverEngine.ps1 b/Modules/Actions/DriverEngine.ps1 index 9657dbf..6fbfcc9 100644 --- a/Modules/Actions/DriverEngine.ps1 +++ b/Modules/Actions/DriverEngine.ps1 @@ -37,6 +37,44 @@ function Invoke-DriverAction { $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + # -------------------------------------------------- + # Normalisation de l'action + # -------------------------------------------------- + # Les actions créées dynamiquement par le Pipeline ne portent pas + # forcément les propriétés de résultat. Les ajouter avant le try + # garantit que le bloc catch peut toujours enregistrer le résultat, + # même si la validation ou l'injection échoue. + + if ($null -eq $Action.PSObject.Properties["Success"]) { + + Add-Member ` + -InputObject $Action ` + -MemberType NoteProperty ` + -Name "Success" ` + -Value $false + + } + + if ($null -eq $Action.PSObject.Properties["Duration"]) { + + Add-Member ` + -InputObject $Action ` + -MemberType NoteProperty ` + -Name "Duration" ` + -Value ([TimeSpan]::Zero) + + } + + if ($null -eq $Action.PSObject.Properties["Error"]) { + + Add-Member ` + -InputObject $Action ` + -MemberType NoteProperty ` + -Name "Error" ` + -Value $null + + } + try { # -------------------------------------------------- diff --git a/Modules/Core/Pipeline.ps1 b/Modules/Core/Pipeline.ps1 index 56c739c..cbe6181 100644 --- a/Modules/Core/Pipeline.ps1 +++ b/Modules/Core/Pipeline.ps1 @@ -7,6 +7,14 @@ Set-StrictMode -Version Latest +# ========================================== +# Dépendances des providers utilisés par le pipeline +# ========================================== + +. "$PSScriptRoot\..\Package\Chocolatey.ps1" +. "$PSScriptRoot\..\Package\ChocolateyCache.ps1" + + # ========================================== # Exécute une étape du pipeline @@ -588,6 +596,50 @@ function Apply-Drivers { } +# ========================================== +# Prépare le cache Chocolatey Offline +# ========================================== + +function Prepare-ChocolateyCache { + [CmdletBinding()] + param([Parameter(Mandatory)][psobject]$Context) + + $CatalogPath = Join-Path $Context.Project.Root 'Config\Packages\Chocolatey.json' + if (-not (Test-Path -LiteralPath $CatalogPath -PathType Leaf)) { + throw "Le catalogue Chocolatey est introuvable : $CatalogPath" + } + + Write-Log "Préparation du cache Chocolatey Offline..." INFO + $Result = Initialize-ChocolateyCache -Context $Context -CatalogPath $CatalogPath + + # Chocolatey est un prérequis du runtime PostInstall : le Build doit + # impérativement embarquer son .nupkg avant de préparer l'image. + $Bootstrap = Test-ChocolateyBootstrapPackage -CachePath $Result.CachePath + + if (-not $Bootstrap.Present) { + throw "Le Build ne peut pas continuer : le bootstrap Chocolatey n'est pas disponible dans le cache." + } + + Write-Log ("Cache Chocolatey Offline : {0} package(s), {1} téléchargé(s), {2} déjà présent(s)." -f $Result.Total, $Result.Downloaded, $Result.AlreadyCached) SUCCESS + Write-Log ("Bootstrap Chocolatey prêt pour le runtime : {0}" -f $Bootstrap.Name) SUCCESS + + # Conserve explicitement la preuve de préparation dans le BuildState. + if ($Context.BuildState.PSObject.Properties.Name -contains 'Chocolatey') { + $Context.BuildState.Chocolatey.BootstrapReady = $true + $Context.BuildState.Chocolatey.BootstrapPath = $Bootstrap.Path + } + else { + $Context.BuildState | Add-Member -MemberType NoteProperty -Name Chocolatey -Value ([pscustomobject]@{ + BootstrapReady = $true + BootstrapPath = $Bootstrap.Path + CachePath = $Result.CachePath + OfflineCount = $Result.Total + }) -Force + } + + return $Context +} + # ========================================== # Prépare le PostInstall dans l'image montée # ========================================== @@ -624,10 +676,16 @@ function Prepare-PostInstall { $RuntimeSource = Get-PostInstallRuntimePath + $CatalogPath = Join-Path $Context.Project.Root 'Config\Packages\Chocolatey.json' + $ProviderPath = Join-Path $Context.Project.Root 'Modules\Package\Chocolatey.ps1' + $RuntimeResult = Install-PimsOSPostInstallRuntime ` -MountPath $MountPath ` - -SourcePath $RuntimeSource + -SourcePath $RuntimeSource ` + -ChocolateyProviderPath $ProviderPath ` + -ChocolateyCatalogPath $CatalogPath ` + -ChocolateyCachePath (Get-ChocolateyCachePath -Context $Context) $BootstrapPath = "C:\ProgramData\PimsOS\PostInstall\Bootstrap.ps1" @@ -1059,6 +1117,26 @@ function Get-BuildPipeline { }, + # ------------------------------------------ + # Préparation du cache Chocolatey Offline + # ------------------------------------------ + + @{ + + Id = "PrepareChocolateyCache" + Name = "Préparation du cache Chocolatey Offline" + + Action = { + + param($Context) + + Prepare-ChocolateyCache ` + -Context $Context + + } + + }, + # ------------------------------------------ # Préparation du PostInstall # ------------------------------------------ diff --git a/Modules/Package/Chocolatey.ps1 b/Modules/Package/Chocolatey.ps1 index 8ecdcb5..27fc083 100644 --- a/Modules/Package/Chocolatey.ps1 +++ b/Modules/Package/Chocolatey.ps1 @@ -2,7 +2,7 @@ # Module : Package / Chocolatey # Projet : PimsOS Builder # Version : 1.0.0 -# Compatible : PowerShell 7+ +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest @@ -90,9 +90,20 @@ function Invoke-ChocolateyCli { ($Arguments -join " ") ) INFO - & $Choco.Source @Arguments + # Capture la sortie de choco.exe afin qu'elle ne soit pas mélangée + # à l'objet de résultat retourné. Ceci est indispensable sous + # Windows PowerShell 5.1 : l'affectation d'un appel natif peut + # sinon produire un tableau contenant les lignes stdout + l'objet + # ExitCode, et $Result.ExitCode échoue alors sur les chaînes. + $ChocoOutput = @(& $Choco.Source @Arguments 2>&1) $ExitCode = $LASTEXITCODE + foreach ($OutputLine in $ChocoOutput) { + if ($null -ne $OutputLine -and -not [string]::IsNullOrWhiteSpace([string]$OutputLine)) { + Write-Log ([string]$OutputLine) INFO + } + } + return [pscustomobject]@{ ExitCode = [int]$ExitCode } @@ -133,6 +144,156 @@ function Find-ChocolateyCachedPackage { Select-Object -First 1 } +# -------------------------------------------------- +# Installe Chocolatey depuis le package embarqué +# -------------------------------------------------- +function Install-ChocolateyBootstrap { + + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$BootstrapPackagePath + ) + + if (Test-ChocolateyAvailable) { + return $true + } + + if (-not (Test-Path -LiteralPath $BootstrapPackagePath -PathType Leaf)) { + throw "Le package bootstrap Chocolatey est introuvable : $BootstrapPackagePath" + } + + $TempPath = Join-Path $env:TEMP ("PimsOS-ChocolateyBootstrap-" + [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $TempPath -Force -ErrorAction Stop | Out-Null + + try { + Write-Log "Installation locale de Chocolatey depuis le cache PimsOS." INFO + + # Un .nupkg Chocolatey est une archive ZIP. Expand-Archive dépend de + # l'extension .zip et refuse donc certains .nupkg sous Windows + # PowerShell 5.1 comme sous PowerShell 7. On utilise directement + # l'API .NET commune aux deux environnements. + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory( + $BootstrapPackagePath, + $TempPath + ) + + $InstallScript = Join-Path $TempPath 'tools\chocolateyInstall.ps1' + + if (-not (Test-Path -LiteralPath $InstallScript -PathType Leaf)) { + throw "Le package bootstrap Chocolatey ne contient pas tools\chocolateyInstall.ps1." + } + + & $InstallScript + if ($LASTEXITCODE -notin @(0, $null)) { + throw "Le script d'installation local de Chocolatey a retourné le code $LASTEXITCODE." + } + + if (-not (Test-ChocolateyAvailable)) { + $Candidate = Join-Path $env:ProgramData 'chocolatey\bin\choco.exe' + if (Test-Path -LiteralPath $Candidate -PathType Leaf) { + $env:Path = "$(Split-Path $Candidate);$env:Path" + } + } + + if (-not (Test-ChocolateyAvailable)) { + throw "Chocolatey n'est pas disponible après son installation locale." + } + + Write-Log "Chocolatey installé localement avec succès." SUCCESS + return $true + } + finally { + Remove-Item -LiteralPath $TempPath -Recurse -Force -ErrorAction SilentlyContinue + } +} + +# -------------------------------------------------- +# Installe les packages du catalogue selon leur Mode +# -------------------------------------------------- +function Invoke-ChocolateyCatalog { + [CmdletBinding()] + param( + [Parameter(Mandatory)][psobject]$Context, + [Parameter(Mandatory)][string]$CatalogPath, + [Parameter(Mandatory)][string]$RuntimeCachePath + ) + + if (-not (Test-Path -LiteralPath $CatalogPath -PathType Leaf)) { + throw "Le catalogue Chocolatey runtime est introuvable : $CatalogPath" + } + + if (-not (Test-Path -LiteralPath $RuntimeCachePath -PathType Container)) { + New-Item -ItemType Directory -Path $RuntimeCachePath -Force -ErrorAction Stop | Out-Null + } + + $Catalog = Get-Content -LiteralPath $CatalogPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + $Packages = @($Catalog.Packages | Where-Object { $_.Enabled -eq $true }) + $Results = [System.Collections.Generic.List[object]]::new() + + foreach ($Package in $Packages) { + $Mode = if ($Package.PSObject.Properties.Name -contains 'Mode' -and -not [string]::IsNullOrWhiteSpace([string]$Package.Mode)) { [string]$Package.Mode } else { 'Online' } + if ($Mode -eq 'Disabled' -or $Package.Id -eq 'chocolatey') { continue } + + $FailurePolicy = if ( + $Package.PSObject.Properties.Name -contains 'FailurePolicy' -and + -not [string]::IsNullOrWhiteSpace([string]$Package.FailurePolicy) + ) { + [string]$Package.FailurePolicy + } + else { + 'Stop' + } + + if ($FailurePolicy -notin @('Stop','Continue')) { + throw "FailurePolicy Chocolatey invalide pour '$($Package.Id)' : $FailurePolicy" + } + + $Action = [pscustomobject]@{ + Name = [string]$Package.Id + Version = if ($Package.PSObject.Properties.Name -contains 'Version') { [string]$Package.Version } else { $null } + Mode = $Mode + FailurePolicy = $FailurePolicy + } + + Write-Log ("Installation Chocolatey : {0} [{1}]" -f $Action.Name, $Mode) INFO + + try { + $null = Invoke-ChocolateyPackage -Context $Context -Action $Action + $Results.Add([pscustomobject]@{ + Id = $Action.Name + Mode = $Mode + Status = 'Installed' + FailurePolicy = $FailurePolicy + Error = $null + }) + } + catch { + $ErrorMessage = $_.Exception.Message + + if ($FailurePolicy -eq 'Stop') { + throw + } + + Write-Log ( + "Package Chocolatey '{0}' en échec, poursuite du PostInstall selon FailurePolicy=Continue : {1}" -f + $Action.Name, + $ErrorMessage + ) WARNING + + $Results.Add([pscustomobject]@{ + Id = $Action.Name + Mode = $Mode + Status = 'Failed' + FailurePolicy = $FailurePolicy + Error = $ErrorMessage + }) + } + } + + return @($Results) +} + # -------------------------------------------------- # Applique un package Chocolatey # -------------------------------------------------- @@ -165,29 +326,34 @@ function Invoke-ChocolateyPackage { $Version = [string]$Action.Version } + $Mode = if ($Action.PSObject.Properties.Name -contains 'Mode' -and -not [string]::IsNullOrWhiteSpace([string]$Action.Mode)) { [string]$Action.Mode } else { 'Online' } + + if ($Mode -notin @('Offline','Online')) { + throw "Mode Chocolatey invalide pour '$($Action.Name)' : $Mode" + } + $CachedPackage = Find-ChocolateyCachedPackage ` -CachePath $CachePath ` -Name $Action.Name ` -Version $Version - if ($null -ne $CachedPackage) { - Write-Log ( - "Package Chocolatey trouvé dans le cache : {0}" -f - $CachedPackage.Name - ) INFO + if ($Mode -eq 'Offline') { + if ($null -eq $CachedPackage) { + throw "Le package Chocolatey '$($Action.Name)' est en mode Offline mais absent du cache PimsOS." + } + Write-Log ("Package Chocolatey offline trouvé : {0}" -f $CachedPackage.Name) INFO + $Source = $CachePath } else { - Write-Log ( - "Package Chocolatey absent du cache : {0}. Chocolatey pourra le récupérer depuis la source configurée." -f - $Action.Name - ) INFO + if ($null -ne $CachedPackage) { + Write-Log ("Package Chocolatey online déjà présent dans le cache : {0}" -f $CachedPackage.Name) INFO + } + else { + Write-Log ("Package Chocolatey online : téléchargement depuis Community : {0}" -f $Action.Name) INFO + } + $Source = $CommunitySource } - # Le cache est persistant et partagé entre les installations. - # La source locale est prioritaire, avec le dépôt communautaire - # en secours pour les packages/dépendances absents du cache. - $Source = "{0};{1}" -f $CachePath, $CommunitySource - $Arguments = @( "install", $Action.Name, diff --git a/Modules/Package/ChocolateyCache.ps1 b/Modules/Package/ChocolateyCache.ps1 index a4371b7..442509a 100644 --- a/Modules/Package/ChocolateyCache.ps1 +++ b/Modules/Package/ChocolateyCache.ps1 @@ -42,6 +42,31 @@ function Get-ChocolateyPackageDefinitions { if ([string]::IsNullOrWhiteSpace([string]$Package.Id)) { throw "Une entrée activée du catalogue Chocolatey ne possède pas d'Id." } + + $Mode = if ($Package.PSObject.Properties.Name -contains 'Mode' -and + -not [string]::IsNullOrWhiteSpace([string]$Package.Mode)) { + [string]$Package.Mode + } else { + 'Online' + } + + if ($Mode -notin @('Offline','Online','Disabled')) { + throw "Le package '$($Package.Id)' possède un Mode invalide '$Mode'. Valeurs attendues : Offline, Online, Disabled." + } + + $FailurePolicy = if ( + $Package.PSObject.Properties.Name -contains 'FailurePolicy' -and + -not [string]::IsNullOrWhiteSpace([string]$Package.FailurePolicy) + ) { + [string]$Package.FailurePolicy + } + else { + 'Stop' + } + + if ($FailurePolicy -notin @('Stop','Continue')) { + throw "Le package '$($Package.Id)' possède une FailurePolicy invalide '$FailurePolicy'. Valeurs attendues : Stop, Continue." + } } return $Packages @@ -50,7 +75,7 @@ function Get-ChocolateyPackageDefinitions { # -------------------------------------------------- # Recherche un package déjà présent dans le cache # -------------------------------------------------- -function Find-ChocolateyCachedPackage { +function Find-ChocolateyCachePackage { [CmdletBinding()] param( [Parameter(Mandatory)][string]$CachePath, @@ -109,7 +134,7 @@ function Save-ChocolateyPackageToCache { $Version = [string]$Package.Version } - $Existing = Find-ChocolateyCachedPackage ` + $Existing = Find-ChocolateyCachePackage ` -CachePath $CachePath ` -Name ([string]$Package.Id) ` -Version $Version @@ -157,6 +182,58 @@ function Save-ChocolateyPackageToCache { } } +# -------------------------------------------------- +# Vérifie que le package bootstrap Chocolatey est bien +# présent et exploitable dans le cache de Build. +# -------------------------------------------------- +function Test-ChocolateyBootstrapPackage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$CachePath + ) + + if (-not (Test-Path -LiteralPath $CachePath -PathType Container)) { + throw "Le cache Chocolatey est introuvable : $CachePath" + } + + $Package = Get-ChildItem -LiteralPath $CachePath -File -ErrorAction SilentlyContinue | + Where-Object { + $_.Name -eq 'chocolatey.nupkg' -or $_.Name -like 'chocolatey.*.nupkg' + } | + Sort-Object @{Expression={ if ($_.Name -eq 'chocolatey.nupkg') { 0 } else { 1 } }}, Name | + Select-Object -First 1 + + if ($null -eq $Package) { + throw "Le package bootstrap Chocolatey est absent du cache Build : $CachePath" + } + + try { + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop + $Archive = [System.IO.Compression.ZipFile]::OpenRead($Package.FullName) + try { + $InstallScript = $Archive.Entries | + Where-Object { $_.FullName -ieq 'tools/chocolateyInstall.ps1' -or $_.FullName -ieq 'tools\\chocolateyInstall.ps1' } | + Select-Object -First 1 + + if ($null -eq $InstallScript) { + throw "Le package bootstrap Chocolatey '$($Package.Name)' ne contient pas tools\\chocolateyInstall.ps1." + } + } + finally { + $Archive.Dispose() + } + } + catch { + throw "Le package bootstrap Chocolatey '$($Package.Name)' n'est pas un .nupkg exploitable : $($_.Exception.Message)" + } + + return [pscustomobject]@{ + Present = $true + Path = $Package.FullName + Name = $Package.Name + } +} + # -------------------------------------------------- # Prépare le cache persistant des packages activés # -------------------------------------------------- @@ -168,7 +245,11 @@ function Initialize-ChocolateyCache { ) $CachePath = Get-ChocolateyCachePath -Context $Context - $Packages = @(Get-ChocolateyPackageDefinitions -Path $CatalogPath) + $Packages = @(Get-ChocolateyPackageDefinitions -Path $CatalogPath | Where-Object { + $Mode = if ($_.PSObject.Properties.Name -contains 'Mode' -and -not [string]::IsNullOrWhiteSpace([string]$_.Mode)) { [string]$_.Mode } else { 'Online' } + $Mode -eq 'Offline' + }) + $Results = [System.Collections.Generic.List[object]]::new() foreach ($Package in $Packages) { diff --git a/Modules/Package/ChocolateyCatalog.ps1 b/Modules/Package/ChocolateyCatalog.ps1 new file mode 100644 index 0000000..f9e0fd3 --- /dev/null +++ b/Modules/Package/ChocolateyCatalog.ps1 @@ -0,0 +1,132 @@ +# ========================================== +# Module : Package / Chocolatey Catalog +# Projet : PimsOS Builder +# Version : 1.0.0 +# ========================================== + +Set-StrictMode -Version Latest + +function Get-ChocolateyCatalogPath { + [CmdletBinding()] + param([Parameter(Mandatory)][psobject]$Context) + + if ($null -eq $Context.Project -or [string]::IsNullOrWhiteSpace([string]$Context.Project.Root)) { + throw "Le contexte ne contient pas de racine de projet valide." + } + + return Join-Path ([string]$Context.Project.Root) 'Config\Packages\Chocolatey.json' +} + +function Read-ChocolateyCatalog { + [CmdletBinding()] + param([Parameter(Mandatory)][psobject]$Context) + + $Path = Get-ChocolateyCatalogPath -Context $Context + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { + throw "Catalogue Chocolatey introuvable : $Path" + } + + $Catalog = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json + if ($null -eq $Catalog.Packages) { + $Catalog | Add-Member -MemberType NoteProperty -Name Packages -Value @() + } + + return $Catalog +} + +function Save-ChocolateyCatalog { + [CmdletBinding()] + param( + [Parameter(Mandatory)][psobject]$Context, + [Parameter(Mandatory)][psobject]$Catalog + ) + + $Path = Get-ChocolateyCatalogPath -Context $Context + $Directory = Split-Path -Parent $Path + New-Item -ItemType Directory -Path $Directory -Force -ErrorAction Stop | Out-Null + + $Json = $Catalog | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText( + $Path, + $Json + [Environment]::NewLine, + (New-Object System.Text.UTF8Encoding($false)) + ) + + return $Path +} + +function Test-ChocolateyCatalogPackageId { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$Id) + + return $Id -match '^[a-z0-9][a-z0-9.-]*$' +} + +function Add-ChocolateyCatalogPackage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][psobject]$Context, + [Parameter(Mandatory)][string]$Id, + [string]$Version, + [ValidateSet('Online','Offline','Disabled')][string]$Mode = 'Online', + [ValidateSet('Stop','Continue')][string]$FailurePolicy = 'Stop', + [string]$Category = 'Other' + ) + + $Id = $Id.Trim().ToLowerInvariant() + if (-not (Test-ChocolateyCatalogPackageId -Id $Id)) { + throw "Identifiant Chocolatey invalide : '$Id'." + } + + if ($Id -eq 'chocolatey') { + throw "Le package 'chocolatey' est réservé au bootstrap PimsOS et ne doit pas être ajouté manuellement." + } + + $Catalog = Read-ChocolateyCatalog -Context $Context + $Packages = @($Catalog.Packages) + + if ($Packages | Where-Object { [string]$_.Id -ieq $Id }) { + throw "Le package '$Id' existe déjà dans le catalogue." + } + + $Package = [ordered]@{ + Id = $Id + Enabled = ($Mode -ne 'Disabled') + Category = if ([string]::IsNullOrWhiteSpace($Category)) { 'Other' } else { $Category.Trim() } + Mode = $Mode + FailurePolicy = $FailurePolicy + Version = if ([string]::IsNullOrWhiteSpace($Version)) { $null } else { $Version.Trim() } + } + + $Catalog.Packages = @($Packages + [pscustomobject]$Package) + Save-ChocolateyCatalog -Context $Context -Catalog $Catalog | Out-Null + Write-Log "Package Chocolatey ajouté au catalogue : $Id ($Mode)." SUCCESS + + return [pscustomobject]$Package +} + +function Remove-ChocolateyCatalogPackage { + [CmdletBinding()] + param( + [Parameter(Mandatory)][psobject]$Context, + [Parameter(Mandatory)][string]$Id + ) + + $Id = $Id.Trim().ToLowerInvariant() + if ($Id -eq 'chocolatey') { + throw "Le package 'chocolatey' est obligatoire pour le bootstrap PimsOS et ne peut pas être supprimé." + } + + $Catalog = Read-ChocolateyCatalog -Context $Context + $Packages = @($Catalog.Packages) + $Match = @($Packages | Where-Object { [string]$_.Id -ieq $Id }) + + if ($Match.Count -eq 0) { + throw "Le package '$Id' n'existe pas dans le catalogue." + } + + $Catalog.Packages = @($Packages | Where-Object { [string]$_.Id -ine $Id }) + Save-ChocolateyCatalog -Context $Context -Catalog $Catalog | Out-Null + Write-Log "Package Chocolatey supprimé du catalogue : $Id." INFO + return $true +} diff --git a/Modules/PimsOS.psm1 b/Modules/PimsOS.psm1 index f70d539..3186863 100644 --- a/Modules/PimsOS.psm1 +++ b/Modules/PimsOS.psm1 @@ -1,3 +1,52 @@ +# ========================================== +# Module : PimsOS +# Projet : PimsOS Builder +# Version : 3.0.0 +# Compatible : PowerShell 7+ +# ========================================== + +Set-StrictMode -Version Latest + +# ========================================== +# Chargement des composants internes +# ========================================== + +# -------------------------------------------------- +# Infrastructure +# -------------------------------------------------- + +. "$PSScriptRoot\Infrastructure\Security.ps1" +. "$PSScriptRoot\Infrastructure\Logger.ps1" +. "$PSScriptRoot\Infrastructure\Recovery.ps1" +. "$PSScriptRoot\Infrastructure\Prerequisites.ps1" +. "$PSScriptRoot\Infrastructure\Check.ps1" +. "$PSScriptRoot\Infrastructure\Validation.ps1" +. "$PSScriptRoot\Infrastructure\Service.ps1" + +# -------------------------------------------------- +# Core +# -------------------------------------------------- + +. "$PSScriptRoot\Core\Core.ps1" +. "$PSScriptRoot\Core\BuildContext.ps1" +. "$PSScriptRoot\Core\ActionRegistry.ps1" +. "$PSScriptRoot\Core\Workflow.ps1" +. "$PSScriptRoot\Core\Pipeline.ps1" +. "$PSScriptRoot\Core\Complete-Build.ps1" +. "$PSScriptRoot\Core\Report.ps1" +. "$PSScriptRoot\Core\Engine.ps1" + +# -------------------------------------------------- +# Configuration +# -------------------------------------------------- + +. "$PSScriptRoot\Configuration\Categories.ps1" +. "$PSScriptRoot\Configuration\Tweak.ps1" +. "$PSScriptRoot\Configuration\TweakCatalog.ps1" +. "$PSScriptRoot\Configuration\Profile.ps1" +. "$PSScriptRoot\Configuration\Configuration.ps1" + +# -------------------------------------------------- # Managers # -------------------------------------------------- @@ -18,6 +67,7 @@ . "$PSScriptRoot\Package\Chocolatey.ps1" . "$PSScriptRoot\Package\ChocolateyCache.ps1" +. "$PSScriptRoot\Package\ChocolateyCatalog.ps1" . "$PSScriptRoot\Package\Winget.ps1" # -------------------------------------------------- @@ -29,3 +79,261 @@ # -------------------------------------------------- # Image # -------------------------------------------------- + +. "$PSScriptRoot\Image\Iso.ps1" +. "$PSScriptRoot\Image\Dism.ps1" +. "$PSScriptRoot\Image\Wim.ps1" + +# ========================================== +# PostInstall +# ========================================== + +. "$PSScriptRoot\PostInstall\State.ps1" +. "$PSScriptRoot\PostInstall\Network.ps1" +. "$PSScriptRoot\PostInstall\PostInstall.ps1" +. "$PSScriptRoot\PostInstall\Finalize.ps1" +. "$PSScriptRoot\PostInstall\FirstBoot.ps1" +. "$PSScriptRoot\PostInstall\Unattend.ps1" +. "$PSScriptRoot\PostInstall\Installer.ps1" + +# ========================================== +# UI +# ========================================== + +. "$PSScriptRoot\UI\Wizard.ps1" + +# -------------------------------------------------- +# Actions +# -------------------------------------------------- + +. "$PSScriptRoot\Actions\ActionEngine.ps1" +. "$PSScriptRoot\Actions\RegistryEngine.ps1" +. "$PSScriptRoot\Actions\ServiceEngine.ps1" +. "$PSScriptRoot\Actions\PackageEngine.ps1" +. "$PSScriptRoot\Actions\DriverEngine.ps1" +. "$PSScriptRoot\Actions\FeatureEngine.ps1" +. "$PSScriptRoot\Actions\CapabilityEngine.ps1" +. "$PSScriptRoot\Actions\CommandEngine.ps1" +. "$PSScriptRoot\Actions\FileEngine.ps1" +. "$PSScriptRoot\Actions\FolderEngine.ps1" +. "$PSScriptRoot\Actions\EnvironmentEngine.ps1" +. "$PSScriptRoot\Actions\ScheduledTaskEngine.ps1" +. "$PSScriptRoot\Actions\ShortcutEngine.ps1" + + +# ========================================== +# Initialisation de PimsOS +# ========================================== + +function Initialize-PimsOS { + + [CmdletBinding()] + param() + + $ExitCode = 0 + $Context = $null + + try { + + # ------------------------------------------ + # BuildContext + # ------------------------------------------ + + Write-Verbose "Création du BuildContext..." + + $Context = New-BuildContext + + $Context = Initialize-BuildContext ` + -Context $Context + + + # ------------------------------------------ + # Logger + # ------------------------------------------ + + Start-Logger ` + -Path $Context.Logger.Path + + Write-Log "Initialisation de PimsOS..." + + Write-Log ( + "Version : {0}" -f + $Context.Project.Version + ) + + Write-Log ( + "Build ID : {0}" -f + $Context.Build.Id + ) + + Write-Log ( + "Projet : {0}" -f + $Context.Project.Root + ) + + Write-Log ( + "Profil : {0}" -f + $Context.ConfigurationProfile + ) + + # ------------------------------------------ + # Assistant de configuration + # ------------------------------------------ + + if ($Context.Build.Interactive) { + + Write-Verbose "Lancement de l'assistant de configuration..." + + Write-Log "Lancement de l'assistant de configuration..." INFO + + $Context = Show-PimsOSBuildWizard ` + -Context $Context + } + + # ------------------------------------------ + # Recovery + # ------------------------------------------ + + Write-Verbose "Préparation de l'environnement..." + + $Context = Repair-BuildEnvironment ` + -Context $Context + + # ------------------------------------------ + # Windows ADK + # ------------------------------------------ + + $AdkRequired = $false + + if ( + $null -ne $Context.Project.Config -and + $null -ne $Context.Project.Config.Requirements -and + $null -ne $Context.Project.Config.Requirements.WindowsADK + ) { + + $AdkConfiguration = + $Context.Project.Config.Requirements.WindowsADK + + if ( + $AdkConfiguration.PSObject.Properties.Name ` + -contains "Required" + ) { + + $AdkRequired = + [bool]$AdkConfiguration.Required + + } + + } + + + if ($AdkRequired) { + + Write-Verbose "Vérification de Windows ADK..." + + $AdkStatus = Test-PimsOSWindowsADK + + if (-not $AdkStatus.Installed) { + + Write-Log ( + "Windows ADK requis mais absent. " + + "Préparation de l'installation..." + ) + + $Context.BuildState.Environment.WindowsADK = $false + + Install-PimsOSWindowsADK ` + -Configuration $Context.Project.Config ` + -DestinationPath ( + Join-Path ` + $Context.Workspace.Temp ` + "ADK" + ) + + Write-Log ( + "Windows ADK installé : {0}" -f + (Get-PimsOSOsCdImgPath) + ) SUCCESS + + } + else { + + Write-Log ( + "Windows ADK déjà disponible : {0}" -f + $AdkStatus.OsCdImgPath + ) SUCCESS + + } + + } + + + # ------------------------------------------ + # Vérifications + # ------------------------------------------ + + Write-Verbose "Vérification de l'environnement..." + + $Context = Invoke-EnvironmentChecks ` + -Context $Context + + if (-not $Context.Report.Environment.Success) { + + throw "Les prérequis ne sont pas satisfaits." + + } + + # ------------------------------------------ + # Pipeline + # ------------------------------------------ + + $Context = Invoke-BuildPipeline ` + -Context $Context + + # ------------------------------------------ + # Etat final + # ------------------------------------------ + + $Context.BuildState.Status = "Completed" + + Write-Log ( + "Etat final : {0}" -f + $Context.BuildState.Status + ) + + Write-Log "Initialisation de PimsOS terminée." SUCCESS + + } + catch { + + $ExitCode = 1 + + if ($Context) { + + $Context.BuildState.Status = "Failed" + + } + + throw + + } + finally { + + if ($Context) { + + $Context = Complete-Build ` + -Context $Context ` + -ExitCode $ExitCode + + } + + } + + return $Context + +} + +Export-ModuleMember ` + -Function @( + "Initialize-PimsOS" + ) diff --git a/Modules/PostInstall/Bootstrap.ps1 b/Modules/PostInstall/Bootstrap.ps1 index 6cf3fa7..e875d6e 100644 --- a/Modules/PostInstall/Bootstrap.ps1 +++ b/Modules/PostInstall/Bootstrap.ps1 @@ -1,8 +1,8 @@ -# ========================================== +# ========================================== # Module : PostInstall Bootstrap # Projet : PimsOS Builder -# Version : 1.1.0 -# Compatible : PowerShell 7+ +# Version : 1.2.0 +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest @@ -58,16 +58,31 @@ function Start-PimsOSPostInstall { -Path $RuntimePath ` -ChildPath "UI.ps1" + $DriverCheckPath = Join-Path ` + -Path $RuntimePath ` + -ChildPath "DriverCheck.ps1" + $PostInstallPath = Join-Path ` -Path $RuntimePath ` -ChildPath "PostInstall.ps1" + $FinalizePath = Join-Path ` + -Path $RuntimePath ` + -ChildPath "Finalize.ps1" + + $ChocolateyPath = Join-Path ` + -Path $RuntimePath ` + -ChildPath "Chocolatey.ps1" + foreach ($Path in @( $LoggerPath, $StatePath, $NetworkPath, $UIPath, - $PostInstallPath + $DriverCheckPath, + $ChocolateyPath, + $PostInstallPath, + $FinalizePath )) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { @@ -97,14 +112,49 @@ function Start-PimsOSPostInstall { . $StatePath . $NetworkPath . $UIPath + . $DriverCheckPath + . $ChocolateyPath . $PostInstallPath + . $FinalizePath try { - return Invoke-PostInstall ` + $State = Invoke-PostInstall ` -WaitForNetwork:$WaitForNetwork ` -NetworkTimeoutMinutes $NetworkTimeoutMinutes + Write-Log ` + "Vérification finale du PostInstall." ` + INFO + + $Finalization = Complete-PimsOSPostInstall ` + -State $State ` + -RuntimePath $RuntimePath + + $State = Save-PostInstallState ` + -State $Finalization.State + + Write-Log ` + "Vérification finale du PostInstall réussie." ` + SUCCESS + + if ($Finalization.Cleanup.Scheduled) { + + Write-Log ` + ("Nettoyage Bootstrap programmé dans {0} seconde(s)." -f $Finalization.Cleanup.DelaySeconds) ` + SUCCESS + + } + else { + + Write-Log ` + "Le nettoyage Bootstrap n'a pas pu être programmé. L'état, le journal et le cache Chocolatey sont conservés." ` + WARNING + + } + + return $State + } catch { diff --git a/Modules/PostInstall/DeploymentValidation.ps1 b/Modules/PostInstall/DeploymentValidation.ps1 index fe4fe3c..cf8284f 100644 --- a/Modules/PostInstall/DeploymentValidation.ps1 +++ b/Modules/PostInstall/DeploymentValidation.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Module : PostInstall DeploymentValidation # Projet : PimsOS Builder # Version : 1.0.0 @@ -28,6 +28,8 @@ function Test-PostInstallDeployment { "DriverCheck.ps1" "PostInstall.ps1" "State.ps1" + "Chocolatey.ps1" + "Finalize.ps1" ) $MissingFiles = @() @@ -56,6 +58,9 @@ function Test-PostInstallDeployment { $FirstLogonCommands = $false $BootstrapReferenced = $false $RunOnceReferenced = $false + $BootstrapLoadsDriverCheck = $false + $BootstrapLoadsChocolatey = $false + $BootstrapLoadsFinalize = $false if ($UnattendExists) { @@ -129,13 +134,38 @@ function Test-PostInstallDeployment { } + $BootstrapPath = Join-Path -Path $PostInstallPath -ChildPath "Bootstrap.ps1" + + if (Test-Path -LiteralPath $BootstrapPath -PathType Leaf) { + + try { + + $BootstrapText = Get-Content -LiteralPath $BootstrapPath -Raw -ErrorAction Stop + $BootstrapLoadsDriverCheck = $BootstrapText -match '(?i)DriverCheck\.ps1' + $BootstrapLoadsChocolatey = $BootstrapText -match '(?i)Chocolatey\.ps1' + $BootstrapLoadsFinalize = $BootstrapText -match '(?i)Finalize\.ps1' + + } + catch { + + $BootstrapLoadsDriverCheck = $false + $BootstrapLoadsChocolatey = $false + $BootstrapLoadsFinalize = $false + + } + + } + $Success = ( $MissingFiles.Count -eq 0 -and $UnattendExists -and $XmlValid -and $FirstLogonCommands -and $BootstrapReferenced -and - -not $RunOnceReferenced + -not $RunOnceReferenced -and + $BootstrapLoadsDriverCheck -and + $BootstrapLoadsChocolatey -and + $BootstrapLoadsFinalize ) return [pscustomobject]@{ @@ -146,7 +176,10 @@ function Test-PostInstallDeployment { XmlValid = $XmlValid FirstLogonCommands = $FirstLogonCommands BootstrapReferenced = $BootstrapReferenced - RunOnceReferenced = $RunOnceReferenced + RunOnceReferenced = $RunOnceReferenced + BootstrapLoadsDriverCheck = $BootstrapLoadsDriverCheck + BootstrapLoadsChocolatey = $BootstrapLoadsChocolatey + BootstrapLoadsFinalize = $BootstrapLoadsFinalize } diff --git a/Modules/PostInstall/DriverCheck.ps1 b/Modules/PostInstall/DriverCheck.ps1 index 0e4844e..8f5195b 100644 --- a/Modules/PostInstall/DriverCheck.ps1 +++ b/Modules/PostInstall/DriverCheck.ps1 @@ -2,7 +2,7 @@ # Module : PostInstall DriverCheck # Projet : PimsOS Builder # Version : 1.0.0 -# Compatible : PowerShell 7+ +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest diff --git a/Modules/PostInstall/Finalize.ps1 b/Modules/PostInstall/Finalize.ps1 new file mode 100644 index 0000000..18ca329 --- /dev/null +++ b/Modules/PostInstall/Finalize.ps1 @@ -0,0 +1,356 @@ +# ========================================== +# Module : PostInstall Finalize +# Projet : PimsOS Builder +# Version : 1.0.0 +# Compatible : PowerShell 5.1+ +# ========================================== + +Set-StrictMode -Version Latest + +# -------------------------------------------------- +# Vérifie que le PostInstall est réellement terminé +# -------------------------------------------------- + +function Test-PimsOSPostInstallCompletion { + + [CmdletBinding()] + param( + + [Parameter(Mandatory)] + [psobject]$State + + ) + + if ($null -eq $State) { + + throw "L'état PostInstall est null." + + } + + $RequiredTasks = @( + "Initialize", + "Network", + "DriverCheck", + "Chocolatey", + "Applications", + "MicrosoftStore", + "Configuration", + "Cleanup" + ) + + $CompletedTasks = @() + + if ($State.PSObject.Properties.Name -contains "CompletedTasks") { + + $CompletedTasks = @($State.CompletedTasks) + + } + + $Status = if ($State.PSObject.Properties.Name -contains "Status") { + [string]$State.Status + } else { + "" + } + + $Completed = if ($State.PSObject.Properties.Name -contains "Completed") { + [bool]$State.Completed + } else { + $false + } + + $Failed = if ($State.PSObject.Properties.Name -contains "Failed") { + [bool]$State.Failed + } else { + $true + } + + $CurrentPhase = if ($State.PSObject.Properties.Name -contains "CurrentPhase") { + $State.CurrentPhase + } else { + $null + } + + $MissingTasks = @( + $RequiredTasks | Where-Object { + $CompletedTasks -notcontains $_ + } + ) + + $Success = ( + $Status -eq "Completed" -and + $Completed -eq $true -and + $Failed -eq $false -and + ($null -eq $CurrentPhase -or + [string]::IsNullOrWhiteSpace([string]$CurrentPhase)) -and + $MissingTasks.Count -eq 0 + ) + + return [PSCustomObject]@{ + + Success = $Success + + Status = $Status + + Completed = $Completed + + Failed = $Failed + + CurrentPhase = $CurrentPhase + + MissingTasks = @($MissingTasks) + + } + +} + +# -------------------------------------------------- +# Programme le nettoyage après la fin du Bootstrap +# -------------------------------------------------- + +function Invoke-PimsOSPostInstallCleanup { + + [CmdletBinding()] + param( + + [Parameter(Mandatory)] + [string]$RuntimePath, + + [Parameter()] + [string]$UnattendPath = "C:\Windows\Panther\unattend.xml", + + [Parameter()] + [int]$DelaySeconds = 10 + + ) + + if ([string]::IsNullOrWhiteSpace($RuntimePath)) { + + throw "Le chemin du runtime PostInstall est vide." + + } + + if (-not (Test-Path -LiteralPath $RuntimePath -PathType Container)) { + + throw "Le runtime PostInstall est introuvable : $RuntimePath" + + } + + if ($DelaySeconds -lt 1) { + + throw "Le délai de nettoyage doit être supérieur ou égal à 1 seconde." + + } + + $CleanupFiles = @( + "Bootstrap.ps1", + "Finalize.ps1", + "Logger.ps1", + "Network.ps1", + "UI.ps1", + "DriverCheck.ps1", + "Chocolatey.ps1", + "PostInstall.ps1", + "State.ps1" + ) + + $PathsToRemove = @() + + foreach ($FileName in $CleanupFiles) { + + $PathsToRemove += Join-Path -Path $RuntimePath -ChildPath $FileName + + } + + if (-not [string]::IsNullOrWhiteSpace($UnattendPath)) { + + $PathsToRemove += $UnattendPath + + } + + $ExistingPaths = @( + $PathsToRemove | Where-Object { + Test-Path -LiteralPath $_ + } + ) + + # -------------------------------------------------- + # Le nettoyage est exécuté dans un processus séparé afin + # de pouvoir supprimer Bootstrap.ps1 après sa sortie. + # Les fichiers d'état, logs et cache Chocolatey sont conservés. + # -------------------------------------------------- + + $PowerShellPath = Join-Path $PSHOME "powershell.exe" + + if (-not (Test-Path -LiteralPath $PowerShellPath -PathType Leaf)) { + + $PowerShellPath = "powershell.exe" + + } + + $PathsJson = @($ExistingPaths) | ConvertTo-Json -Compress + $PathsEncoded = [Convert]::ToBase64String( + [System.Text.Encoding]::Unicode.GetBytes($PathsJson) + ) + + $CleanupCommand = @" +`$ErrorActionPreference = 'SilentlyContinue' +Start-Sleep -Seconds $DelaySeconds +`$PathsJson = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('$PathsEncoded')) +`$Paths = @(`$PathsJson | ConvertFrom-Json) +foreach (`$Path in `$Paths) { + if (Test-Path -LiteralPath `$Path) { + Remove-Item -LiteralPath `$Path -Force -ErrorAction SilentlyContinue + } +} +"@ + + $EncodedCommand = [Convert]::ToBase64String( + [System.Text.Encoding]::Unicode.GetBytes($CleanupCommand) + ) + + $Process = Start-Process ` + -FilePath $PowerShellPath ` + -ArgumentList @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-EncodedCommand", + $EncodedCommand + ) ` + -WindowStyle Hidden ` + -PassThru ` + -ErrorAction Stop + + return [PSCustomObject]@{ + + Scheduled = $true + + ProcessId = $Process.Id + + DelaySeconds = $DelaySeconds + + RemovedItems = @($ExistingPaths) + + PreservedItems = @( + Join-Path -Path $RuntimePath -ChildPath "state.json" + Join-Path -Path $RuntimePath -ChildPath "PostInstall.log" + Join-Path -Path $RuntimePath -ChildPath "Chocolatey" + ) + + } + +} + +# -------------------------------------------------- +# Finalise le cycle PostInstall +# -------------------------------------------------- + +function Complete-PimsOSPostInstall { + + [CmdletBinding()] + param( + + [Parameter(Mandatory)] + [psobject]$State, + + [Parameter(Mandatory)] + [string]$RuntimePath, + + [Parameter()] + [string]$UnattendPath = "C:\Windows\Panther\unattend.xml", + + [Parameter()] + [int]$DelaySeconds = 10 + + ) + + $Verification = Test-PimsOSPostInstallCompletion -State $State + + if (-not $Verification.Success) { + + throw ( + "Vérification finale du PostInstall échouée. Tâches manquantes : {0}. Statut : {1}. Phase : {2}." -f + ($Verification.MissingTasks -join ", "), + $Verification.Status, + $Verification.CurrentPhase + ) + + } + + if ( + $State.PSObject.Properties.Name -notcontains "Verification" -or + $null -eq $State.Verification + ) { + + $State | Add-Member -MemberType NoteProperty -Name Verification -Value ([PSCustomObject]@{}) -Force + + } + + $State.Verification.Verified = $true + $State.Verification.VerifiedAt = [DateTime]::UtcNow + $State.Verification.MissingTasks = @() + + if ( + $State.PSObject.Properties.Name -notcontains "Cleanup" -or + $null -eq $State.Cleanup + ) { + + $State | Add-Member -MemberType NoteProperty -Name Cleanup -Value ([PSCustomObject]@{}) -Force + + } + + try { + + $Cleanup = Invoke-PimsOSPostInstallCleanup ` + -RuntimePath $RuntimePath ` + -UnattendPath $UnattendPath ` + -DelaySeconds $DelaySeconds + + $State.Cleanup.Status = "Scheduled" + $State.Cleanup.Scheduled = $true + $State.Cleanup.ScheduledAt = [DateTime]::UtcNow + $State.Cleanup.RemovedItems = @($Cleanup.RemovedItems) + $State.Cleanup.PreservedItems = @($Cleanup.PreservedItems) + $State.Cleanup.Errors = @() + + } + catch { + + $Cleanup = [PSCustomObject]@{ + + Scheduled = $false + + ProcessId = $null + + DelaySeconds = $DelaySeconds + + RemovedItems = @() + + PreservedItems = @( + Join-Path -Path $RuntimePath -ChildPath "state.json" + Join-Path -Path $RuntimePath -ChildPath "PostInstall.log" + Join-Path -Path $RuntimePath -ChildPath "Chocolatey" + ) + + } + + $State.Cleanup.Status = "Failed" + $State.Cleanup.Scheduled = $false + $State.Cleanup.ScheduledAt = [DateTime]::UtcNow + $State.Cleanup.RemovedItems = @() + $State.Cleanup.PreservedItems = @($Cleanup.PreservedItems) + $State.Cleanup.Errors = @($_.Exception.Message) + + } + + return [PSCustomObject]@{ + + State = $State + + Verification = $Verification + + Cleanup = $Cleanup + + } + +} diff --git a/Modules/PostInstall/Installer.ps1 b/Modules/PostInstall/Installer.ps1 index b9998f3..2451b8d 100644 --- a/Modules/PostInstall/Installer.ps1 +++ b/Modules/PostInstall/Installer.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Module : PostInstall Installer # Projet : PimsOS Builder # Version : 1.0.0 @@ -21,7 +21,16 @@ function Install-PimsOSPostInstallRuntime { [string]$MountPath, [Parameter(Mandatory)] - [string]$SourcePath + [string]$SourcePath, + + [Parameter()] + [string]$ChocolateyProviderPath, + + [Parameter()] + [string]$ChocolateyCatalogPath, + + [Parameter()] + [string]$ChocolateyCachePath ) @@ -69,7 +78,9 @@ function Install-PimsOSPostInstallRuntime { "Network.ps1" "UI.ps1" "DriverCheck.ps1" + "Chocolatey.ps1" "PostInstall.ps1" + "Finalize.ps1" "State.ps1" ) @@ -79,6 +90,14 @@ function Install-PimsOSPostInstallRuntime { $SourceFile = $LoggerSourcePath + } + elseif ($FileName -eq "Chocolatey.ps1") { + + if ([string]::IsNullOrWhiteSpace($ChocolateyProviderPath)) { + throw "Le chemin du provider Chocolatey est requis pour le runtime PostInstall." + } + $SourceFile = $ChocolateyProviderPath + } else { @@ -128,6 +147,14 @@ function Install-PimsOSPostInstallRuntime { $SourceFile = $LoggerSourcePath + } + elseif ($FileName -eq "Chocolatey.ps1") { + + if ([string]::IsNullOrWhiteSpace($ChocolateyProviderPath)) { + throw "Le chemin du provider Chocolatey est requis pour le runtime PostInstall." + } + $SourceFile = $ChocolateyProviderPath + } else { @@ -170,6 +197,32 @@ function Install-PimsOSPostInstallRuntime { } + # -------------------------------------------------- + # Catalogue Chocolatey + cache offline + # -------------------------------------------------- + + $RuntimeChocolateyPath = Join-Path $DestinationPath "Chocolatey" + if (-not (Test-Path -LiteralPath $RuntimeChocolateyPath -PathType Container)) { + New-Item -ItemType Directory -Path $RuntimeChocolateyPath -Force -ErrorAction Stop | Out-Null + } + + if (-not [string]::IsNullOrWhiteSpace($ChocolateyCatalogPath)) { + if (-not (Test-Path -LiteralPath $ChocolateyCatalogPath -PathType Leaf)) { + throw "Le catalogue Chocolatey est introuvable : $ChocolateyCatalogPath" + } + Copy-Item -LiteralPath $ChocolateyCatalogPath -Destination (Join-Path $RuntimeChocolateyPath "Chocolatey.json") -Force -ErrorAction Stop + } + + if (-not [string]::IsNullOrWhiteSpace($ChocolateyCachePath)) { + if (-not (Test-Path -LiteralPath $ChocolateyCachePath -PathType Container)) { + throw "Le cache Chocolatey est introuvable : $ChocolateyCachePath" + } + $RuntimeCachePath = Join-Path $RuntimeChocolateyPath "Cache" + New-Item -ItemType Directory -Path $RuntimeCachePath -Force -ErrorAction Stop | Out-Null + Get-ChildItem -LiteralPath $ChocolateyCachePath -Filter '*.nupkg' -File -ErrorAction Stop | + Copy-Item -Destination $RuntimeCachePath -Force -ErrorAction Stop + } + return [PSCustomObject]@{ ObjectType = "PimsOSPostInstallRuntime" @@ -184,6 +237,10 @@ function Install-PimsOSPostInstallRuntime { Files = $RequiredFiles + ChocolateyCatalogPath = if ([string]::IsNullOrWhiteSpace($ChocolateyCatalogPath)) { $null } else { Join-Path $RuntimeChocolateyPath "Chocolatey.json" } + + ChocolateyCachePath = if ([string]::IsNullOrWhiteSpace($ChocolateyCachePath)) { $null } else { Join-Path $RuntimeChocolateyPath "Cache" } + Installed = $true } diff --git a/Modules/PostInstall/Network.ps1 b/Modules/PostInstall/Network.ps1 index 2343f20..90f868c 100644 --- a/Modules/PostInstall/Network.ps1 +++ b/Modules/PostInstall/Network.ps1 @@ -2,7 +2,7 @@ # Module : PostInstall Network # Projet : PimsOS Builder # Version : 1.0.0 -# Compatible : PowerShell 7+ +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest diff --git a/Modules/PostInstall/PostInstall.ps1 b/Modules/PostInstall/PostInstall.ps1 index 7a42eb6..a9bf4cc 100644 --- a/Modules/PostInstall/PostInstall.ps1 +++ b/Modules/PostInstall/PostInstall.ps1 @@ -2,7 +2,7 @@ # Module : PostInstall # Projet : PimsOS Builder # Version : 1.2.0 -# Compatible : PowerShell 7+ +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest @@ -464,10 +464,48 @@ function Invoke-PostInstall { INFO # -------------------------------------------------- - # L'installation de Chocolatey et l'utilisation - # du cache local seront ajoutées ici. + # Bootstrap Chocolatey depuis le cache embarqué # -------------------------------------------------- + $RuntimeRoot = Split-Path -Parent $State.StatePath + $ChocolateyRoot = Join-Path $RuntimeRoot "Chocolatey" + $ChocolateyCatalogPath = Join-Path $ChocolateyRoot "Chocolatey.json" + $ChocolateyCachePath = Join-Path $ChocolateyRoot "Cache" + $ChocolateyBootstrapPackage = Get-ChildItem -LiteralPath $ChocolateyCachePath -File -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'chocolatey.nupkg' -or $_.Name -like 'chocolatey.*.nupkg' } | + Sort-Object @{Expression={ if ($_.Name -eq 'chocolatey.nupkg') { 0 } else { 1 } }}, Name | + Select-Object -First 1 + + if ($null -eq $ChocolateyBootstrapPackage) { + throw "Le package bootstrap Chocolatey est absent du cache runtime : $ChocolateyCachePath" + } + + Install-ChocolateyBootstrap -BootstrapPackagePath $ChocolateyBootstrapPackage.FullName | Out-Null + + if (-not (Test-Path -LiteralPath $ChocolateyCatalogPath -PathType Leaf)) { + throw "Le catalogue Chocolatey runtime est introuvable : $ChocolateyCatalogPath" + } + + # Les packages Offline utilisent exclusivement le cache embarqué. + # Les packages Online utilisent Community et ne sont donc téléchargés + # qu'à ce stade, après le contrôle des pilotes. + $CatalogContext = [pscustomobject]@{ + Workspace = [pscustomobject]@{ PackagesChocolatey = $ChocolateyCachePath } + } + + $ChocolateyResults = @(Invoke-ChocolateyCatalog ` + -Context $CatalogContext ` + -CatalogPath $ChocolateyCatalogPath ` + -RuntimeCachePath $ChocolateyCachePath) + + $State | Add-Member -MemberType NoteProperty -Name ChocolateyResults -Value $ChocolateyResults -Force + + $ChocolateyFailures = @($ChocolateyResults | Where-Object { + $_.Status -eq 'Failed' + }) + + $State | Add-Member -MemberType NoteProperty -Name ChocolateyFailures -Value $ChocolateyFailures -Force + $State = Complete-PostInstallTask ` -State $State ` -TaskName "Chocolatey" @@ -475,9 +513,17 @@ function Invoke-PostInstall { $State = Save-PostInstallState ` -State $State - Write-Log ` - "Phase Chocolatey terminée." ` - SUCCESS + if ($ChocolateyFailures.Count -gt 0) { + Write-Log ( + "Phase Chocolatey terminée avec {0} échec(s) non bloquant(s)." -f + $ChocolateyFailures.Count + ) WARNING + } + else { + Write-Log ` + "Phase Chocolatey terminée." ` + SUCCESS + } } else { diff --git a/Modules/PostInstall/State.ps1 b/Modules/PostInstall/State.ps1 index 50a80a7..f6c86b0 100644 --- a/Modules/PostInstall/State.ps1 +++ b/Modules/PostInstall/State.ps1 @@ -1,8 +1,8 @@ -# ========================================== +# ========================================== # Module : PostInstall State # Projet : PimsOS Builder -# Version : 1.0.0 -# Compatible : PowerShell 7+ +# Version : 1.1.0 +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest @@ -51,6 +51,32 @@ function New-PostInstallState { PendingTasks = @() + Verification = [PSCustomObject]@{ + + Verified = $false + + VerifiedAt = $null + + MissingTasks = @() + + } + + Cleanup = [PSCustomObject]@{ + + Status = "Pending" + + Scheduled = $false + + ScheduledAt = $null + + RemovedItems = @() + + PreservedItems = @() + + Errors = @() + + } + } } diff --git a/Modules/PostInstall/UI.ps1 b/Modules/PostInstall/UI.ps1 index c40ffd7..27552aa 100644 --- a/Modules/PostInstall/UI.ps1 +++ b/Modules/PostInstall/UI.ps1 @@ -2,7 +2,7 @@ # Module : PostInstall UI # Projet : PimsOS Builder # Version : 1.1.0 -# Compatible : PowerShell 7+ +# Compatible : PowerShell 5.1+ # ========================================== Set-StrictMode -Version Latest diff --git a/Modules/UI/Wizard.ps1 b/Modules/UI/Wizard.ps1 index b432c46..7b30a91 100644 --- a/Modules/UI/Wizard.ps1 +++ b/Modules/UI/Wizard.ps1 @@ -72,8 +72,9 @@ function Show-PimsOSBuildWizard { Write-Host "[2] Configurer les Tweaks" Write-Host "[3] Options du Build" Write-Host "[4] Configuration des drivers" - Write-Host "[5] Afficher le résumé" - Write-Host "[6] Valider et continuer" -ForegroundColor Green + Write-Host "[5] Gérer les packages Chocolatey" + Write-Host "[6] Afficher le résumé" + Write-Host "[7] Valider et continuer" -ForegroundColor Green Write-Host "[0] Annuler" -ForegroundColor Red Write-Host "" @@ -107,12 +108,18 @@ function Show-PimsOSBuildWizard { "5" { - Show-PimsOSBuildSummary ` + Show-PimsOSChocolateyPackageMenu ` -Context $Context } "6" { + Show-PimsOSBuildSummary ` + -Context $Context + } + + "7" { + Write-Log ` "Configuration du Build validée." ` SUCCESS @@ -1038,6 +1045,95 @@ function Show-PimsOSDriverMenu { } + +function Show-PimsOSChocolateyPackageMenu { + + [CmdletBinding()] + param([Parameter(Mandatory)][psobject]$Context) + + while ($true) { + Clear-PimsOSScreen + $Catalog = Read-ChocolateyCatalog -Context $Context + $Packages = @($Catalog.Packages | Sort-Object Id) + + Write-Host "" + Write-Host "==================================================" -ForegroundColor Cyan + Write-Host " Packages Chocolatey" -ForegroundColor Cyan + Write-Host "==================================================" -ForegroundColor Cyan + Write-Host "" + Write-Host ("Packages : {0}" -f $Packages.Count) -ForegroundColor Yellow + Write-Host "" + Write-Host "[1] Afficher la liste" + Write-Host "[2] Ajouter un package" + Write-Host "[3] Supprimer un package" + Write-Host "[0] Retour" + Write-Host "" + + $Choice = Read-Host "Votre choix" + switch ($Choice) { + "1" { + Clear-PimsOSScreen + Write-Host "" + Write-Host "Catalogue Chocolatey" -ForegroundColor Cyan + Write-Host "-------------------" + foreach ($Package in $Packages) { + $Version = if ([string]::IsNullOrWhiteSpace([string]$Package.Version)) { "auto" } else { [string]$Package.Version } + $State = if ([bool]$Package.Enabled) { "ON" } else { "OFF" } + Write-Host ("{0,-30} {1,-8} {2,-8} {3}" -f $Package.Id, $Package.Mode, $State, $Version) + } + Write-Host "" + $null = Read-Host "Appuyez sur Entrée" + } + "2" { + Clear-PimsOSScreen + Write-Host "" + Write-Host "Ajouter un package Chocolatey" -ForegroundColor Cyan + Write-Host "" + $Id = Read-Host "ID Chocolatey" + if ([string]::IsNullOrWhiteSpace($Id)) { continue } + $Version = Read-Host "Version (Entrée = automatique)" + $Category = Read-Host "Catégorie (Entrée = Other)" + $Mode = Read-Host "Mode [Online/Offline] (Entrée = Online)" + if ([string]::IsNullOrWhiteSpace($Mode)) { $Mode = 'Online' } + if ($Mode -notin @('Online','Offline')) { + Write-Host "Mode invalide : utilisez Online ou Offline." -ForegroundColor Red + $null = Read-Host "Appuyez sur Entrée" + continue + } + try { + Add-ChocolateyCatalogPackage -Context $Context -Id $Id -Version $Version -Mode $Mode -Category $Category | Out-Null + Write-Host "Package ajouté. Le Build préparera son cache uniquement s'il est Offline." -ForegroundColor Green + } + catch { + Write-Host $_.Exception.Message -ForegroundColor Red + } + $null = Read-Host "Appuyez sur Entrée" + } + "3" { + Clear-PimsOSScreen + Write-Host "" + Write-Host "Supprimer un package Chocolatey" -ForegroundColor Cyan + Write-Host "" + $Id = Read-Host "ID Chocolatey à supprimer" + if ([string]::IsNullOrWhiteSpace($Id)) { continue } + try { + Remove-ChocolateyCatalogPackage -Context $Context -Id $Id | Out-Null + Write-Host "Package supprimé du catalogue." -ForegroundColor Green + } + catch { + Write-Host $_.Exception.Message -ForegroundColor Red + } + $null = Read-Host "Appuyez sur Entrée" + } + "0" { return } + default { + Write-Host "Choix invalide." -ForegroundColor Red + $null = Read-Host "Appuyez sur Entrée" + } + } + } +} + function Show-PimsOSBuildSummary { [CmdletBinding()] diff --git a/README-Addition-SessionChecklist.md b/README-Addition-SessionChecklist.md new file mode 100644 index 0000000..83a49b6 --- /dev/null +++ b/README-Addition-SessionChecklist.md @@ -0,0 +1,20 @@ +## Reprise de session et diagnostic + +Avant toute nouvelle séance de travail, suivre **[`Documentation/SessionChecklist.md`](Documentation/SessionChecklist.md)**. + +La procédure impose notamment : + +1. vérifier l'état Git et la synchronisation avec `origin/main` ; +2. lire la documentation et le fichier de reprise avant d'émettre des hypothèses ; +3. vérifier les derniers résultats de tests ; +4. utiliser `Tests/Tools/Invoke-PimsOSDiagnostics.ps1` pour classifier les tests ; +5. ne lancer une validation de Build réelle qu'avec une autorisation explicite ; +6. mettre à jour la passation et la documentation en fin de séance. + +Le diagnostic distingue trois niveaux : + +- **SAFE** — exécution normale autorisée ; +- **BUILD-CAPABLE** — peut déclencher un Build réel, donc réservé à `BuildValidation` ; +- **UNKNOWN** — neutralisation insuffisamment prouvée, donc bloqué par défaut. + +Voir également [`Documentation/ADR/ADR-0014-SafeTestClassification.md`](Documentation/ADR/ADR-0014-SafeTestClassification.md). diff --git a/README.md b/README.md index 0d572d1..8694d5d 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Cette séparation permet de construire un framework : **Version technique : 3.0.0** -**Statut :** 🚧 Développement actif / architecture stabilisée +**Statut :** 🚧 Développement actif / architecture stabilisée ; chaîne Build + VM validée | Domaine | État | |---------|------| @@ -90,14 +90,25 @@ Cette séparation permet de construire un framework : | Image ISO / WIM | ✅ Implémentée | | Tests Pester | ✅ Forte couverture / extension en cours | | Reporting | 🟡 À enrichir | -| Génération ISO | ✅ ISO 3.0.0 générée le 31/08 ; validation de l’artefact restante | -| Providers Chocolatey / Winget | 🟡 À finaliser | +| Génération ISO | ✅ ISO 3.0.0 générée le 02/09 ; Build réel validé | +| Providers Chocolatey / Winget | 🟢 Chocolatey fonctionnel ; Winget à venir | | Converters | ⬜ À implémenter | La version technique 3.0.0 ne constitue pas encore une release finale stable du produit. --- + +## Diagnostic sécurisé + +Avant une campagne Pester, PimsOS recommande l'outil : + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Unit -InventoryOnly -ExplainFailures +``` + +Les tests susceptibles de lancer un Build réel sont séparés des tests `SAFE`. Une validation de Build réel nécessite explicitement `-BuildValidation -AllowBuild`. + # Fonctionnalités actuelles ## Build @@ -124,7 +135,7 @@ Les principales étapes du Build sont actuellement implémentées : - ✔ Nettoyage - ✔ Finalisation du Build -Un Build réel de bout en bout a été exécuté avec succès le 31/08/2026 et une ISO PimsOS 3.0.0 a été générée. La validation fonctionnelle de cette nouvelle ISO reste à effectuer. +Un Build réel de bout en bout a été exécuté avec succès le 02/09/2026. L’ISO `PimsOS_3.0.0_20260902_141928.iso` a ensuite été validée en VM sur le flux FirstBoot/PostInstall/Finalization. --- @@ -651,3 +662,87 @@ Pour le suivi de l'état du projet : ```text Documentation/ProjectStatus.md ``` + +# PimsOS Builder + +Framework PowerShell de construction et personnalisation d'images Windows. + +## Etat + +**Version technique : 3.0.0** + +**Statut :** développement actif / architecture stabilisée. + +Base de référence actuelle : + +```text +Tests : 815 Passed / 0 Failed / 1 Skipped +Référence Build : PimsOS_3.0.0_20260902_141928.iso +Tweaks : 27 chargés avec Actions valides +``` + +## Architecture + +PimsOS utilise un **module PowerShell unique** : + +```text +Modules\PimsOS.psd1 +Modules\PimsOS.psm1 +``` + +Les sous-répertoires de `Modules` sont des composants internes chargés par le +module principal. + +API publique : + +```text +Initialize-PimsOS +``` + +## Composants principaux + +- BuildContext / BuildState +- Workflow / Pipeline +- Configuration / Profils / Tweaks +- ActionRegistry / ActionEngine +- Engines spécialisés +- Managers +- Image ISO / WIM / DISM +- Registry +- Drivers +- PostInstall / FirstBoot +- Wizard +- Reporting + +## Tests + +Campagne officielle : + +```text +Tests\Unit +Tests\Integration +``` + +`Tests\Legacy` est historique et exclu. + +Dernière campagne : + +```text +815 Passed +0 Failed +1 Skipped +798 Total +``` + +## Prochaines étapes + +1. Synchronisation Git du code et de la documentation. +2. Régénération éventuelle de `Tests\testResults.xml`. +3. Validation Rufus / physique. +4. Audit Offline des packages Chocolatey. +5. Winget. +6. Recovery / Security / Reporting. +7. Enrichissement du catalogue. + +Le fichier `PimsOS_Reprise_2026-09-02.txt` constitue le point de reprise +détaillé pour les prochaines sessions. diff --git a/Tests/Integration/BuildPipeline.Tests.ps1 b/Tests/Integration/BuildPipeline.Tests.ps1 index 5f0bbc4..cd82e1f 100644 --- a/Tests/Integration/BuildPipeline.Tests.ps1 +++ b/Tests/Integration/BuildPipeline.Tests.ps1 @@ -88,6 +88,13 @@ Describe "BuildPipeline" { } + Workspace = [pscustomobject]@{ + + Cache = Join-Path $TestDrive "Cache" + PackagesChocolatey = Join-Path $TestDrive "Cache\Chocolatey" + + } + Drivers = [System.Collections.Generic.List[object]]::new() Configuration = @() diff --git a/Tests/Legacy/Classes/MigrationContext.Tests.ps1 b/Tests/Legacy/Classes/MigrationContext.Tests.ps1 deleted file mode 100644 index c196cb5..0000000 --- a/Tests/Legacy/Classes/MigrationContext.Tests.ps1 +++ /dev/null @@ -1,218 +0,0 @@ -# ========================================== -# MigrationContext.Tests.ps1 -# ========================================== - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - Import-Module ` - (Join-Path $ProjectRoot "Tools\Migration\Modules\Common.psm1") ` - -Force - -} - -Describe "New-MigrationContext" { - - Context "Création" { - - It "Peut être créé" { - - $Context = New-MigrationContext - - $Context | - Should -Not -BeNullOrEmpty - - } - - It "Retourne un objet MigrationContext" { - - $Context = New-MigrationContext - - $Context.ObjectType | - Should -Be "MigrationContext" - - } - - } - - Context "Initialisation" { - - BeforeEach { - - $Context = New-MigrationContext - - } - - It "Initialise Results" { - - ($null -eq $Context.Results) | - Should -BeFalse - - $Context.Results.Count | - Should -Be 0 - - $Context.Results.GetType() | - Should -Be ([System.Collections.Generic.List[object]]) - - } - - It "Initialise AnalyzeOnly à False" { - - $Context.AnalyzeOnly | - Should -BeFalse - - } - - It "Initialise ExecuteAll à False" { - - $Context.ExecuteAll | - Should -BeFalse - - } - - It "Initialise ProjectName vide" { - - $Context.ProjectName | - Should -Be "" - - } - - It "Initialise ProjectRoot vide" { - - $Context.ProjectRoot | - Should -Be "" - - } - - It "Initialise Rule vide" { - - $Context.Rule | - Should -Be "" - - } - - } - - Context "Propriétés" { - - BeforeEach { - - $Context = New-MigrationContext - - } - - It "Peut stocker ProjectName" { - - $Context.ProjectName = "PimsOS" - - $Context.ProjectName | - Should -Be "PimsOS" - - } - - It "Peut stocker ProjectRoot" { - - $Context.ProjectRoot = "C:\Projets\PimsOS" - - $Context.ProjectRoot | - Should -Be "C:\Projets\PimsOS" - - } - - It "Peut stocker ToolsPath" { - - $Context.ToolsPath = "C:\Projets\PimsOS\Tools" - - $Context.ToolsPath | - Should -Be "C:\Projets\PimsOS\Tools" - - } - - It "Peut stocker MigrationPath" { - - $Context.MigrationPath = "C:\Projets\PimsOS\Tools\Migration" - - $Context.MigrationPath | - Should -Be "C:\Projets\PimsOS\Tools\Migration" - - } - - It "Peut stocker ModulesPath" { - - $Context.ModulesPath = "C:\Projets\PimsOS\Tools\Migration\Modules" - - $Context.ModulesPath | - Should -Be "C:\Projets\PimsOS\Tools\Migration\Modules" - - } - - It "Peut stocker TestsPath" { - - $Context.TestsPath = "C:\Projets\PimsOS\Tests" - - $Context.TestsPath | - Should -Be "C:\Projets\PimsOS\Tests" - - } - - It "Peut stocker Rule" { - - $Context.Rule = "Logger" - - $Context.Rule | - Should -Be "Logger" - - } - - It "Peut modifier AnalyzeOnly" { - - $Context.AnalyzeOnly = $true - - $Context.AnalyzeOnly | - Should -BeTrue - - } - - It "Peut modifier ExecuteAll" { - - $Context.ExecuteAll = $true - - $Context.ExecuteAll | - Should -BeTrue - - } - - } - - Context "Résultats" { - - BeforeEach { - - $Context = New-MigrationContext - - } - - It "Peut ajouter un résultat" { - - $Context.Results.Add("Test") - - $Context.Results.Count | - Should -Be 1 - - } - - It "Conserve le résultat ajouté" { - - $Context.Results.Add("Premier") - - $Context.Results[0] | - Should -Be "Premier" - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Classes/MigrationResult.Tests.ps1 b/Tests/Legacy/Classes/MigrationResult.Tests.ps1 deleted file mode 100644 index b89e146..0000000 --- a/Tests/Legacy/Classes/MigrationResult.Tests.ps1 +++ /dev/null @@ -1,154 +0,0 @@ -# ========================================== -# MigrationResult.Tests.ps1 -# ========================================== - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - Import-Module ` - (Join-Path $ProjectRoot "Tools\Migration\Modules\Common.psm1") ` - -Force - -} - -Describe "New-MigrationResult" { - - Context "Création" { - - It "Peut être créé" { - - $Result = New-MigrationResult - - $Result | - Should -Not -BeNullOrEmpty - - } - - It "Retourne un objet MigrationResult" { - - $Result = New-MigrationResult - - $Result.ObjectType | - Should -Be "MigrationResult" - - } - - } - - Context "Initialisation" { - - BeforeEach { - - $Result = New-MigrationResult - - } - - It "Initialise Modified à False" { - - $Result.Modified | - Should -BeFalse - - } - - It "Initialise File vide" { - - $Result.File | - Should -BeNullOrEmpty - - } - - It "Initialise Rule vide" { - - $Result.Rule | - Should -BeNullOrEmpty - - } - - It "Initialise Message vide" { - - $Result.Message | - Should -Be "" - - } - - } - - Context "Propriétés" { - - BeforeEach { - - $Result = New-MigrationResult - - } - - It "Peut stocker Rule" { - - $Result.Rule = "Logger" - - $Result.Rule | - Should -Be "Logger" - - } - - It "Peut stocker File" { - - $Result.File = "C:\Projets\PimsOS\Modules\Logger.psm1" - - $Result.File | - Should -Be "C:\Projets\PimsOS\Modules\Logger.psm1" - - } - - It "Peut modifier Modified" { - - $Result.Modified = $true - - $Result.Modified | - Should -BeTrue - - } - - It "Peut modifier Message" { - - $Result.Message = "Migration réussie" - - $Result.Message | - Should -Be "Migration réussie" - - } - - } - - Context "Constructeur avec paramètres" { - - It "Crée un résultat complet" { - - $Result = New-MigrationResult ` - -File "Test.ps1" ` - -Rule "Rule001" ` - -Modified $true ` - -Message "OK" - - $Result.ObjectType | - Should -Be "MigrationResult" - - $Result.File | - Should -Be "Test.ps1" - - $Result.Rule | - Should -Be "Rule001" - - $Result.Modified | - Should -BeTrue - - $Result.Message | - Should -Be "OK" - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Classes/MigrationRule.Tests.ps1 b/Tests/Legacy/Classes/MigrationRule.Tests.ps1 deleted file mode 100644 index cee0852..0000000 --- a/Tests/Legacy/Classes/MigrationRule.Tests.ps1 +++ /dev/null @@ -1,147 +0,0 @@ -# ========================================== -# MigrationRule.Tests.ps1 -# ========================================== - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - Import-Module ` - (Join-Path $ProjectRoot "Tools\Migration\Modules\Common.psm1") ` - -Force - -} - -Describe "New-MigrationRule" { - - Context "Création" { - - It "Peut être créé" { - - $Rule = New-MigrationRule - - $Rule | - Should -Not -BeNullOrEmpty - - } - - It "Retourne un objet MigrationRule" { - - $Rule = New-MigrationRule - - $Rule.ObjectType | - Should -Be "MigrationRule" - - } - - } - - Context "Initialisation" { - - BeforeEach { - - $Rule = New-MigrationRule - - } - - It "Initialise Name vide" { - - $Rule.Name | - Should -Be "" - - } - - It "Initialise Description vide" { - - $Rule.Description | - Should -Be "" - - } - - It "Initialise Enabled à True" { - - $Rule.Enabled | - Should -BeTrue - - } - - It "Initialise Priority à 100" { - - $Rule.Priority | - Should -Be 100 - - } - - It "Initialise Script à Null" { - - $Rule.Script | - Should -BeNullOrEmpty - - } - - } - - Context "Propriétés" { - - BeforeEach { - - $Rule = New-MigrationRule - - } - - It "Peut stocker Name" { - - $Rule.Name = "Logger" - - $Rule.Name | - Should -Be "Logger" - - } - - It "Peut stocker Description" { - - $Rule.Description = "Migration du module Logger" - - $Rule.Description | - Should -Be "Migration du module Logger" - - } - - It "Peut modifier Enabled" { - - $Rule.Enabled = $false - - $Rule.Enabled | - Should -BeFalse - - } - - It "Peut modifier Priority" { - - $Rule.Priority = 10 - - $Rule.Priority | - Should -Be 10 - - } - - It "Peut stocker Script" { - - $Script = { - - "Hello" - - } - - $Rule.Script = $Script - - $Rule.Script | - Should -Be $Script - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Integration/MigrationPipeline.Tests.ps1 b/Tests/Legacy/Integration/MigrationPipeline.Tests.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/Legacy/Modules/Ast.Tests.ps1 b/Tests/Legacy/Modules/Ast.Tests.ps1 deleted file mode 100644 index 2ebb922..0000000 --- a/Tests/Legacy/Modules/Ast.Tests.ps1 +++ /dev/null @@ -1,813 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Ast. - -.DESCRIPTION - Vérifie les fonctions d'analyse AST du framework de migration. - -.NOTES - - Projet : PimsOS - Module : Ast.Tests - Framework : Pester 5 - -#> - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $ModuleRoot = Join-Path $ProjectRoot "Tools\Migration\Modules" - - Import-Module ` - (Join-Path $ModuleRoot "Common.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Scanner.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Ast.psm1") ` - -Force - -} - -Describe "Module Ast" { - - BeforeEach { - - $TestFile = Join-Path $TestDrive "Test.ps1" - - @' -param( - [string]$Name -) - -class DemoClass { - - [string] GetName() { - - return "Demo" - - } - -} - -function Test-Function { - - param( - [string]$Value - ) - - $Variable = "Hello" - - Write-Host $Variable - - Get-Date - -} -'@ | Set-Content ` - -LiteralPath $TestFile ` - -Encoding UTF8 - - $Script = Get-ScriptAst ` - -File (Get-Item $TestFile) - - } - - - Context "Chargement du module" { - - It "Le module est chargé" { - - Get-Module Ast | - Should -Not -BeNullOrEmpty - - } - - } - - Context "Get-ScriptAst" { - - - It "Retourne une hashtable" { - - $Script | - Should -BeOfType ([hashtable]) - - } - - It "Contient les clés attendues" { - - $Script.Keys | - Should -Contain "File" - - $Script.Keys | - Should -Contain "Ast" - - $Script.Keys | - Should -Contain "Tokens" - - $Script.Keys | - Should -Contain "Errors" - - } - - It "Retourne un AST valide" { - - $Script.Ast | - Should -Not -BeNullOrEmpty - - } - - It "Lève une exception si le fichier est absent" { - - { - - Get-ScriptAst ` - -File ([System.IO.FileInfo]::new("C:\Impossible\Test.ps1")) - - } | - Should -Throw - - } - - } - - Context "Erreurs de parsing" { - - It "Ne contient aucune erreur" { - - Get-ParseErrors ` - -Script $Script | - Should -BeNullOrEmpty - - } - - It "Test-ParseErrors retourne False" { - - Test-ParseErrors ` - -Script $Script | - Should -BeFalse - - } - - } - - Context "Tokens" { - - It "Retourne une collection" { - - $Tokens = Get-Tokens ` - -Script $Script - - $Tokens | - Should -Not -BeNullOrEmpty - - } - - It "Retourne plusieurs tokens" { - - (Get-Tokens ` - -Script $Script).Count | - Should -BeGreaterThan 10 - - } - - } - - Context "Commandes" { - - It "Retourne plusieurs commandes" { - - $Commands = Get-Commands ` - -Script $Script - - $Commands.Count | - Should -BeGreaterThan 1 - - } - - It "Trouve Write-Host" { - - $Commands = Find-Commands ` - -Script $Script ` - -Name "Write-Host" - - $Commands.Count | - Should -Be 1 - - } - - It "Trouve Get-Date" { - - $Commands = Find-Commands ` - -Script $Script ` - -Name "Get-Date" - - $Commands.Count | - Should -Be 1 - - } - - It "Ne trouve pas une commande inexistante" { - - $Commands = Find-Commands ` - -Script $Script ` - -Name "CommandeInexistante" - - $Commands | - Should -BeNullOrEmpty - - } - - } - Context "Informations sur les commandes" { - - It "Retourne le nom d'une commande" { - - $Command = ( - Find-Commands ` - -Script $Script ` - -Name "Write-Host" - )[0] - - Get-CommandName ` - -Command $Command | - Should -Be "Write-Host" - - } - - It "Retourne les éléments d'une commande" { - - $Command = ( - Find-Commands ` - -Script $Script ` - -Name "Write-Host" - )[0] - - $Elements = Get-CommandElements ` - -Command $Command - - $Elements.Count | - Should -BeGreaterThan 1 - - } - - It "Retourne les arguments d'une commande" { - - $Command = ( - Find-Commands ` - -Script $Script ` - -Name "Write-Host" - )[0] - - $Arguments = Get-CommandArguments ` - -Command $Command - - $Arguments.Count | - Should -Be 1 - - } - - It "Retourne le texte exact d'un argument" { - - $Command = ( - Find-Commands ` - -Script $Script ` - -Name "Write-Host" - )[0] - - $Argument = ( - Get-CommandArguments ` - -Command $Command - )[0] - - Get-ArgumentText ` - -Argument $Argument | - Should -Be '$Variable' - - } - - It "Retourne tous les textes des arguments" { - - $Command = ( - Find-Commands ` - -Script $Script ` - -Name "Write-Host" - )[0] - - $Arguments = Get-ArgumentTexts ` - -Command $Command - - $Arguments.Count | - Should -Be 1 - - $Arguments[0] | - Should -Be '$Variable' - - } - - } - - Context "Fonctions" { - - It "Retourne toutes les fonctions" { - - $Functions = Get-Functions ` - -Script $Script - - $Functions.Count | - Should -Be 2 - - } - - It "Trouve Test-Function" { - - $Functions = Find-Functions ` - -Script $Script ` - -Name "Test-Function" - - $Functions.Count | - Should -Be 1 - - } - - It "Ne trouve pas une fonction inexistante" { - - $Functions = Find-Functions ` - -Script $Script ` - -Name "FonctionImpossible" - - $Functions | - Should -BeNullOrEmpty - - } - - It "Retourne le nom de la fonction" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-FunctionName ` - -Function $Function | - Should -Be "Test-Function" - - } - - } - Context "Classes" { - - It "Retourne toutes les classes" { - - $Classes = Get-Classes ` - -Script $Script - - $Classes.Count | - Should -Be 1 - - } - - It "Trouve DemoClass" { - - $Classes = Find-Classes ` - -Script $Script ` - -Name "DemoClass" - - $Classes.Count | - Should -Be 1 - - } - - It "Ne trouve pas une classe inexistante" { - - $Classes = Find-Classes ` - -Script $Script ` - -Name "ClasseImpossible" - - $Classes | - Should -BeNullOrEmpty - - } - - It "Retourne le nom de la classe" { - - $Class = ( - Find-Classes ` - -Script $Script ` - -Name "DemoClass" - )[0] - - Get-ClassName ` - -Class $Class | - Should -Be "DemoClass" - - } - - } - - Context "Variables" { - - It "Retourne toutes les variables" { - - $Variables = Get-Variables ` - -Script $Script - - $Variables.Count | - Should -BeGreaterThan 2 - - } - - It "Trouve la variable Variable" { - - $Variables = Find-Variables ` - -Script $Script ` - -Name "Variable" - - $Variables.Count | - Should -Be 2 - - } - - It "Trouve la variable Name" { - - $Variables = Find-Variables ` - -Script $Script ` - -Name "Name" - - $Variables.Count | - Should -BeGreaterThan 0 - - } - - It "Ne trouve pas une variable inexistante" { - - $Variables = Find-Variables ` - -Script $Script ` - -Name "Impossible" - - $Variables | - Should -BeNullOrEmpty - - } - - It "Retourne le nom d'une variable" { - - $Variable = ( - Find-Variables ` - -Script $Script ` - -Name "Variable" - )[0] - - Get-VariableName ` - -Variable $Variable | - Should -Be "Variable" - - } - - } - - Context "Paramètres" { - - It "Retourne les blocs Param()" { - - $Blocks = Get-ParameterBlocks ` - -Script $Script - - $Blocks.Count | - Should -BeGreaterThan 0 - - } - - It "Retourne tous les paramètres" { - - $Parameters = Get-Parameters ` - -Script $Script - - $Parameters.Count | - Should -Be 2 - - } - - It "Trouve le paramètre Name" { - - $Parameter = Find-Parameters ` - -Script $Script ` - -Name "Name" - - $Parameter.Count | - Should -Be 1 - - } - - It "Trouve le paramètre Value" { - - $Parameter = Find-Parameters ` - -Script $Script ` - -Name "Value" - - $Parameter.Count | - Should -Be 1 - - } - - It "Ne trouve pas un paramètre inexistant" { - - $Parameter = Find-Parameters ` - -Script $Script ` - -Name "Impossible" - - $Parameter | - Should -BeNullOrEmpty - - } - - It "Retourne le nom d'un paramètre" { - - $Parameter = ( - Find-Parameters ` - -Script $Script ` - -Name "Value" - )[0] - - Get-ParameterName ` - -Parameter $Parameter | - Should -Be "Value" - - } - - } - Context "Informations sur les nœuds AST" { - - It "Retourne l'Extent d'un nœud" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - $Extent = Get-Extent ` - -Node $Function - - $Extent | - Should -Not -BeNullOrEmpty - - } - - It "Retourne un offset de début valide" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-StartOffset ` - -Node $Function | - Should -BeGreaterOrEqual 0 - - } - - It "Retourne un offset de fin valide" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-EndOffset ` - -Node $Function | - Should -BeGreaterThan ( - Get-StartOffset -Node $Function - ) - - } - - It "Retourne le texte exact du nœud" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-Text ` - -Node $Function | - Should -Match "function Test-Function" - - } - - It "Retourne une longueur valide" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-TextLength ` - -Node $Function | - Should -BeGreaterThan 0 - - } - - It "Retourne le type .NET du nœud" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Get-NodeType ` - -Node $Function | - Should -Be ([System.Management.Automation.Language.FunctionDefinitionAst]) - - } - - It "Indique que le nœud possède des enfants" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - Test-HasChildren ` - -Node $Function | - Should -BeTrue - - } - - It "Retourne les enfants directs" { - - $Function = ( - Find-Functions ` - -Script $Script ` - -Name "Test-Function" - )[0] - - (Get-ChildNodes ` - -Node $Function).Count | - Should -BeGreaterThan 0 - - } - - It "Recherche les nœuds d'un type donné" { - - $Nodes = Find-Nodes ` - -Script $Script ` - -NodeType ([System.Management.Automation.Language.CommandAst]) - - $Nodes.Count | - Should -BeGreaterThan 1 - - } - - } - - Context "Statistiques AST" { - - It "Retourne un objet" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics | - Should -Not -BeNullOrEmpty - - } - - It "Retourne le nombre de commandes" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Commands | - Should -BeGreaterThan 1 - - } - - It "Retourne le nombre de fonctions" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Functions | - Should -Be 2 - - } - - It "Retourne le nombre de classes" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Classes | - Should -Be 1 - - } - - It "Retourne le nombre de paramètres" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Parameters | - Should -Be 2 - - } - - It "Retourne zéro erreur" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Errors | - Should -Be 0 - - } - - It "Retourne plusieurs tokens" { - - $Statistics = Get-AstStatistics ` - -Script $Script - - $Statistics.Tokens | - Should -BeGreaterThan 10 - - } - - } - - Context "Exports du module" { - - It "Exporte toutes les fonctions attendues" { - - $Module = Get-Module Ast - - $Expected = @( - - "Get-ScriptAst", - "Get-ParseErrors", - "Test-ParseErrors", - "Get-Tokens", - - "Get-Commands", - "Find-Commands", - "Get-CommandName", - "Get-CommandElements", - "Get-CommandArguments", - "Get-ArgumentText", - "Get-ArgumentTexts", - - "Get-Functions", - "Find-Functions", - "Get-FunctionName", - - "Get-Classes", - "Find-Classes", - "Get-ClassName", - - "Get-Variables", - "Find-Variables", - "Get-VariableName", - - "Get-ParameterBlocks", - "Get-Parameters", - "Find-Parameters", - "Get-ParameterName", - - "Get-Extent", - "Get-StartOffset", - "Get-EndOffset", - "Get-Text", - "Get-TextLength", - "Get-NodeType", - "Test-HasChildren", - "Get-ChildNodes", - "Find-Nodes", - - "Get-AstStatistics" - - ) - - foreach ($Function in $Expected) - { - $Module.ExportedFunctions.Keys | - Should -Contain $Function - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Backup.Tests.ps1 b/Tests/Legacy/Modules/Backup.Tests.ps1 deleted file mode 100644 index 86d0a61..0000000 --- a/Tests/Legacy/Modules/Backup.Tests.ps1 +++ /dev/null @@ -1,757 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Backup. - -.DESCRIPTION - Vérifie le gestionnaire de sauvegardes du framework PimsOS. - -.NOTES - - Projet : PimsOS - Module : Backup.Tests - Framework : Pester 5 - -#> - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $ModuleRoot = Join-Path $ProjectRoot "Tools\Migration\Modules" - - Import-Module ` - (Join-Path $ModuleRoot "Common.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Backup.psm1") ` - -Force - - - # - # Création d'un faux projet - # - - $FakeProject = Join-Path ` - $TestDrive ` - "Project" - - New-Item ` - -ItemType Directory ` - -Path $FakeProject ` - -Force | Out-Null - - $Folder = Join-Path ` - $FakeProject ` - "Source" - - New-Item ` - -ItemType Directory ` - -Path $Folder ` - -Force | Out-Null - - $TestFile = Join-Path ` - $Folder ` - "Sample.txt" - - "Hello Backup" | - Set-Content ` - -LiteralPath $TestFile ` - -Encoding UTF8 - - # - # Simulation des fonctions du module Common - # - - Mock Get-ProjectRoot -ModuleName Backup { - - $FakeProject - - } - - Mock Get-MigrationRoot -ModuleName Backup { - - Join-Path ` - $TestDrive ` - "Migration" - - } - -} - -Describe "Module Backup" { - - Context "Get-BackupRoot" { - - It "Retourne le dossier Backups" { - - $Root = Get-BackupRoot - - $Root | - Should -Match "Backups$" - - } - - } - - Context "Get-RelativeProjectPath" { - - It "Retourne le chemin relatif" { - - (Get-ProjectRoot) | Write-Host - $TestFile | Write-Host - - $Relative = Get-RelativeProjectPath ` - -File (Get-Item $TestFile) - - $Relative | - Should -Be "Source\Sample.txt" - - } - - It "Lève une exception si le fichier n'appartient pas au projet" { - - $Outside = Join-Path ` - $TestDrive ` - "Outside.txt" - - "Test" | - Set-Content ` - -LiteralPath $Outside - - { - - Get-RelativeProjectPath ` - -File (Get-Item $Outside) - - } | - Should -Throw - - } - - } - - Context "Get-BackupPath" { - - BeforeEach { - - $Session = New-BackupSession - - } - - It "Retourne le dossier de la session" { - - Get-BackupPath ` - -Session $Session | - Should -Be $Session.BackupRoot - - } - - } - - Context "Get-BackupFile" { - - BeforeEach { - - $Session = New-BackupSession - - } - - It "Construit le chemin de sauvegarde" { - - $BackupFile = Get-BackupFile ` - -File (Get-Item $TestFile) ` - -Session $Session - - $BackupFile | - Should -Match "Sample.txt$" - - } - - It "Place la sauvegarde dans le dossier de session" { - - $BackupFile = Get-BackupFile ` - -File (Get-Item $TestFile) ` - -Session $Session - - $BackupFile.StartsWith( - $Session.BackupRoot, - [System.StringComparison]::OrdinalIgnoreCase - ) | - Should -BeTrue - - } - - } - - - Context "Test-BackupSession" { - - BeforeEach { - - $Session = New-BackupSession - - } - - It "Retourne True pour une session valide" { - - Test-BackupSession ` - -Session $Session | - Should -BeTrue - - } - - It "Retourne False pour une session vide" { - - Test-BackupSession ` - -Session @{} | - Should -BeFalse - - } - - It "Retourne False si le dossier n'existe plus" { - - Remove-Item ` - -LiteralPath $Session.BackupRoot ` - -Recurse ` - -Force - - Test-BackupSession ` - -Session $Session | - Should -BeFalse - - $Session = New-BackupSession - - } - - } - - Context "New-BackupSession" { - - It "Crée une nouvelle session" { - - $NewSession = New-BackupSession - - $NewSession | - Should -Not -BeNullOrEmpty - - } - - It "Contient les propriétés attendues" { - - $NewSession = New-BackupSession - - $NewSession.Id | - Should -Not -BeNullOrEmpty - - $NewSession.Started | - Should -Not -BeNullOrEmpty - - $NewSession.BackupRoot | - Should -Not -BeNullOrEmpty - - } - - It "Crée le dossier de sauvegarde" { - - $NewSession = New-BackupSession - - Test-Path ` - -LiteralPath $NewSession.BackupRoot ` - -PathType Container | - Should -BeTrue - - } - - } - - Context "Get-BackupFiles" { - - BeforeEach { - - $Session = New-BackupSession - - } - - It "Retourne une collection vide au départ" { - - (Get-BackupFiles ` - -Session $Session).Count | - Should -Be 0 - - } - - It "Retourne les fichiers sauvegardés" { - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - $Files = Get-BackupFiles ` - -Session $Session - - $Files.Count | - Should -Be 1 - - } - - It "Lève une exception avec une session invalide" { - - { - - Get-BackupFiles ` - -Session @{} - - } | - Should -Throw - - } - - } - - Context "New-Backup" { - - BeforeEach { - - $Session = New-BackupSession - - } - - It "Crée une sauvegarde" { - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - Test-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session | - Should -BeTrue - - } - - It "Copie le contenu du fichier" { - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - $BackupFile = Get-BackupFile ` - -File (Get-Item $TestFile) ` - -Session $Session - - Get-Content ` - -LiteralPath $BackupFile ` - -Raw | - Should -Be "Hello Backup`r`n" - - } - - It "Lève une exception si le fichier n'existe pas" { - - $Missing = Join-Path ` - $TestDrive ` - "Missing.txt" - - { - - New-Backup ` - -File ([System.IO.FileInfo]::new($Missing)) ` - -Session $Session - - } | - Should -Throw - - } - - It "Lève une exception avec une session invalide" { - - { - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session @{} - - } | - Should -Throw - - } - - } - Context "Restore-Backup" { - - BeforeEach { - - $Session = New-BackupSession - - "Version originale" | - Set-Content ` - -LiteralPath $TestFile ` - -Encoding UTF8 - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - } - - It "Restaure le contenu sauvegardé" { - - "Version modifiée" | - Set-Content ` - -LiteralPath $TestFile ` - -Encoding UTF8 - - Restore-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session ` - -Confirm:$false | Out-Null - - Get-Content ` - -LiteralPath $TestFile ` - -Raw | - Should -Be "Version originale`r`n" - - } - - It "Retourne le fichier restauré" { - - $Result = Restore-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session ` - -Confirm:$false - - $Result | - Should -BeOfType ([System.IO.FileInfo]) - - } - - It "Lève une exception lorsqu'aucune sauvegarde n'existe" { - - $OtherSession = New-BackupSession - - { - - Restore-Backup ` - -File (Get-Item $TestFile) ` - -Session $OtherSession ` - -Confirm:$false - - } | - Should -Throw - - } - - } - - Context "Remove-Backup" { - - BeforeEach { - - $Session = New-BackupSession - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - } - - It "Supprime la sauvegarde" { - - Remove-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session ` - -Confirm:$false - - Test-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session | - Should -BeFalse - - } - - It "Ne lève pas d'exception si la sauvegarde est absente" { - - Remove-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session ` - -Confirm:$false - - { - - Remove-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session ` - -Confirm:$false - - } | - Should -Not -Throw - - } - - } - - Context "Test-Backup" { - - It "Retourne False lorsqu'aucune sauvegarde n'existe" { - - $Session = New-BackupSession - - Test-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session | - Should -BeFalse - - } - - It "Retourne True lorsqu'une sauvegarde existe" { - - $Session = New-BackupSession - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - Test-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session | - Should -BeTrue - - } - - It "Retourne False pour une session invalide" { - - Test-Backup ` - -File (Get-Item $TestFile) ` - -Session @{} | - Should -BeFalse - - } - - } - - Context "Get-BackupStatistics" { - - BeforeEach { - - $Session = New-BackupSession - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - } - - It "Retourne un objet de statistiques" { - - $Statistics = Get-BackupStatistics ` - -Session $Session - - $Statistics | - Should -Not -BeNullOrEmpty - - } - - It "Retourne le nombre correct de fichiers" { - - $Statistics = Get-BackupStatistics ` - -Session $Session - - $Statistics.Files | - Should -Be 1 - - } - - It "Retourne une taille supérieure à zéro" { - - $Statistics = Get-BackupStatistics ` - -Session $Session - - $Statistics.Size | - Should -BeGreaterThan 0 - - } - - It "Retourne l'identifiant de session" { - - $Statistics = Get-BackupStatistics ` - -Session $Session - - $Statistics.Session | - Should -Be $Session.Id - - } - - It "Lève une exception avec une session invalide" { - - { - - Get-BackupStatistics ` - -Session @{} - - } | - Should -Throw - - } - - } - Context "Get-BackupSessions" { - - BeforeEach { - - $null = New-BackupSession - Start-Sleep -Milliseconds 25 - $null = New-BackupSession - - } - - It "Retourne une collection" { - - $Sessions = Get-BackupSessions - - $Sessions | - Should -Not -BeNullOrEmpty - - } - - It "Retourne des dossiers" { - - $Sessions = Get-BackupSessions - - foreach ($SessionFolder in $Sessions) - { - $SessionFolder | - Should -BeOfType ([System.IO.DirectoryInfo]) - } - - } - - It "Trie les sessions par ordre décroissant" { - - $Sessions = Get-BackupSessions - - if ($Sessions.Count -ge 2) - { - $Sessions[0].Name.CompareTo( - $Sessions[1].Name - ) | - Should -BeGreaterThan 0 - } - - } - - } - - Context "Remove-BackupSession" { - - It "Supprime une session complète" { - - $Session = New-BackupSession - - New-Backup ` - -File (Get-Item $TestFile) ` - -Session $Session - - Remove-BackupSession ` - -Session $Session ` - -Confirm:$false - - Test-Path ` - -LiteralPath $Session.BackupRoot | - Should -BeFalse - - } - - It "Lève une exception avec une session invalide" { - - { - - Remove-BackupSession ` - -Session @{} ` - -Confirm:$false - - } | - Should -Throw - - } - - } - - Context "Clear-Backups" { - - BeforeEach { - - 1..5 | ForEach-Object { - - $null = New-BackupSession - - Start-Sleep -Milliseconds 25 - - } - - } - - It "Conserve le nombre demandé de sessions" { - - Clear-Backups ` - -Keep 2 ` - -Confirm:$false - - (Get-BackupSessions).Count | - Should -BeLessOrEqual 2 - - } - - It "Ne supprime rien si le nombre est inférieur à Keep" { - - $Before = (Get-BackupSessions).Count - - Clear-Backups ` - -Keep 100 ` - -Confirm:$false - - (Get-BackupSessions).Count | - Should -Be $Before - - } - - } - - Context "Exports du module" { - - It "Exporte toutes les fonctions attendues" { - - $Module = Get-Module Backup - - $Expected = @( - - "Get-BackupRoot", - "Get-RelativeProjectPath", - "Get-BackupPath", - "Get-BackupFile", - "Test-BackupSession", - "New-BackupSession", - "Get-BackupFiles", - "New-Backup", - "Restore-Backup", - "Remove-Backup", - "Test-Backup", - "Get-BackupStatistics", - "Get-BackupSessions", - "Remove-BackupSession", - "Clear-Backups" - - ) - - foreach ($Function in $Expected) - { - $Module.ExportedFunctions.Keys | - Should -Contain $Function - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Common.Tests.ps1 b/Tests/Legacy/Modules/Common.Tests.ps1 deleted file mode 100644 index 4ef0701..0000000 --- a/Tests/Legacy/Modules/Common.Tests.ps1 +++ /dev/null @@ -1,442 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Common. - -.DESCRIPTION - Vérifie les fonctions utilitaires du framework de migration. - -.NOTES - - Projet : PimsOS - Module : Common.Tests - Framework : Pester 5 - -#> - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $ModuleRoot = Join-Path $ProjectRoot "Tools\Migration\Modules" - - Import-Module ` - (Join-Path $ModuleRoot "Common.psm1") ` - -Force - -} - -Describe "Module Common" { - - Context "Chargement du module" { - - It "Le module est chargé" { - - Get-Module Common | - Should -Not -BeNullOrEmpty - - } - - } - - Context "Informations du framework" { - - It "Retourne le répertoire du projet" { - - $Result = Get-ProjectRoot - - $Result | - Should -Not -BeNullOrEmpty - - Test-Path $Result | - Should -BeTrue - - } - - It "Retourne le répertoire Migration" { - - $Result = Get-MigrationRoot - - $Result | - Should -Not -BeNullOrEmpty - - Test-Path $Result | - Should -BeTrue - - } - - It "Retourne le nom du framework" { - - Get-FrameworkName | - Should -Be "PimsOS Migration Framework" - - } - - It "Retourne la version du framework" { - - Get-FrameworkVersion | - Should -Be "1.1.0" - - } - - It "Retourne les extensions du projet" { - - $Extensions = Get-ProjectExtensions - - $Extensions | - Should -Contain "*.ps1" - - $Extensions | - Should -Contain "*.psm1" - - $Extensions | - Should -Contain "*.psd1" - - } - - It "Retourne les dossiers exclus" { - - $Folders = Get-ExcludedFolders - - $Folders | - Should -Contain ".git" - - $Folders | - Should -Contain ".github" - - $Folders | - Should -Contain ".vscode" - - } - - } - - Context "Version de PowerShell" { - - It "PowerShell 7.6 est supporté" { - - Test-PowerShellVersion | - Should -BeTrue - - } - - It "Une version très élevée est refusée" { - - Test-PowerShellVersion ` - -MinimumVersion 99.0 | - Should -BeFalse - - } - - } - - Context "Gestion des chemins" { - - It "Valide un chemin existant" { - - Test-MigrationPath ` - -Path $env:TEMP | - Should -BeTrue - - } - - It "Refuse un chemin inexistant" { - - Test-MigrationPath ` - -Path "C:\___Impossible___\ABC" | - Should -BeFalse - - } - - It "Calcule un chemin relatif" { - - $Base = "C:\Temp" - $Target = "C:\Temp\Test\File.ps1" - - $Result = Get-RelativePath ` - -BasePath $Base ` - -Path $Target - - $Result | - Should -Be "Test\File.ps1" - - } - - } - - Context "Objets métier" { - - It "Crée une règle de migration" { - - $Rule = New-MigrationRule - - $Rule.ObjectType | - Should -Be "MigrationRule" - - $Rule.Name | - Should -Be "" - - $Rule.Description | - Should -Be "" - - $Rule.Enabled | - Should -BeTrue - - $Rule.Priority | - Should -Be 100 - - $Rule.Script | - Should -BeNullOrEmpty - - } - - It "Crée un contexte de migration" { - - $Context = New-MigrationContext - - $Context.ObjectType | - Should -Be "MigrationContext" - - $Context.ProjectName | - Should -Be "" - - $Context.ProjectRoot | - Should -Be "" - - $Context.Rule | - Should -Be "" - - $Context.AnalyzeOnly | - Should -BeFalse - - $Context.ExecuteAll | - Should -BeFalse - - ($null -eq $Context.Results) | - Should -BeFalse - - $Context.Results.Count | - Should -Be 0 - - } - - } - - Context "Objets résultat" { - - It "Crée un résultat vide" { - - $Result = New-MigrationResult - - $Result.ObjectType | - Should -Be "MigrationResult" - - $Result.Modified | - Should -BeFalse - - $Result.File | - Should -BeNullOrEmpty - - $Result.Rule | - Should -BeNullOrEmpty - - $Result.Message | - Should -Be "" - - } - - It "Crée un résultat complet" { - - $Result = New-MigrationResult ` - -File "Test.ps1" ` - -Rule "Rule001" ` - -Modified $true ` - -Message "OK" - - $Result.ObjectType | - Should -Be "MigrationResult" - - $Result.File | - Should -Be "Test.ps1" - - $Result.Rule | - Should -Be "Rule001" - - $Result.Modified | - Should -BeTrue - - $Result.Message | - Should -Be "OK" - - } - - It "Crée une erreur de migration" { - - $Error = New-MigrationError ` - -File "Test.ps1" ` - -Rule "Rule001" ` - -Message "Erreur" - - $Error.ObjectType | - Should -Be "MigrationError" - - $Error.File | - Should -Be "Test.ps1" - - $Error.Rule | - Should -Be "Rule001" - - $Error.Message | - Should -Be "Erreur" - - } - - } - - Context "Chronomètre" { - - It "Crée un chronomètre" { - - $Stopwatch = New-Stopwatch - - $Stopwatch | - Should -BeOfType ([System.Diagnostics.Stopwatch]) - - $Stopwatch.IsRunning | - Should -BeTrue - - } - - It "Retourne un temps écoulé" { - - $Stopwatch = New-Stopwatch - - Start-Sleep -Milliseconds 100 - - $Elapsed = Get-ElapsedTime ` - -Stopwatch $Stopwatch - - $Elapsed | - Should -BeOfType ([TimeSpan]) - - $Elapsed.TotalMilliseconds | - Should -BeGreaterThan 50 - - } - - } - - Context "Fonctions d'affichage" { - - InModuleScope Common { - - BeforeEach { - - Mock Write-Host {} - - } - - It "Write-Blank appelle Write-Host" { - - Write-Blank - - Should -Invoke Write-Host -Times 1 - - } - - It "Write-Banner affiche le bandeau" { - - Write-Banner - - Should -Invoke Write-Host -Times 6 - - } - - It "Write-Section affiche une section" { - - Write-Section -Title "Migration" - - Should -Invoke Write-Host -Times 2 - - } - - It "Write-Info affiche un message" { - - Write-Info -Message "Information" - - Should -Invoke Write-Host -Times 1 - - } - - It "Write-Success affiche un succès" { - - Write-Success -Message "Succès" - - Should -Invoke Write-Host -Times 1 - - } - - It "Write-WarningMessage affiche un avertissement" { - - Write-WarningMessage -Message "Attention" - - Should -Invoke Write-Host -Times 1 - - } - - It "Write-ErrorMessage affiche une erreur" { - - Write-ErrorMessage -Message "Erreur" - - Should -Invoke Write-Host -Times 1 - - } - - } - - } - - Context "Exports du module" { - - It "Exporte toutes les fonctions attendues" { - - $Module = Get-Module Common - - $Expected = @( - "Get-ProjectRoot", - "Get-MigrationRoot", - "Get-FrameworkName", - "Get-FrameworkVersion", - "Get-ProjectExtensions", - "Get-ExcludedFolders", - "Test-PowerShellVersion", - "Test-MigrationPath", - "Get-RelativePath", - "New-Stopwatch", - "Get-ElapsedTime", - "New-MigrationRule", - "New-MigrationContext", - "New-MigrationResult", - "New-MigrationError", - "Write-Blank", - "Write-Banner", - "Write-Section", - "Write-Info", - "Write-Success", - "Write-WarningMessage", - "Write-ErrorMessage" - ) - - foreach ($Function in $Expected) { - - $Module.ExportedFunctions.Keys | - Should -Contain $Function - - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Migration.Tests.ps1 b/Tests/Legacy/Modules/Migration.Tests.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/Legacy/Modules/Replace.Tests.ps1 b/Tests/Legacy/Modules/Replace.Tests.ps1 deleted file mode 100644 index e7b3cbf..0000000 --- a/Tests/Legacy/Modules/Replace.Tests.ps1 +++ /dev/null @@ -1,1444 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Replace. - -.DESCRIPTION - Vérifie le fonctionnement du moteur de remplacement - utilisé par le framework de migration PimsOS. - -.NOTES - - Projet : PimsOS - Module : Replace - Version : 3.0.0 - Framework: Pester 5 - -#> - -#Requires -Modules Pester - -Set-StrictMode -Version Latest - -#-------------------------------------------------- -# Localisation du projet -#-------------------------------------------------- - -$ProjectRoot = Split-Path ( - Split-Path $PSScriptRoot -Parent -) -Parent - -$ModulePath = Join-Path ` - $ProjectRoot ` - 'Tools\Migration\Modules\Replace.psm1' - -if (-not (Test-Path -LiteralPath $ModulePath)) -{ - throw "Module introuvable : $ModulePath" -} - -Import-Module ` - -Name $ModulePath ` - -Force ` - -ErrorAction Stop - -BeforeAll { - -} - -AfterAll { - - Remove-Module ` - Replace ` - -ErrorAction SilentlyContinue - -} - -Describe 'Replace module' { - - It 'Le module est chargé' { - - Get-Module Replace | - Should -Not -BeNullOrEmpty - - } - - InModuleScope Replace { - - #================================================== - # New-ReplacementCollection - #================================================== - - Describe 'New-ReplacementCollection' { - - It 'Retourne une collection vide' { - - $Collection = New-ReplacementCollection - - ($null -eq $Collection) | - Should -BeFalse - - $Collection.Count | - Should -Be 0 - - } - - - - } - - #================================================== - # New-Replacement - #================================================== - - Describe 'New-Replacement' { - - Context 'Création valide' { - - It 'Crée un remplacement standard' { - - $Replacement = New-Replacement ` - -Start 10 ` - -End 20 ` - -Original 'abcdefghij' ` - -Replacement '0123456789' ` - -Description 'Test' - - $Replacement.Start | - Should -Be 10 - - $Replacement.End | - Should -Be 20 - - $Replacement.Length | - Should -Be 10 - - $Replacement.Original | - Should -BeExactly 'abcdefghij' - - $Replacement.Replacement | - Should -BeExactly '0123456789' - - $Replacement.Description | - Should -BeExactly 'Test' - - $Replacement.Applied | - Should -BeFalse - - } - - It 'Accepte un Original vide (insertion)' { - - $Replacement = New-Replacement ` - -Start 5 ` - -End 5 ` - -Original '' ` - -Replacement 'ABC' - - $Replacement.Length | - Should -Be 0 - - $Replacement.Original | - Should -BeExactly '' - - } - - It 'Accepte un Replacement vide (suppression)' { - - $Replacement = New-Replacement ` - -Start 3 ` - -End 8 ` - -Original 'ABCDE' ` - -Replacement '' - - $Replacement.Length | - Should -Be 5 - - $Replacement.Replacement | - Should -BeExactly '' - - } - - } - - Context 'Validation' { - - It 'Refuse End inférieur à Start' { - - { - New-Replacement ` - -Start 20 ` - -End 10 ` - -Original 'ABC' ` - -Replacement 'DEF' - - } | - Should -Throw - - } - - It 'Refuse un Start négatif' { - - { - New-Replacement ` - -Start -1 ` - -End 5 ` - -Original 'ABC' ` - -Replacement 'DEF' - - } | - Should -Throw - - } - - It 'Refuse un End négatif' { - - { - New-Replacement ` - -Start 0 ` - -End -1 ` - -Original 'ABC' ` - -Replacement 'DEF' - - } | - Should -Throw - - } - - } - - } - #================================================== - # Add-Replacement - #================================================== - - Describe 'Add-Replacement' { - - Context 'Ajout valide' { - - It 'Ajoute un remplacement dans la collection' { - - $Collection = New-ReplacementCollection - - # Vérifications de diagnostic - ($null -eq $Collection) | - Should -BeFalse - - $Collection.GetType().Name | - Should -Be 'List`1' - - $Collection.Count | - Should -Be 0 - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'ABCDE' ` - -Replacement '12345' - - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement - - $Collection.Count | - Should -Be 1 - - $Collection[0] | - Should -Be $Replacement - - } - - It 'Ajoute plusieurs remplacements' { - - $Collection = New-ReplacementCollection - - foreach ($i in 0..9) - { - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start ($i * 10) ` - -End (($i * 10) + 5) ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - } - - $Collection.Count | - Should -Be 10 - - } - - } - - Context 'Validation' { - - It 'Refuse un remplacement invalide' { - - $Collection = New-ReplacementCollection - - $Replacement = [PSCustomObject]@{ - - Start = 0 - - } - - { - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement - - } | - Should -Throw - - } - - } - - } - - #================================================== - # Get-ReplacementCount - #================================================== - - Describe 'Get-ReplacementCount' { - - It 'Retourne zéro pour une collection vide' { - - $Collection = New-ReplacementCollection - - Get-ReplacementCount ` - -Collection $Collection | - Should -Be 0 - - } - - It 'Retourne le nombre réel de remplacements' { - - $Collection = New-ReplacementCollection - - foreach ($i in 1..5) - { - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start ($i * 20) ` - -End (($i * 20) + 5) ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - } - - Get-ReplacementCount ` - -Collection $Collection | - Should -Be 5 - - } - - } - - #================================================== - # Test-Replacement - #================================================== - - Describe 'Test-Replacement' { - - Context 'Objet valide' { - - It 'Retourne True pour un remplacement valide' { - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - - Test-Replacement ` - -Replacement $Replacement | - Should -BeTrue - - } - - } - - Context 'Objet invalide' { - - It 'Retourne False si une propriété est absente' { - - $Replacement = [PSCustomObject]@{ - - Start = 0 - End = 5 - } - - Test-Replacement ` - -Replacement $Replacement | - Should -BeFalse - - } - - It 'Retourne False si Length est incorrect' { - - $Replacement = [PSCustomObject]@{ - - Start = 0 - End = 10 - Length = 99 - Original = 'AAAAAAAAAA' - Replacement = 'BBBBBBBBBB' - Description = '' - Applied = $false - - } - - Test-Replacement ` - -Replacement $Replacement | - Should -BeFalse - - } - - It 'Retourne False si End est inférieur à Start' { - - $Replacement = [PSCustomObject]@{ - - Start = 10 - End = 5 - Length = -5 - Original = 'AAAAA' - Replacement = 'BBBBB' - Description = '' - Applied = $false - - } - - Test-Replacement ` - -Replacement $Replacement | - Should -BeFalse - - } - - It 'Retourne False si Start est négatif' { - - $Replacement = [PSCustomObject]@{ - - Start = -1 - End = 5 - Length = 6 - Original = 'AAAAAA' - Replacement = 'BBBBBB' - Description = '' - Applied = $false - - } - - Test-Replacement ` - -Replacement $Replacement | - Should -BeFalse - - } - - It 'Retourne False si Replacement vaut `$null' { - - $Replacement = [PSCustomObject]@{ - - Start = 0 - End = 5 - Length = 5 - Original = 'AAAAA' - Replacement = $null - Description = '' - Applied = $false - - } - - Test-Replacement ` - -Replacement $Replacement | - Should -BeFalse - - } - - } - - } - #================================================== - # Test-Replacements - #================================================== - - Describe 'Test-Replacements' { - - Context 'Collection valide' { - - It 'Retourne True pour une collection vide' { - - $Collection = New-ReplacementCollection - - Test-Replacements ` - -Collection $Collection | - Should -BeTrue - - } - - It 'Retourne True avec un remplacement' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - - Test-Replacements ` - -Collection $Collection | - Should -BeTrue - - } - - It 'Retourne True avec plusieurs remplacements' { - - $Collection = New-ReplacementCollection - - foreach ($i in 0..4) - { - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start ($i * 20) ` - -End (($i * 20) + 5) ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - } - - Test-Replacements ` - -Collection $Collection | - Should -BeTrue - - } - - } - - Context 'Chevauchements' { - - It 'Détecte deux remplacements qui se chevauchent' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 10 ` - -Original 'AAAAAAAAAA' ` - -Replacement 'BBBBBBBBBB' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 15 ` - -Original 'CCCCCCCCCC' ` - -Replacement 'DDDDDDDDDD' - ) - - Test-Replacements ` - -Collection $Collection | - Should -BeFalse - - } - - It 'Autorise deux remplacements adjacents' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 10 ` - -Original 'CCCCC' ` - -Replacement 'DDDDD' - ) - - Test-Replacements ` - -Collection $Collection | - Should -BeTrue - - } - - } - - Context 'Remplacement invalide' { - - It 'Retourne False si un élément est invalide' { - - $Collection = New-ReplacementCollection - - $Collection.Add( - [PSCustomObject]@{ - Start = 0 - } - ) - - Test-Replacements ` - -Collection $Collection | - Should -BeFalse - - } - - } - - } - - #================================================== - # Sort-Replacements - #================================================== - - Describe 'Sort-Replacements' { - - It 'Retourne une collection vide si la collection est vide' { - - $Collection = New-ReplacementCollection - - $Sorted = Sort-Replacements ` - -Collection $Collection - - @($Sorted).Count | - Should -Be 0 - - } - - It 'Trie par Start décroissant' { - - $Collection = New-ReplacementCollection - - foreach ($Start in 10, 50, 20, 40, 30) - { - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start $Start ` - -End ($Start + 5) ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - } - - $Sorted = @(Sort-Replacements ` - -Collection $Collection) - - $Sorted.Count | - Should -Be 5 - - $Sorted[0].Start | - Should -Be 50 - - $Sorted[1].Start | - Should -Be 40 - - $Sorted[2].Start | - Should -Be 30 - - $Sorted[3].Start | - Should -Be 20 - - $Sorted[4].Start | - Should -Be 10 - - } - - It 'Trie également sur End lorsque Start est identique' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 10 ` - -End 20 ` - -Original 'AAAAAAAAAA' ` - -Replacement 'BBBBBBBBBB' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 10 ` - -End 15 ` - -Original 'AAAAA' ` - -Replacement 'BBBBB' - ) - - $Sorted = @(Sort-Replacements ` - -Collection $Collection) - - $Sorted.Count | - Should -Be 2 - - $Sorted[0].End | - Should -Be 20 - - $Sorted[1].End | - Should -Be 15 - - } - - } - #================================================== - # Convert-Replacements - #================================================== - - Describe 'Convert-Replacements' { - - Context 'Collection vide' { - - It 'Retourne le contenu inchangé' { - - $Collection = New-ReplacementCollection - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hello World' - - } - - } - - Context 'Un remplacement' { - - It 'Remplace correctement une chaîne' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hello Pims' - - } - - } - - Context 'Deux remplacements' { - - It 'Applique plusieurs remplacements' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Salut Pims' - - } - - } - - Context 'Insertion' { - - It 'Insère du texte' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 5 ` - -Original '' ` - -Replacement ' World' - ) - - $Result = Convert-Replacements ` - -Content 'Hello' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hello World' - - } - - } - - Context 'Suppression' { - - It 'Supprime une portion du texte' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 11 ` - -Original ' World' ` - -Replacement '' - ) - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hello' - - } - - } - - Context 'Remplacement plus long' { - - It 'Accepte un texte de remplacement plus long' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 2 ` - -Original 'Hi' ` - -Replacement 'Bonjour' - ) - - $Result = Convert-Replacements ` - -Content 'Hi' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Bonjour' - - } - - } - - Context 'Remplacement plus court' { - - It 'Accepte un texte de remplacement plus court' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 7 ` - -Original 'Bonjour' ` - -Replacement 'Hi' - ) - - $Result = Convert-Replacements ` - -Content 'Bonjour' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hi' - - } - - } - - Context 'Ordre des remplacements' { - - It 'Applique les remplacements du dernier vers le premier' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 8 ` - -End 10 ` - -Original '89' ` - -Replacement 'AB' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 2 ` - -End 4 ` - -Original '23' ` - -Replacement 'CD' - ) - - $Result = Convert-Replacements ` - -Content '0123456789' ` - -Collection $Collection - - $Result | - Should -BeExactly '01CD4567AB' - - } - - } - - Context 'Remplacements adjacents' { - - It 'Traite correctement deux remplacements côte à côte' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 6 ` - -Original ' ' ` - -Replacement '-' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Salut-Pims' - - } - - } - Context 'Collection non triée' { - - It 'Trie automatiquement les remplacements avant application' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - ) - - $Result = Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Salut Pims' - - } - - } - } - #================================================== - # Convert-Replacements - Cas d'erreur - #================================================== - - Describe 'Convert-Replacements - Validation' { - - Context 'Collection invalide' { - - It 'Lève une exception lorsqu''un remplacement est invalide' { - - $Collection = New-ReplacementCollection - - $Collection.Add( - [PSCustomObject]@{ - Start = 0 - } - ) - - { - Convert-Replacements ` - -Content 'Hello' ` - -Collection $Collection - - } | - Should -Throw 'La collection de remplacements est invalide.' - - } - - It 'Lève une exception lorsqu''il existe un chevauchement' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 3 ` - -End 8 ` - -Original 'lo Wo' ` - -Replacement 'XXXXX' - ) - - { - Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - } | - Should -Throw 'La collection de remplacements est invalide.' - - } - - } - - Context 'Position invalide' { - - It 'Lève une exception si End dépasse la taille du contenu' { - - $Collection = New-ReplacementCollection - - $Replacement = New-Replacement ` - -Start 8 ` - -End 20 ` - -Original '89ABCDEFGHI' ` - -Replacement 'TEST' - - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement - - { - Convert-Replacements ` - -Content '0123456789' ` - -Collection $Collection - - } | - Should -Throw 'Le remplacement dépasse la taille du contenu.' - - } - - } - - Context 'Texte inattendu' { - - It 'Lève une exception si Original ne correspond pas' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'Earth' ` - -Replacement 'Pims' - ) - - { - Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - } | - Should -Throw - - } - - It 'Le message contient la position' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'Earth' ` - -Replacement 'Pims' ` - -Description 'Test de validation' - ) - - try - { - Convert-Replacements ` - -Content 'Hello World' ` - -Collection $Collection - - throw 'Aucune exception.' - - } - catch - { - $_.Exception.Message | - Should -Match 'Position' - - $_.Exception.Message | - Should -Match '6' - - $_.Exception.Message | - Should -Match 'Earth' - - $_.Exception.Message | - Should -Match 'World' - } - - } - - } - - Context 'Insertion en fin de chaîne' { - - It 'Autorise une insertion exactement à la fin' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 5 ` - -End 5 ` - -Original '' ` - -Replacement ' World' - ) - - $Result = Convert-Replacements ` - -Content 'Hello' ` - -Collection $Collection - - $Result | - Should -BeExactly 'Hello World' - - } - - } - - Context 'Suppression complète' { - - It 'Supprime tout le contenu' { - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement '' - ) - - $Result = Convert-Replacements ` - -Content 'Hello' ` - -Collection $Collection - - $Result | - Should -BeExactly '' - - } - - } - - } - #================================================== - # Invoke-Replacements - #================================================== - - Describe 'Invoke-Replacements' { - - BeforeEach { - - $TestFile = Join-Path ` - $TestDrive ` - 'Replace.txt' - - } - - Context 'Fichier inexistant' { - - It 'Lève une exception' { - - $Collection = New-ReplacementCollection - - $File = [System.IO.FileInfo]::new($TestFile) - - { - Invoke-Replacements ` - -File $File ` - -Collection $Collection - - } | - Should -Throw - - } - - } - - Context 'Application d''un remplacement' { - - It 'Modifie le contenu du fichier' { - - Set-Content ` - -LiteralPath $TestFile ` - -Value 'Hello World' ` - -Encoding UTF8 - - $File = Get-Item ` - -LiteralPath $TestFile - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - Invoke-Replacements ` - -File $File ` - -Collection $Collection - - $Content = Get-Content ` - -LiteralPath $TestFile ` - -Raw - - $Content.TrimEnd("`r", "`n") | - Should -BeExactly 'Hello Pims' - - } - - } - - Context 'Applied' { - - It 'Marque tous les remplacements comme appliqués' { - - Set-Content ` - -LiteralPath $TestFile ` - -Value 'Hello World' ` - -Encoding UTF8 - - $File = Get-Item ` - -LiteralPath $TestFile - - $Collection = New-ReplacementCollection - - $Replacement = New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement - - Invoke-Replacements ` - -File $File ` - -Collection $Collection - - $Replacement.Applied | - Should -BeTrue - - } - - } - - Context 'Plusieurs remplacements' { - - It 'Applique correctement tous les remplacements' { - - Set-Content ` - -LiteralPath $TestFile ` - -Value 'Hello World' ` - -Encoding UTF8 - - $File = Get-Item ` - -LiteralPath $TestFile - - $Collection = New-ReplacementCollection - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - ) - - Add-Replacement ` - -Collection $Collection ` - -Replacement ( - New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - ) - - Invoke-Replacements ` - -File $File ` - -Collection $Collection - - $Content = Get-Content ` - -LiteralPath $TestFile ` - -Raw - - $Content.TrimEnd("`r", "`n") | - Should -BeExactly 'Salut Pims' - - } - It 'Marque tous les remplacements de la collection comme appliqués' { - - Set-Content ` - -LiteralPath $TestFile ` - -Value 'Hello World' ` - -Encoding UTF8 - - $File = Get-Item ` - -LiteralPath $TestFile - - $Collection = New-ReplacementCollection - - $Replacement1 = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original 'Hello' ` - -Replacement 'Salut' - - $Replacement2 = New-Replacement ` - -Start 6 ` - -End 11 ` - -Original 'World' ` - -Replacement 'Pims' - - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement1 - - Add-Replacement ` - -Collection $Collection ` - -Replacement $Replacement2 - - Invoke-Replacements ` - -File $File ` - -Collection $Collection - - foreach ($Replacement in $Collection) - { - $Replacement.Applied | - Should -BeTrue - } - - } - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Report.Tests.ps1 b/Tests/Legacy/Modules/Report.Tests.ps1 deleted file mode 100644 index c86ff9e..0000000 --- a/Tests/Legacy/Modules/Report.Tests.ps1 +++ /dev/null @@ -1,1140 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Report. - -.DESCRIPTION - Vérifie le gestionnaire de rapports du framework de migration PimsOS. - -.NOTES - - Projet : PimsOS - Module : Report.Tests - Framework : Pester 5 - -#> - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $ModuleRoot = Join-Path $ProjectRoot "Tools\Migration\Modules" - - Import-Module ` - (Join-Path $ModuleRoot "Common.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Replace.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Report.psm1") ` - -Force - -} - -Describe "Module Report" { - - Context "New-Report" { - - BeforeEach { - - $Report = New-Report - - } - - It "Crée un rapport valide" { - - $Report | - Should -Not -BeNullOrEmpty - - } - - It "Possède un ReportId" { - - $Report.ReportId | - Should -Not -BeNullOrEmpty - - } - - It "Initialise StartTime" { - - $Report.StartTime | - Should -Not -BeNullOrEmpty - - } - - It "Initialise EndTime à null" { - - $Report.EndTime | - Should -BeNullOrEmpty - - } - - It "Initialise Duration à zéro" { - - $Report.Duration | - Should -Be ([TimeSpan]::Zero) - - } - - It "Initialise les collections" { - - $Report.Files.Count | - Should -Be 0 - - $Report.Replacements.Count | - Should -Be 0 - - $Report.Messages.Count | - Should -Be 0 - - $Report.Warnings.Count | - Should -Be 0 - - $Report.Errors.Count | - Should -Be 0 - - } - - } - - Context "Test-Report" { - - BeforeEach { - - $Report = New-Report - - } - - It "Retourne True pour un rapport valide" { - - Test-Report ` - -Report $Report | - Should -BeTrue - - } - - - It "Retourne False si une propriété est absente" { - - $Invalid = [PSCustomObject]@{ - - ReportId = [guid]::NewGuid() - - } - - Test-Report ` - -Report $Invalid | - Should -BeFalse - - } - - It "Retourne False avec un objet vide" { - - Test-Report ` - -Report ([PSCustomObject]@{}) | - Should -BeFalse - - } - - } - - Context "Clear-Report" { - - BeforeEach { - - $Report = New-Report - - Add-ReportMessage ` - -Report $Report ` - -Text "Message" - - Add-ReportWarning ` - -Report $Report ` - -Text "Warning" - - Add-ReportError ` - -Report $Report ` - -Text "Error" - - Add-ReportFile ` - -Report $Report ` - -Path "C:\Temp\Test.txt" - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - Add-ReportReplacement ` - -Report $Report ` - -Replacement $Replacement - - Complete-Report ` - -Report $Report - - } - - It "Vide toutes les collections" { - - Clear-Report ` - -Report $Report - - $Report.Files.Count | - Should -Be 0 - - $Report.Replacements.Count | - Should -Be 0 - - $Report.Messages.Count | - Should -Be 0 - - $Report.Warnings.Count | - Should -Be 0 - - $Report.Errors.Count | - Should -Be 0 - - } - - It "Réinitialise EndTime" { - - Clear-Report ` - -Report $Report - - $Report.EndTime | - Should -BeNullOrEmpty - - } - - It "Réinitialise Duration" { - - Clear-Report ` - -Report $Report - - $Report.Duration | - Should -Be ([TimeSpan]::Zero) - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Clear-Report ` - -Report ([PSCustomObject]@{}) - - } | - Should -Throw - - } - - } - - Context "New-ReportEntry" { - - It "Crée un message" { - - $Entry = New-ReportEntry ` - -Type Message ` - -Text "Bonjour" - - $Entry | - Should -Not -BeNullOrEmpty - - } - - It "Initialise correctement les propriétés" { - - $Entry = New-ReportEntry ` - -Type Warning ` - -Text "Attention" - - $Entry.Type | - Should -Be "Warning" - - $Entry.Text | - Should -Be "Attention" - - $Entry.Time | - Should -Not -BeNullOrEmpty - - } - - It "Accepte le type Error" { - - { - - New-ReportEntry ` - -Type Error ` - -Text "Erreur" - - } | - Should -Not -Throw - - } - - It "Refuse un type invalide" { - - { - - New-ReportEntry ` - -Type "Invalid" ` - -Text "Test" - - } | - Should -Throw - - } - - } - Context "Add-ReportMessage" { - - BeforeEach { - - $Report = New-Report - - } - - It "Ajoute un message" { - - Add-ReportMessage ` - -Report $Report ` - -Text "Premier message" - - $Report.Messages.Count | - Should -Be 1 - - } - - It "Ajoute le bon texte" { - - Add-ReportMessage ` - -Report $Report ` - -Text "Bonjour" - - $Report.Messages[0].Text | - Should -Be "Bonjour" - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Add-ReportMessage ` - -Report ([PSCustomObject]@{}) ` - -Text "Test" - - } | - Should -Throw - - } - - } - - Context "Add-ReportWarning" { - - BeforeEach { - - $Report = New-Report - - } - - It "Ajoute un avertissement" { - - Add-ReportWarning ` - -Report $Report ` - -Text "Attention" - - $Report.Warnings.Count | - Should -Be 1 - - } - - It "Crée une entrée de type Warning" { - - Add-ReportWarning ` - -Report $Report ` - -Text "Attention" - - $Report.Warnings[0].Type | - Should -Be "Warning" - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Add-ReportWarning ` - -Report ([PSCustomObject]@{}) ` - -Text "Test" - - } | - Should -Throw - - } - - } - - Context "Add-ReportError" { - - BeforeEach { - - $Report = New-Report - - } - - It "Ajoute une erreur" { - - Add-ReportError ` - -Report $Report ` - -Text "Erreur" - - $Report.Errors.Count | - Should -Be 1 - - } - - It "Crée une entrée de type Error" { - - Add-ReportError ` - -Report $Report ` - -Text "Erreur" - - $Report.Errors[0].Type | - Should -Be "Error" - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Add-ReportError ` - -Report ([PSCustomObject]@{}) ` - -Text "Test" - - } | - Should -Throw - - } - - } - - Context "New-ReportFile" { - - It "Crée une entrée de fichier" { - - $File = New-ReportFile ` - -Path "C:\Temp\Test.txt" - - $File | - Should -Not -BeNullOrEmpty - - } - - It "Initialise correctement les propriétés" { - - $File = New-ReportFile ` - -Path "C:\Temp\Test.txt" - - $File.Path | - Should -Be "C:\Temp\Test.txt" - - $File.Time | - Should -Not -BeNullOrEmpty - - } - - It "Positionne Exists à False pour un fichier absent" { - - $File = New-ReportFile ` - -Path "C:\Temp\Inexistant.txt" - - $File.Exists | - Should -BeFalse - - } - - } - - Context "Add-ReportFile" { - - BeforeEach { - - $Report = New-Report - - } - - It "Ajoute un fichier" { - - Add-ReportFile ` - -Report $Report ` - -Path "C:\Temp\Test.txt" - - $Report.Files.Count | - Should -Be 1 - - } - - It "Conserve le chemin" { - - Add-ReportFile ` - -Report $Report ` - -Path "C:\Temp\Test.txt" - - $Report.Files[0].Path | - Should -Be "C:\Temp\Test.txt" - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Add-ReportFile ` - -Report ([PSCustomObject]@{}) ` - -Path "C:\Temp\Test.txt" - - } | - Should -Throw - - } - - } - - Context "Add-ReportReplacement" { - - BeforeEach { - - $Report = New-Report - - } - - It "Ajoute un remplacement" { - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - Add-ReportReplacement ` - -Report $Report ` - -Replacement $Replacement - - $Report.Replacements.Count | - Should -Be 1 - - } - - It "Conserve le même objet" { - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - Add-ReportReplacement ` - -Report $Report ` - -Replacement $Replacement - - $Report.Replacements[0] | - Should -Be $Replacement - - } - - It "Lève une exception avec un remplacement invalide" { - - { - - Add-ReportReplacement ` - -Report $Report ` - -Replacement ([PSCustomObject]@{}) - - } | - Should -Throw - - } - - It "Lève une exception avec un rapport invalide" { - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - { - - Add-ReportReplacement ` - -Report ([PSCustomObject]@{}) ` - -Replacement $Replacement - - } | - Should -Throw - - } - - } - Context "Get-ReportMessages" { - - BeforeEach { - - $Report = New-Report - - Add-ReportMessage ` - -Report $Report ` - -Text "Premier" - - Add-ReportMessage ` - -Report $Report ` - -Text "Second" - - } - - It "Retourne tous les messages" { - - $Messages = Get-ReportMessages ` - -Report $Report - - $Messages.Count | - Should -Be 2 - - } - - It "Retourne les messages dans l'ordre d'ajout" { - - $Messages = Get-ReportMessages ` - -Report $Report - - $Messages[0].Text | - Should -Be "Premier" - - $Messages[1].Text | - Should -Be "Second" - - } - - } - - Context "Get-ReportWarnings" { - - BeforeEach { - - $Report = New-Report - - Add-ReportWarning ` - -Report $Report ` - -Text "Attention" - - } - - It "Retourne les avertissements" { - - $Warnings = Get-ReportWarnings ` - -Report $Report - - $Warnings.Count | - Should -Be 1 - - } - - It "Retourne le bon type" { - - $Warnings = Get-ReportWarnings ` - -Report $Report - - $Warnings[0].Type | - Should -Be "Warning" - - } - - } - - Context "Get-ReportErrors" { - - BeforeEach { - - $Report = New-Report - - Add-ReportError ` - -Report $Report ` - -Text "Erreur" - - } - - It "Retourne les erreurs" { - - $Errors = Get-ReportErrors ` - -Report $Report - - $Errors.Count | - Should -Be 1 - - } - - It "Retourne le bon type" { - - $Errors = Get-ReportErrors ` - -Report $Report - - $Errors[0].Type | - Should -Be "Error" - - } - - } - - Context "Get-ReportFiles" { - - BeforeEach { - - $Report = New-Report - - Add-ReportFile ` - -Report $Report ` - -Path "C:\Temp\Test.txt" - - } - - It "Retourne les fichiers" { - - $Files = Get-ReportFiles ` - -Report $Report - - $Files.Count | - Should -Be 1 - - } - - It "Retourne le bon chemin" { - - $Files = Get-ReportFiles ` - -Report $Report - - $Files[0].Path | - Should -Be "C:\Temp\Test.txt" - - } - - } - - Context "Get-ReportReplacements" { - - BeforeEach { - - $Report = New-Report - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - Add-ReportReplacement ` - -Report $Report ` - -Replacement $Replacement - - } - - It "Retourne les remplacements" { - - $Replacements = Get-ReportReplacements ` - -Report $Report - - $Replacements.Count | - Should -Be 1 - - } - - It "Retourne le même objet" { - - $Replacements = Get-ReportReplacements ` - -Report $Report - - $Replacements[0].Original | - Should -Be "Hello" - - } - - } - - Context "Complete-Report" { - - BeforeEach { - - $Report = New-Report - - } - - It "Renseigne EndTime" { - - Complete-Report ` - -Report $Report - - $Report.EndTime | - Should -Not -BeNullOrEmpty - - } - - It "Calcule une durée positive" { - - Start-Sleep -Milliseconds 100 - - Complete-Report ` - -Report $Report - - $Report.Duration.TotalMilliseconds | - Should -BeGreaterThan 0 - - } - - It "Empêche de terminer deux fois le rapport" { - - Complete-Report ` - -Report $Report - - { - - Complete-Report ` - -Report $Report - - } | - Should -Throw - - } - - } - - Context "Get-ReportDuration" { - - It "Retourne une durée pendant l'exécution" { - - $Report = New-Report - - Start-Sleep -Milliseconds 100 - - $Duration = Get-ReportDuration ` - -Report $Report - - $Duration.TotalMilliseconds | - Should -BeGreaterThan 0 - - } - - It "Retourne la durée finale après Complete-Report" { - - $Report = New-Report - - Start-Sleep -Milliseconds 100 - - Complete-Report ` - -Report $Report - - $Duration = Get-ReportDuration ` - -Report $Report - - $Duration | - Should -Be $Report.Duration - - } - - } - - Context "Get-ReportStatistics" { - - BeforeEach { - - $Report = New-Report - - Add-ReportMessage ` - -Report $Report ` - -Text "Message" - - Add-ReportWarning ` - -Report $Report ` - -Text "Warning" - - Add-ReportError ` - -Report $Report ` - -Text "Error" - - Add-ReportFile ` - -Report $Report ` - -Path "C:\Temp\Test.txt" - - $Replacement = New-Replacement ` - -Start 0 ` - -End 5 ` - -Original "Hello" ` - -Replacement "Bonjour" - - Add-ReportReplacement ` - -Report $Report ` - -Replacement $Replacement - - } - - It "Retourne les bonnes statistiques" { - - $Stats = Get-ReportStatistics ` - -Report $Report - - $Stats.Files | - Should -Be 1 - - $Stats.Replacements | - Should -Be 1 - - $Stats.Messages | - Should -Be 1 - - $Stats.Warnings | - Should -Be 1 - - $Stats.Errors | - Should -Be 1 - - } - - It "Indique que le rapport n'est pas terminé" { - - $Stats = Get-ReportStatistics ` - -Report $Report - - $Stats.Completed | - Should -BeFalse - - } - - It "Indique que le rapport est terminé" { - - Complete-Report ` - -Report $Report - - $Stats = Get-ReportStatistics ` - -Report $Report - - $Stats.Completed | - Should -BeTrue - - } - - } - Context "Export-ReportJson" { - - BeforeEach { - - $Report = New-Report - - Add-ReportMessage ` - -Report $Report ` - -Text "Message de test" - - $OutputFile = Join-Path ` - $TestDrive ` - "Report.json" - - } - - It "Crée le fichier JSON" { - - Export-ReportJson ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - Test-Path ` - -LiteralPath $OutputFile | - Should -BeTrue - - } - - It "Produit un JSON valide" { - - Export-ReportJson ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - { - - Get-Content ` - -LiteralPath $OutputFile ` - -Raw | - ConvertFrom-Json - - } | - Should -Not -Throw - - } - - It "Contient les propriétés attendues" { - - Export-ReportJson ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - $Json = Get-Content ` - -LiteralPath $OutputFile ` - -Raw | - ConvertFrom-Json - - $Json.ReportId | - Should -Not -BeNullOrEmpty - - $Json.Messages.Count | - Should -Be 1 - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Export-ReportJson ` - -Report ([PSCustomObject]@{}) ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - } | - Should -Throw - - } - - } - - Context "Export-ReportText" { - - BeforeEach { - - $Report = New-Report - - Add-ReportMessage ` - -Report $Report ` - -Text "Message" - - Add-ReportWarning ` - -Report $Report ` - -Text "Attention" - - Add-ReportError ` - -Report $Report ` - -Text "Erreur" - - $OutputFile = Join-Path ` - $TestDrive ` - "Report.txt" - - } - - It "Crée le fichier texte" { - - Export-ReportText ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - Test-Path ` - -LiteralPath $OutputFile | - Should -BeTrue - - } - - It "Contient le titre du rapport" { - - Export-ReportText ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - $Content = Get-Content ` - -LiteralPath $OutputFile ` - -Raw - - $Content | - Should -Match "Rapport de migration PimsOS" - - } - - It "Contient les messages" { - - Export-ReportText ` - -Report $Report ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - $Content = Get-Content ` - -LiteralPath $OutputFile ` - -Raw - - $Content | - Should -Match "Message" - - $Content | - Should -Match "Attention" - - $Content | - Should -Match "Erreur" - - } - - It "Lève une exception avec un rapport invalide" { - - { - - Export-ReportText ` - -Report ([PSCustomObject]@{}) ` - -File ([System.IO.FileInfo]::new($OutputFile)) - - } | - Should -Throw - - } - - } - - Context "Exports du module" { - - It "Exporte toutes les fonctions attendues" { - - $Module = Get-Module Report - - $Expected = @( - - "New-Report", - "Test-Report", - "Clear-Report", - "New-ReportEntry", - "Add-ReportMessage", - "Add-ReportWarning", - "Add-ReportError", - "New-ReportFile", - "Add-ReportFile", - "Add-ReportReplacement", - "Get-ReportMessages", - "Get-ReportWarnings", - "Get-ReportErrors", - "Get-ReportFiles", - "Get-ReportReplacements", - "Complete-Report", - "Get-ReportDuration", - "Get-ReportStatistics", - "Export-ReportJson", - "Export-ReportText" - - ) - - foreach ($Function in $Expected) - { - $Module.ExportedFunctions.Keys | - Should -Contain $Function - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Scanner.Tests.ps1 b/Tests/Legacy/Modules/Scanner.Tests.ps1 deleted file mode 100644 index a8dd81c..0000000 --- a/Tests/Legacy/Modules/Scanner.Tests.ps1 +++ /dev/null @@ -1,366 +0,0 @@ -<# -.SYNOPSIS - Tests unitaires du module Scanner. - -.DESCRIPTION - Vérifie les fonctions de découverte des fichiers du framework - de migration PimsOS. - -.NOTES - - Projet : PimsOS - Module : Scanner.Tests - Framework : Pester 5 - -#> - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $ModuleRoot = Join-Path $ProjectRoot "Tools\Migration\Modules" - - Import-Module ` - (Join-Path $ModuleRoot "Common.psm1") ` - -Force - - Import-Module ` - (Join-Path $ModuleRoot "Scanner.psm1") ` - -Force - -} - -Describe "Module Scanner" { - - Context "Chargement du module" { - - It "Le module Scanner est chargé" { - - Get-Module Scanner | - Should -Not -BeNullOrEmpty - - } - - } - - Context "Test-IsExcluded" { - - It "Retourne True pour un dossier exclu" { - - $File = [System.IO.FileInfo]::new( - (Join-Path (Get-ProjectRoot) ".git\config") - ) - - Test-IsExcluded ` - -File $File | - Should -BeTrue - - } - - It "Retourne False pour un fichier normal" { - - $File = [System.IO.FileInfo]::new( - (Join-Path (Get-ProjectRoot) "README.md") - ) - - Test-IsExcluded ` - -File $File | - Should -BeFalse - - } - - } - - Context "Get-ProjectFiles" { - - It "Retourne une collection" { - - $Files = Get-ProjectFiles - - $Files | - Should -Not -BeNullOrEmpty - - } - - It "Retourne uniquement des FileInfo" { - - $Files = Get-ProjectFiles - - foreach ($File in $Files) - { - $File | - Should -BeOfType ([System.IO.FileInfo]) - } - - } - - It "Retourne des fichiers PowerShell" { - - $Files = Get-ProjectFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -BeIn @( - ".ps1", - ".psm1", - ".psd1" - ) - } - - } - - It "Lève une exception sur un dossier inexistant" { - - { - Get-ProjectFiles ` - -Root "C:\___Impossible___" - } | - Should -Throw - - } - - } - - Context "Get-PowerShellFiles" { - - It "Retourne au moins un fichier" { - - $Files = Get-PowerShellFiles - - $Files.Count | - Should -BeGreaterThan 0 - - } - - It "Retourne uniquement des fichiers PowerShell" { - - $Files = Get-PowerShellFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -BeIn @( - ".ps1", - ".psm1", - ".psd1" - ) - } - - } - - } - - Context "Get-MarkdownFiles" { - - It "Retourne une collection" { - - $Files = Get-MarkdownFiles - - @($Files).Count | - Should -BeGreaterThan 0 - - } - - It "Retourne uniquement des fichiers Markdown" { - - $Files = Get-MarkdownFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -Be ".md" - } - - } - - It "Retourne une collection vide si le dossier n'existe pas" { - - $Files = Get-MarkdownFiles ` - -Root "C:\___Impossible___" - - $Files.Count | - Should -Be 0 - - } - - } - - Context "Get-JsonFiles" { - - It "Retourne une collection" { - - $Files = Get-JsonFiles - - @($Files).Count | - Should -BeGreaterThan 0 - - } - - It "Retourne uniquement des fichiers JSON" { - - $Files = Get-JsonFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -Be ".json" - } - - } - - It "Retourne une collection vide si le dossier n'existe pas" { - - $Files = Get-JsonFiles ` - -Root "C:\___Impossible___" - - $Files.Count | - Should -Be 0 - - } - - } - Context "Get-MigrationFiles" { - - It "Retourne une collection" { - - $Files = Get-MigrationFiles - - $Files | - Should -Not -BeNullOrEmpty - - } - - It "Ne retourne aucun fichier situé dans un dossier exclu" { - - $Files = Get-MigrationFiles - - foreach ($File in $Files) - { - Test-IsExcluded ` - -File $File | - Should -BeFalse - } - - } - - } - - Context "Get-RuleFiles" { - - It "Retourne une collection" { - - $Files = Get-RuleFiles - - @($Files).Count | - Should -BeGreaterThan 0 - - } - - It "Retourne uniquement des scripts PowerShell" { - - $Files = Get-RuleFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -Be ".ps1" - } - - } - - } - - Context "Get-ModuleFiles" { - - It "Retourne une collection" { - - $Files = Get-ModuleFiles - - @($Files).Count | - Should -BeGreaterThan 0 - - } - - It "Retourne uniquement des modules PowerShell" { - - $Files = Get-ModuleFiles - - foreach ($File in $Files) - { - $File.Extension | - Should -Be ".psm1" - } - - } - - } - - Context "Get-ProjectInventory" { - - It "Retourne un objet" { - - $Inventory = Get-ProjectInventory - - $Inventory | - Should -Not -BeNullOrEmpty - - } - - It "Contient toutes les propriétés attendues" { - - $Inventory = Get-ProjectInventory - - $Inventory.PowerShell | - Should -Not -BeNullOrEmpty - - $Inventory.Markdown | - Should -Not -BeNullOrEmpty - - $Inventory.Json | - Should -Not -BeNullOrEmpty - - $Inventory.Migration | - Should -Not -BeNullOrEmpty - - $Inventory.Rules | - Should -Not -BeNullOrEmpty - - $Inventory.Modules | - Should -Not -BeNullOrEmpty - - } - - } - - Context "Exports du module" { - - It "Exporte toutes les fonctions attendues" { - - $Module = Get-Module Scanner - - $Expected = @( - "Test-IsExcluded", - "Get-PowerShellFiles", - "Get-MarkdownFiles", - "Get-JsonFiles", - "Get-ProjectFiles", - "Get-MigrationFiles", - "Get-RuleFiles", - "Get-ModuleFiles", - "Get-ProjectInventory" - ) - - foreach ($Function in $Expected) - { - $Module.ExportedFunctions.Keys | - Should -Contain $Function - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Modules/Template.Tests.ps1 b/Tests/Legacy/Modules/Template.Tests.ps1 deleted file mode 100644 index 2770399..0000000 --- a/Tests/Legacy/Modules/Template.Tests.ps1 +++ /dev/null @@ -1,159 +0,0 @@ -<# -.SYNOPSIS - Gabarit des tests unitaires Pester pour PimsOS. - -.DESCRIPTION - Ce fichier sert de modèle pour tous les tests unitaires - des modules du projet PimsOS. - -.NOTES - - Projet : PimsOS - Framework: Pester 5 - Version : 1.0.0 - -#> - -#Requires -Modules Pester - -BeforeAll { - - Set-StrictMode -Version Latest - - #-------------------------------------------------- - # Détermination des chemins - #-------------------------------------------------- - - $ProjectRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent - - $ModuleName = '' - - $ModulePath = Join-Path ` - $ProjectRoot ` - "Modules\$ModuleName.psm1" - - #-------------------------------------------------- - # Vérification du module - #-------------------------------------------------- - - if (-not (Test-Path -LiteralPath $ModulePath)) - { - throw "Module introuvable : $ModulePath" - } - - #-------------------------------------------------- - # Chargement du module - #-------------------------------------------------- - - Import-Module ` - -Name $ModulePath ` - -Force ` - -ErrorAction Stop - - #-------------------------------------------------- - # Données communes aux tests - #-------------------------------------------------- - - $TestData = @{ - - # Ajouter ici les objets communs - # Exemple : - # - # Report = New-Report - # Replacement = New-Replacement ... - # - - } - -} - -AfterAll { - - Remove-Module ` - -Name $ModuleName ` - -ErrorAction SilentlyContinue - -} - -Describe $ModuleName { - - It 'Le module est chargé' { - - Get-Module -Name $ModuleName | - Should -Not -BeNullOrEmpty - - } - - InModuleScope $ModuleName { - - Describe '' { - - Context 'Avec des paramètres valides' { - - It 'Exécute correctement le scénario attendu' { - - #-------------------------------------------------- - # Arrange - # Prépare les données de test - #-------------------------------------------------- - - #-------------------------------------------------- - # Act - # Exécute la fonction - #-------------------------------------------------- - - #-------------------------------------------------- - # Assert - # Vérifie le résultat - #-------------------------------------------------- - - } - - } - - Context 'Avec des paramètres invalides' { - - It 'Lève une exception' { - - { - # Appel de la fonction - - } | Should -Throw - - } - - } - - Context 'Valeur retournée' { - - It 'Retourne le type attendu' { - - # Arrange - - # Act - - # Assert - - } - - } - - Context 'Cas limites' { - - It 'Gère correctement les cas particuliers' { - - # Arrange - - # Act - - # Assert - - } - - } - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Private/Import-MigrationRules.Tests.ps1 b/Tests/Legacy/Private/Import-MigrationRules.Tests.ps1 deleted file mode 100644 index 84a532e..0000000 --- a/Tests/Legacy/Private/Import-MigrationRules.Tests.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -# ========================================== -# Import-MigrationRules.Tests.ps1 -# ========================================== - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - Import-Module ` - (Join-Path $ProjectRoot "Tools\Migration\Modules\Common.psm1") ` - -Force - - . (Join-Path $ProjectRoot "Tools\Migration\Private\Import-MigrationRules.ps1") - -} - -Describe "Import-MigrationRules" { - - Context "Dossier Rules valide" { - - It "Retourne True lorsque le dossier Rules existe" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - - Import-MigrationRules -Context $Context | - Should -BeTrue - - } - - } - - Context "Dossier Rules absent" { - - It "Retourne False lorsque le dossier Rules est introuvable" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.MigrationPath = Join-Path $ProjectRoot "Inexistant" - - Import-MigrationRules -Context $Context | - Should -BeFalse - - } - - } - - Context "Type de retour" { - - It "Retourne un booléen" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - - $Result = Import-MigrationRules -Context $Context - - $Result | - Should -BeOfType ([System.Boolean]) - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Private/Test-MigrationProject.Tests.ps1 b/Tests/Legacy/Private/Test-MigrationProject.Tests.ps1 deleted file mode 100644 index 76486cb..0000000 --- a/Tests/Legacy/Private/Test-MigrationProject.Tests.ps1 +++ /dev/null @@ -1,135 +0,0 @@ -# ========================================== -# Test-MigrationProject.Tests.ps1 -# ========================================== - -BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - Import-Module ` - (Join-Path $ProjectRoot "Tools\Migration\Modules\Common.psm1") ` - -Force - - . (Join-Path $ProjectRoot "Tools\Migration\Private\Test-MigrationProject.ps1") - -} - -Describe "Test-MigrationProject" { - - Context "Projet valide" { - - It "Retourne True lorsque tous les dossiers existent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.ToolsPath = Join-Path $ProjectRoot "Tools" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Modules" - $Context.TestsPath = Join-Path $ProjectRoot "Tests" - - Test-MigrationProject -Context $Context | - Should -BeTrue - - } - - } - - Context "ProjectRoot" { - - It "Retourne False si ProjectRoot est absent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = "C:\Inexistant" - $Context.ToolsPath = Join-Path $ProjectRoot "Tools" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Modules" - $Context.TestsPath = Join-Path $ProjectRoot "Tests" - - Test-MigrationProject -Context $Context | - Should -BeFalse - - } - - } - - Context "ToolsPath" { - - It "Retourne False si ToolsPath est absent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.ToolsPath = Join-Path $ProjectRoot "Inexistant" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Modules" - $Context.TestsPath = Join-Path $ProjectRoot "Tests" - - Test-MigrationProject -Context $Context | - Should -BeFalse - - } - - } - - Context "MigrationPath" { - - It "Retourne False si MigrationPath est absent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.ToolsPath = Join-Path $ProjectRoot "Tools" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Inexistant" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Modules" - $Context.TestsPath = Join-Path $ProjectRoot "Tests" - - Test-MigrationProject -Context $Context | - Should -BeFalse - - } - - } - - Context "ModulesPath" { - - It "Retourne False si ModulesPath est absent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.ToolsPath = Join-Path $ProjectRoot "Tools" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Inexistant" - $Context.TestsPath = Join-Path $ProjectRoot "Tests" - - Test-MigrationProject -Context $Context | - Should -BeFalse - - } - - } - - Context "TestsPath" { - - It "Retourne False si TestsPath est absent" { - - $Context = New-MigrationContext - - $Context.ProjectRoot = $ProjectRoot - $Context.ToolsPath = Join-Path $ProjectRoot "Tools" - $Context.MigrationPath = Join-Path $ProjectRoot "Tools\Migration" - $Context.ModulesPath = Join-Path $ProjectRoot "Tools\Migration\Modules" - $Context.TestsPath = Join-Path $ProjectRoot "Inexistant" - - Test-MigrationProject -Context $Context | - Should -BeFalse - - } - - } - -} \ No newline at end of file diff --git a/Tests/Legacy/Rules/LoggerRule.Tests.ps1 b/Tests/Legacy/Rules/LoggerRule.Tests.ps1 deleted file mode 100644 index 737c983..0000000 --- a/Tests/Legacy/Rules/LoggerRule.Tests.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -using module "..\..\..\Tools\Migration\Migration.psd1" - -<# -.SYNOPSIS - Tests unitaires de la règle Logger. - -.DESCRIPTION - Vérifie que la règle Logger est correctement construite - et respecte le contrat d'une MigrationRule. - -.NOTES - Projet : PimsOS - Auteur : Pims -#> - -Describe "Rules\Logger.ps1" { - - BeforeAll { - - $ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) - - $Rule = . ( - Join-Path $ProjectRoot "Tools\Migration\Rules\Logger.ps1" - ) - - } - - Context "Construction de la règle" { - - It "Retourne un objet MigrationRule" { - - $Rule.ObjectType | - Should -Be "MigrationRule" - - } - - It "Possède le nom 'Logger'" { - - $Rule.Name | Should -Be "Logger" - - } - - It "Possède une description" { - - $Rule.Description | Should -Not -BeNullOrEmpty - - } - - It "Est activée" { - - $Rule.Enabled | Should -BeTrue - - } - - It "Possède une priorité" { - - $Rule.Priority | Should -BeGreaterThan 0 - - } - - It "Possède un ScriptBlock" { - - $Rule.Script | Should -BeOfType ScriptBlock - - } - - } - -} \ No newline at end of file diff --git a/Tests/Tools/Invoke-PimsOSDiagnostics.ps1 b/Tests/Tools/Invoke-PimsOSDiagnostics.ps1 new file mode 100644 index 0000000..aaba3c8 --- /dev/null +++ b/Tests/Tools/Invoke-PimsOSDiagnostics.ps1 @@ -0,0 +1,748 @@ +#requires -Version 7.0 +<# +.SYNOPSIS + Diagnostic statique et sécurisé du dépôt PimsOS. + +.DESCRIPTION + Analyse les tests Pester avant exécution afin de distinguer : + SAFE : test ne présentant pas d'appel build réel détectable. + BUILD-CAPABLE : appel à une opération de build potentiellement réelle. + UNKNOWN : analyse statique insuffisante -> jamais exécuté automatiquement. + + IMPORTANT : + - -InventoryOnly n'exécute aucun test. + - En mode -Unit, les BUILD-CAPABLE et UNKNOWN sont exclus. + - -BuildValidation exige explicitement -AllowBuild. + - Le script ne lance jamais Initialize-PimsOS pendant l'inventaire. + +.EXAMPLE + .\Invoke-PimsOSDiagnostics.ps1 -Unit -InventoryOnly + +.EXAMPLE + .\Invoke-PimsOSDiagnostics.ps1 -Unit -ExplainFailures + +.EXAMPLE + .\Invoke-PimsOSDiagnostics.ps1 -Integration -InventoryOnly -ExplainFailures + +.EXAMPLE + .\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -InventoryOnly -ExplainFailures + +.EXAMPLE + .\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -ExplainFailures +#> + +[CmdletBinding()] +param( + [switch]$Unit, + [switch]$Integration, + [switch]$BuildValidation, + [switch]$AllowBuild, + [switch]$InventoryOnly, + [switch]$ExplainFailures +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +$TestsRoot = Join-Path $ProjectRoot 'Tests' +$ReportsRoot = Join-Path $TestsRoot 'Reports\Diagnostics' + +New-Item -ItemType Directory -Path $ReportsRoot -Force | Out-Null + +if (-not ($Unit -or $Integration -or $BuildValidation)) { + $Unit = $true +} + +if (($Unit -and $Integration) -or ($BuildValidation -and ($Unit -or $Integration))) { + throw 'Choisissez un seul mode : -Unit, -Integration ou -BuildValidation.' +} + +if ($BuildValidation -and -not $AllowBuild) { + throw 'Le mode -BuildValidation exige explicitement -AllowBuild.' +} + +function Get-PesterVersion { + $module = Get-Module -ListAvailable Pester | + Sort-Object Version -Descending | + Select-Object -First 1 + + if ($module) { return [string]$module.Version } + return 'Non installé' +} + +function Get-TestFiles { + param([string]$Root) + + $files = [System.Collections.Generic.List[object]]::new() + + if ($Unit -or $BuildValidation) { + $unitRoot = Join-Path $Root 'Unit' + if (Test-Path $unitRoot) { + Get-ChildItem $unitRoot -Recurse -Filter '*.Tests.ps1' -File | + ForEach-Object { $files.Add($_) } + } + } + + if ($Integration -or $BuildValidation) { + $integrationRoot = Join-Path $Root 'Integration' + if (Test-Path $integrationRoot) { + Get-ChildItem $integrationRoot -Recurse -Filter '*.Tests.ps1' -File | + ForEach-Object { $files.Add($_) } + } + + $rootIntegration = Join-Path $Root 'Integration.Tests.ps1' + if (Test-Path $rootIntegration) { + $files.Add((Get-Item $rootIntegration)) + } + } + + return @($files | Sort-Object FullName -Unique) +} + +function Get-FunctionBody { + param( + [string]$Text, + [string]$Name + ) + + # Analyse approximative suffisante pour le classement de sécurité. + # On extrait les blocs Mock associés au nom demandé. + $escaped = [regex]::Escape($Name) + $pattern = "(?is)Mock\s+$escaped\b.*?(?=(?:\r?\n\s*Mock\s+)|(?:\r?\n\s*It\s+)|(?:\r?\n\s*Context\s+)|\z)" + return @([regex]::Matches($Text, $pattern) | ForEach-Object { $_.Value }) +} + +function Test-CommandActuallyMocked { + param( + [string]$Text, + [string]$Name + ) + + $blocks = @(Get-FunctionBody -Text $text -Name $Name) + return ($blocks.Count -gt 0) +} + +function Test-ForcedDryRun { + param([string]$Text) + + return ( + $Text -match '(?im)\bDryRun\s*=\s*\$true\b' -or + $Text -match '(?im)\bCreateISO\s*=\s*\$false\b' + ) +} + +function Get-BuildRisk { + param( + [System.IO.FileInfo]$File + ) + + $text = Get-Content -LiteralPath $File.FullName -Raw -ErrorAction Stop + + $dangerous = @( + 'Initialize-PimsOS', + 'Invoke-BuildPipeline', + 'Complete-Build', + 'Mount-Wim', + 'Mount-WindowsImage', + 'Dismount-Wim', + 'oscdimg' + ) + + $hits = [System.Collections.Generic.List[string]]::new() + $unmocked = [System.Collections.Generic.List[string]]::new() + + foreach ($name in $dangerous) { + + $escaped = [regex]::Escape($name) + + if ($text -notmatch "(?im)\b$escaped\b") { + continue + } + + $hits.Add($name) + + # -------------------------------------------------- + # 1. Appel explicite + # -------------------------------------------------- + + # Détecte également les appels avec affectation : + # Initialize-PimsOS ... + # $Result = Initialize-PimsOS ... + # & Initialize-PimsOS ... + $invokePattern = "(?im)^\s*(?:(?:\$[\w:.-]+|\$\{[^}]+\})\s*=\s*)?(?:&\s*)?$escaped\b" + $isInvoked = $text -match $invokePattern + + if (-not $isInvoked) { + continue + } + + # -------------------------------------------------- + # 2. Mock Pester explicite + # -------------------------------------------------- + + $mocked = Test-CommandActuallyMocked ` + -Text $text ` + -Name $name + + # -------------------------------------------------- + # Fonction directement testée + # + # Un fichier X.Tests.ps1 peut naturellement invoquer + # X sans que cela constitue un build réel. + # + # On analyse ensuite ses dépendances dangereuses, + # mais on ne classe pas X lui-même comme dangereux. + # -------------------------------------------------- + + $functionUnderTest = $File.Name -match ( + '^{0}\.Tests\.ps1$' -f [regex]::Escape($name) + ) + + if ($functionUnderTest) { + continue + } + + # -------------------------------------------------- + # Initialize-PimsOS est une API de build. + # + # Un test d'intégration qui invoque réellement + # Initialize-PimsOS est BUILD-CAPABLE. + # + # Un Mock Pester explicite neutralise cet appel. + # -------------------------------------------------- + + if ($name -eq 'Initialize-PimsOS') { + + if ($mocked) { + continue + } + + if ($File.FullName -match '\\Tests\\Integration(\\|\.Tests\.ps1$)') { + $unmocked.Add($name) + continue + } + } + + if ($mocked) { + continue + } + + # -------------------------------------------------- + # 3. Neutralisation par faux outil / TestDrive + # + # Exemple : + # oscdimg.exe créé dans $TestDrive + # PATH temporairement modifié + # -------------------------------------------------- + + $fakeTool = $false + + if ( + $name -eq 'oscdimg' -and + $text -match '(?is)\$TestDrive' -and + $text -match '(?im)oscdimg(?:\.exe)?' -and + ( + $text -match '(?im)Set-Content.*oscdimg' -or + $text -match '(?im)New-Item.*oscdimg' -or + $text -match '(?im)Out-File.*oscdimg' -or + $text -match '(?im)\$env:PATH.*\$TestDrive' + ) + ) { + $fakeTool = $true + } + + if ($fakeTool) { + continue + } + + # -------------------------------------------------- + # 4. Appel neutralisé explicitement par DryRun + # -------------------------------------------------- + + if (Test-ForcedDryRun -Text $text) { + continue + } + + # -------------------------------------------------- + # 5. Appel réellement non neutralisé + # -------------------------------------------------- + + $unmocked.Add($name) + } + + $dryRun = Test-ForcedDryRun -Text $text + + $relative = $File.FullName.Substring( + $ProjectRoot.Length + ).TrimStart('\') + + # ------------------------------------------------------ + # SAFE + # ------------------------------------------------------ + + if ($unmocked.Count -eq 0) { + + return [pscustomobject]@{ + File = $File + RelativePath = $relative + Classification = 'SAFE' + Reasons = @( + 'Aucun appel build non neutralisé détecté.' + ) + Dangerous = @($hits) + Unmocked = @() + } + } + + # ------------------------------------------------------ + # BUILD-CAPABLE + # + # Un appel réel à Initialize-PimsOS est suffisant. + # ------------------------------------------------------ + + if ($unmocked -contains 'Initialize-PimsOS') { + + return [pscustomobject]@{ + File = $File + RelativePath = $relative + Classification = 'BUILD-CAPABLE' + Reasons = @( + "Appel réel non neutralisé : Initialize-PimsOS." + ) + Dangerous = @($hits) + Unmocked = @($unmocked) + } + } + + # ------------------------------------------------------ + # Les tests d'intégration appelant une opération build + # non neutralisée restent BUILD-CAPABLE. + # ------------------------------------------------------ + + if ( + $unmocked.Count -gt 0 -and + $File.FullName -match '\\Tests\\Integration(\\|\.Tests\.ps1$)' + ) { + + return [pscustomobject]@{ + File = $File + RelativePath = $relative + Classification = 'BUILD-CAPABLE' + Reasons = @( + "Appel(s) potentiellement réel(s) : $($unmocked -join ', ')." + ) + Dangerous = @($hits) + Unmocked = @($unmocked) + } + } + + # ------------------------------------------------------ + # UNKNOWN + # + # Cas où le scanner détecte une opération dangereuse + # mais ne peut pas établir suffisamment clairement + # si elle est neutralisée. + # ------------------------------------------------------ + + return [pscustomobject]@{ + File = $File + RelativePath = $relative + Classification = 'UNKNOWN' + Reasons = @( + "Appel potentiellement dangereux détecté sans preuve suffisante de neutralisation : $($unmocked -join ', ')." + ) + Dangerous = @($hits) + Unmocked = @($unmocked) + } +} + +function Get-TestSelection { + param([object[]]$Inventory) + + switch ($true) { + $Unit { + return @($Inventory | Where-Object Classification -eq 'SAFE') + } + + $Integration { + return @( + $Inventory | + Where-Object { + $_.Classification -eq 'SAFE' + } + ) + } + + $BuildValidation { + return @( + $Inventory | + Where-Object { + $_.Classification -eq 'BUILD-CAPABLE' + } + ) + } + + default { + return @() + } + } +} + +function Get-PesterFailureDetails { + param([object]$PesterResult) + + $failures = [System.Collections.Generic.List[object]]::new() + + if ($null -eq $PesterResult) { + return @() + } + + $failedItems = @() + if ($PesterResult.PSObject.Properties['Failed']) { + $failedItems = @($PesterResult.Failed) + } + + foreach ($failure in $failedItems) { + $name = $null + foreach ($property in @('ExpandedName', 'Name', 'ParameterizedSuiteName')) { + if ($failure.PSObject.Properties[$property] -and $failure.$property) { + $name = [string]$failure.$property + break + } + } + if (-not $name) { $name = 'Test Pester sans nom' } + + $path = $null + if ($failure.PSObject.Properties['ScriptBlock'] -and $failure.ScriptBlock) { + try { $path = [string]$failure.ScriptBlock.File } + catch { $path = $null } + } + if (-not $path -and $failure.PSObject.Properties['Block'] -and $failure.Block) { + try { + if ($failure.Block.PSObject.Properties['Path']) { + $path = [string]$failure.Block.Path + } + } + catch { $path = $null } + } + + $message = $null + $line = $null + $position = $null + + $errorRecord = $null + foreach ($property in @('ErrorRecord', 'ErrorRecordException')) { + if ($failure.PSObject.Properties[$property] -and $failure.$property) { + $errorRecord = $failure.$property + break + } + } + + if ($errorRecord) { + try { + if ($errorRecord.PSObject.Properties['Exception'] -and $errorRecord.Exception) { + $message = [string]$errorRecord.Exception.Message + } + elseif ($errorRecord.PSObject.Properties['Message']) { + $message = [string]$errorRecord.Message + } + } + catch { } + + try { + if ($errorRecord.PSObject.Properties['InvocationInfo'] -and $errorRecord.InvocationInfo) { + $invocation = $errorRecord.InvocationInfo + if ($invocation.PSObject.Properties['ScriptName'] -and $invocation.ScriptName) { + $path = [string]$invocation.ScriptName + } + if ($invocation.PSObject.Properties['ScriptLineNumber']) { + $line = [int]$invocation.ScriptLineNumber + } + if ($invocation.PSObject.Properties['PositionMessage']) { + $position = [string]$invocation.PositionMessage + } + } + } + catch { } + } + + if (-not $message -and $failure.PSObject.Properties['ErrorRecord']) { + try { $message = [string]$failure.ErrorRecord } + catch { } + } + if (-not $message -and $failure.PSObject.Properties['Result']) { + try { $message = [string]$failure.Result } + catch { } + } + if (-not $message) { $message = 'Erreur Pester non détaillée.' } + + if (-not $path) { + try { + if ($failure.PSObject.Properties['Container'] -and $failure.Container) { + if ($failure.Container.PSObject.Properties['Item']) { + $path = [string]$failure.Container.Item + } + } + } + catch { } + } + + $relativePath = $path + if ($path -and $path.StartsWith($ProjectRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + $relativePath = $path.Substring($ProjectRoot.Length).TrimStart('\') + } + + $failures.Add([ordered]@{ + Path = $relativePath + Name = $name + Message = $message + Line = $line + Position = $position + }) + } + + return @($failures) +} + +function Write-Inventory { + param([object[]]$Inventory) + + Write-Host '' + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host ' PimsOS DIAGNOSTICS' -ForegroundColor Cyan + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host "Projet : $ProjectRoot" + Write-Host "PowerShell : $($PSVersionTable.PSVersion)" + Write-Host "Pester : $(Get-PesterVersion)" + + $mode = if ($BuildValidation) { 'BuildValidation' } + elseif ($Integration) { 'Integration' } + else { 'Unit' } + + Write-Host "Mode : $mode" + + if ($BuildValidation) { + Write-Host 'Build réel : AUTORISÉ' -ForegroundColor Yellow + } + else { + Write-Host 'Build réel : INTERDIT' -ForegroundColor Green + } + + $unitCount = @($Inventory | Where-Object RelativePath -like 'Tests\Unit\*').Count + $intCount = @($Inventory | Where-Object RelativePath -like 'Tests\Integration*').Count + $buildCount = @($Inventory | Where-Object Classification -eq 'BUILD-CAPABLE').Count + $unknownCount = @($Inventory | Where-Object Classification -eq 'UNKNOWN').Count + + Write-Host "Unit : $unitCount" + Write-Host "Integration : $intCount" + Write-Host "Build-capable: $buildCount" + Write-Host "Unknown : $unknownCount" + + Write-Host '' + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host ' INVENTAIRE' -ForegroundColor Cyan + Write-Host '============================================================' + + foreach ($item in $Inventory) { + $label = "[{0}] {1}" -f $item.Classification, $item.RelativePath + + switch ($item.Classification) { + 'SAFE' { + Write-Host $label -ForegroundColor Green + } + 'BUILD-CAPABLE' { + Write-Host $label -ForegroundColor Yellow + } + 'UNKNOWN' { + Write-Host $label -ForegroundColor Red + } + } + + if ($ExplainFailures -and $item.Classification -ne 'SAFE') { + foreach ($reason in $item.Reasons) { + Write-Host " -> $reason" + } + } + } +} + +$files = @(Get-TestFiles -Root $TestsRoot) +$inventory = @( + foreach ($file in @($files)) { + Get-BuildRisk -File $file + } +) + +Write-Inventory -Inventory $inventory + +$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' +$reportMd = Join-Path $ReportsRoot "Diagnostics-$timestamp.md" +$reportJson = Join-Path $ReportsRoot "Diagnostics-$timestamp.json" + +$selected = @( + foreach ($item in @(Get-TestSelection -Inventory $inventory)) { + + if ($null -eq $item) { + continue + } + + if (-not $item.PSObject.Properties['RelativePath']) { + continue + } + + $item + } +) + +$report = [ordered]@{ + Date = (Get-Date).ToString('s') + Project = $ProjectRoot + PowerShell = [string]$PSVersionTable.PSVersion + Pester = Get-PesterVersion + Mode = if ($BuildValidation) { 'BuildValidation' } elseif ($Integration) { 'Integration' } else { 'Unit' } + AllowBuild = [bool]$AllowBuild + InventoryOnly = [bool]$InventoryOnly + TotalFiles = $inventory.Count + Safe = @($inventory | Where-Object Classification -eq 'SAFE').Count + BuildCapable = @($inventory | Where-Object Classification -eq 'BUILD-CAPABLE').Count + Unknown = @($inventory | Where-Object Classification -eq 'UNKNOWN').Count + Selected = @( + foreach ($item in $selected) { + [string]$item.RelativePath + } + ) + Failures = @() + Inventory = @( + $inventory | ForEach-Object { + [ordered]@{ + Path = $_.RelativePath + Classification = $_.Classification + Reasons = @($_.Reasons) + Dangerous = @($_.Dangerous) + Unmocked = @($_.Unmocked) + } + } + ) +} + +$failureDetails = @() + +$md = [System.Text.StringBuilder]::new() +[void]$md.AppendLine('# PimsOS Diagnostics') +[void]$md.AppendLine('') +[void]$md.AppendLine("- Date : $($report.Date)") +[void]$md.AppendLine("- Mode : $($report.Mode)") +[void]$md.AppendLine("- Build réel autorisé : $($report.AllowBuild)") +[void]$md.AppendLine("- Fichiers analysés : $($report.TotalFiles)") +[void]$md.AppendLine("- SAFE : $($report.Safe)") +[void]$md.AppendLine("- BUILD-CAPABLE : $($report.BuildCapable)") +[void]$md.AppendLine("- UNKNOWN : $($report.Unknown)") +[void]$md.AppendLine('') +[void]$md.AppendLine('## Classification') +[void]$md.AppendLine('') + +foreach ($item in $inventory) { + [void]$md.AppendLine("- **$($item.Classification)** — $($item.RelativePath)") + foreach ($reason in $item.Reasons) { + [void]$md.AppendLine(" - $reason") + } +} + +if ($InventoryOnly) { + Write-Host '' + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host ' INVENTAIRE TERMINÉ' -ForegroundColor Cyan + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host 'Aucun test exécuté.' + Write-Host 'Aucun build exécuté.' +} +else { + if ($selected.Count -eq 0) { + Write-Host '' + Write-Host 'Aucun test sélectionné pour ce mode.' -ForegroundColor Yellow + } + else { + Write-Host '' + Write-Host '============================================================' -ForegroundColor Cyan + Write-Host ' EXÉCUTION' -ForegroundColor Cyan + Write-Host '============================================================' + + $paths = @( + foreach ($item in $selected) { + + if ( + $item.PSObject.Properties['File'] -and + $null -ne $item.File + ) { + [string]$item.File.FullName + } + } + ) + + # BuildValidation ne sélectionne QUE les tests explicitement classés + # BUILD-CAPABLE. Les UNKNOWN restent toujours bloqués. + if ($BuildValidation) { + Write-Host "Tests build-capable sélectionnés : $($paths.Count)" -ForegroundColor Yellow + } + else { + Write-Host "Tests SAFE sélectionnés : $($paths.Count)" -ForegroundColor Green + } + + $pesterResult = Invoke-Pester -Path $paths -PassThru -Output Detailed + $failureDetails = @(Get-PesterFailureDetails -PesterResult $pesterResult) + $report.Failures = @($failureDetails) + + [void]$md.AppendLine('') + [void]$md.AppendLine('## Résultats') + [void]$md.AppendLine('') + [void]$md.AppendLine("| Total | Pass | Fail | Skip |") + [void]$md.AppendLine("|---:|---:|---:|---:|") + [void]$md.AppendLine("| $($pesterResult.TotalCount) | $($pesterResult.PassedCount) | $($pesterResult.FailedCount) | $($pesterResult.SkippedCount) |") + + if ($failureDetails.Count -gt 0) { + [void]$md.AppendLine('') + [void]$md.AppendLine('## Échecs détaillés') + [void]$md.AppendLine('') + foreach ($failure in $failureDetails) { + [void]$md.AppendLine("### $($failure.Name)") + if ($failure.Path) { [void]$md.AppendLine("- Fichier : $($failure.Path)") } + if ($failure.Line) { [void]$md.AppendLine("- Ligne : $($failure.Line)") } + [void]$md.AppendLine("- Message : $($failure.Message)") + if ($failure.Position) { + [void]$md.AppendLine('') + [void]$md.AppendLine('```text') + [void]$md.AppendLine($failure.Position) + [void]$md.AppendLine('```') + } + [void]$md.AppendLine('') + } + } + } +} + +$report | ConvertTo-Json -Depth 10 | + Set-Content -LiteralPath $reportJson -Encoding utf8NoBOM + +$md.ToString() | + Set-Content -LiteralPath $reportMd -Encoding utf8NoBOM + +Write-Host '' +Write-Host '============================================================' -ForegroundColor Cyan +Write-Host ' RAPPORTS' -ForegroundColor Cyan +Write-Host '============================================================' +Write-Host "Markdown : $reportMd" +Write-Host "JSON : $reportJson" + +if (-not $InventoryOnly -and $selected.Count -gt 0 -and $pesterResult.FailedCount -gt 0) { + exit 1 +} + +$unknownItems = @( + $inventory | + Where-Object { $_.Classification -eq 'UNKNOWN' } +) + +if ($unknownItems.Count -gt 0 -and $BuildValidation) { + Write-Warning 'Des tests UNKNOWN existent : ils restent volontairement exclus.' +} diff --git a/Tests/Tools/README.md b/Tests/Tools/README.md new file mode 100644 index 0000000..0a88397 --- /dev/null +++ b/Tests/Tools/README.md @@ -0,0 +1,85 @@ +# PimsOS Diagnostics Tool v8 + +Outil de diagnostic statique et sécurisé des tests Pester de PimsOS. Il permet d'inventorier les tests avant exécution et de séparer les tests normaux des tests susceptibles de déclencher un Build réel. + +## Objectif + +Le diagnostic ne remplace pas Pester. Il constitue un **garde-fou avant Pester**. + +Classes : + +- `SAFE` : aucun appel Build dangereux non neutralisé détecté par l'analyse statique ; +- `BUILD-CAPABLE` : appel potentiellement réel à une opération Build/WIM/ISO/DISM ; +- `UNKNOWN` : preuve insuffisante de neutralisation. + +L'analyse est volontairement conservatrice. + +## Sécurité + +- Le mode par défaut est `Unit`. +- `-InventoryOnly` n'exécute aucun test. +- `-Unit` et `-Integration` n'exécutent que les fichiers classés `SAFE`. +- Les fichiers `BUILD-CAPABLE` et `UNKNOWN` restent exclus des modes normaux. +- `-BuildValidation` ne sélectionne que les fichiers `BUILD-CAPABLE`. +- `-BuildValidation` exige explicitement `-AllowBuild`. +- Un Build réel n'est jamais déclenché par un inventaire. + +## Commandes de référence + +### Inventaire Unit sans exécution + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Unit -InventoryOnly -ExplainFailures +``` + +### Diagnostic Unit + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Unit -ExplainFailures +``` + +### Inventaire Integration sans exécution + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Integration -InventoryOnly -ExplainFailures +``` + +### Diagnostic Integration + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -Integration -ExplainFailures +``` + +### Inventaire BuildValidation — autorisation explicite + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -InventoryOnly -ExplainFailures +``` + +### Validation Build réelle + +```powershell +.\Tests\Tools\Invoke-PimsOSDiagnostics.ps1 -BuildValidation -AllowBuild -ExplainFailures +``` + +**Attention : cette dernière commande peut sélectionner des tests capables d'exécuter des opérations WIM/ISO/Build.** Elle ne doit être utilisée qu'après décision volontaire. + +## Règles de séance + +Avant chaque nouvelle séance : + +1. lire la note `Documentation\DocumentationSync-*.md` la plus récente ; +2. vérifier `git status` et les derniers commits ; +3. exécuter l'inventaire du mode souhaité ; +4. examiner les `BUILD-CAPABLE` et `UNKNOWN` avant toute exécution ; +5. ne jamais présenter une hypothèse comme un diagnostic. + +## Rapports + +Les rapports sont écrits dans : + +```text +Tests\Reports\Diagnostics\ +``` + +Ils comprennent des rapports Markdown et JSON horodatés. Le répertoire de rapports générés n'est pas une source de vérité du code. diff --git a/Tests/Unit/Modules/Architecture.Tests.ps1 b/Tests/Unit/Modules/Architecture.Tests.ps1 index 976355e..147c091 100644 --- a/Tests/Unit/Modules/Architecture.Tests.ps1 +++ b/Tests/Unit/Modules/Architecture.Tests.ps1 @@ -5,54 +5,93 @@ BeforeAll { $ProjectRoot = (Resolve-Path "$PSScriptRoot\..\..\..").Path - Import-Module "$ProjectRoot\Modules\PimsOS.psd1" -Force + + . "$ProjectRoot\Modules\Infrastructure\Logger.ps1" + . "$ProjectRoot\Modules\Core\Core.ps1" + . "$ProjectRoot\Modules\Core\BuildContext.ps1" + + . "$ProjectRoot\Modules\Configuration\Categories.ps1" + . "$ProjectRoot\Modules\Configuration\Tweak.ps1" + . "$ProjectRoot\Modules\Configuration\TweakCatalog.ps1" + . "$ProjectRoot\Modules\Configuration\Profile.ps1" + . "$ProjectRoot\Modules\Configuration\Configuration.ps1" + + . "$ProjectRoot\Modules\UI\Wizard.ps1" } Describe "Architecture PimsOS" { It "Expose uniquement le point d'entrée public" { - (Get-Module PimsOS).ExportedFunctions.Keys | - Should -Be "Initialize-PimsOS" + # Le test doit charger explicitement le module : l'ordre d'exécution + # des fichiers Pester ne doit pas être une dépendance implicite. + Remove-Module PimsOS -ErrorAction SilentlyContinue + Import-Module "$ProjectRoot\Modules\PimsOS.psd1" -Force + + $Module = Get-Module PimsOS + $Exports = @($Module.ExportedFunctions.Keys) + + $Exports | Should -HaveCount 1 + $Exports | Should -Contain "Initialize-PimsOS" + + # Les fonctions internes (ex. catalogue Chocolatey) ne sont pas + # exposées par le contrat public du module. + $Exports | Should -Not -Contain "Get-ChocolateyCatalog" + $Exports | Should -Not -Contain "Get-ChocolateyCatalogEntries" } It "Charge toutes les définitions de tweaks avec des actions valides" { - InModuleScope PimsOS { - $Context = [pscustomobject]@{ - Project = [pscustomobject]@{ Root = $ProjectRoot } - BuildState = [pscustomobject]@{ - Image = [pscustomobject]@{ TweaksLoaded = $false } - } - } - - $Tweaks = @(Get-TweakDefinitions -Context $Context -Reload) - $Tweaks.Count | Should -BeGreaterThan 0 - - foreach ($Tweak in $Tweaks) { - $Tweak.PSObject.Properties.Name | Should -Contain "Id" - $Tweak.PSObject.Properties.Name | Should -Contain "Enabled" - - foreach ($Action in @($Tweak.Actions)) { - $Action.PSObject.Properties.Name | Should -Contain "Id" - $Action.PSObject.Properties.Name | Should -Contain "Type" - $Action.PSObject.Properties.Name | Should -Contain "Enabled" + + $Context = [pscustomobject]@{ + Project = [pscustomobject]@{ + Root = $ProjectRoot + } + + BuildState = [pscustomobject]@{ + Image = [pscustomobject]@{ + TweaksLoaded = $false } } - } - } + } + + $Tweaks = @(Get-TweakDefinitions -Context $Context -Reload) + + $Tweaks.Count | Should -Be 27 + + foreach ($Tweak in $Tweaks) { + + $Tweak.PSObject.Properties.Name | + Should -Contain "Id" + + $Tweak.PSObject.Properties.Name | + Should -Contain "Enabled" + + foreach ($Action in @($Tweak.Actions)) { + + $Action.PSObject.Properties.Name | + Should -Contain "Id" + + $Action.PSObject.Properties.Name | + Should -Contain "Type" + + $Action.PSObject.Properties.Name | + Should -Contain "Enabled" + } + } + } It "Conserve une configuration Custom plate dans Get-PimsOSTweakConfiguration" { - InModuleScope PimsOS { - $Context = [pscustomobject]@{ - Configuration = @( - [pscustomobject]@{ Id="A"; Name="A"; Enabled=$true }, - [pscustomobject]@{ Id="B"; Name="B"; Enabled=$false } - ) - } - - $Result = @(Get-PimsOSTweakConfiguration -Context $Context) - $Result.Count | Should -Be 2 - $Result[0].Enabled | Should -BeTrue - $Result[1].Enabled | Should -BeFalse - } - } + + $Context = [pscustomobject]@{ + Configuration = @( + [pscustomobject]@{ Id="A"; Name="A"; Enabled=$true }, + [pscustomobject]@{ Id="B"; Name="B"; Enabled=$false } + ) + } + + $Result = @(Get-PimsOSTweakConfiguration -Context $Context) + + $Result.Count | Should -Be 2 + $Result[0].Enabled | Should -BeTrue + $Result[1].Enabled | Should -BeFalse + } } diff --git a/Tests/Unit/Modules/Chocolatey.Tests.ps1 b/Tests/Unit/Modules/Chocolatey.Tests.ps1 index 369dc6c..768be53 100644 --- a/Tests/Unit/Modules/Chocolatey.Tests.ps1 +++ b/Tests/Unit/Modules/Chocolatey.Tests.ps1 @@ -152,6 +152,32 @@ Describe "Chocolatey provider" { Should -Throw "*code de sortie 1*" } + It "Extrait correctement un bootstrap .nupkg sous PowerShell 5.1+" { + $script:ChocolateyAvailabilityCalls = 0 + Mock Test-ChocolateyAvailable { + $script:ChocolateyAvailabilityCalls++ + return ($script:ChocolateyAvailabilityCalls -ge 2) + } + Mock Write-Log {} + + $Source = Join-Path $TestDrive "ChocolateyBootstrap" + $Tools = Join-Path $Source "tools" + New-Item -ItemType Directory -Path $Tools -Force | Out-Null + + @' +# Test uniquement : prouve que le script contenu dans le .nupkg est extractible. +exit 0 +'@ | Set-Content -LiteralPath (Join-Path $Tools "chocolateyInstall.ps1") -Encoding UTF8 + + $Zip = Join-Path $TestDrive "chocolatey.zip" + Compress-Archive -Path (Join-Path $Source "*") -DestinationPath $Zip -Force + $Nupkg = Join-Path $TestDrive "chocolatey.nupkg" + Move-Item -LiteralPath $Zip -Destination $Nupkg -Force + + { Install-ChocolateyBootstrap -BootstrapPackagePath $Nupkg } | Should -Not -Throw + Should -Invoke Test-ChocolateyAvailable -Times 3 -Exactly + } + It "Refuse Chocolatey absent" { Mock Test-ChocolateyAvailable { $false } @@ -165,4 +191,85 @@ Describe "Chocolatey provider" { { Invoke-ChocolateyPackage -Context $Context -Action $Action } | Should -Throw "*choco.exe*introuvable*" } + It "Utilise uniquement le cache en mode Offline" { + Mock Test-ChocolateyAvailable { $true } + Mock Get-Command { [pscustomobject]@{ Source = "choco.exe" } } -ParameterFilter { $Name -eq "choco.exe" } + Mock Invoke-ChocolateyCli { [pscustomobject]@{ ExitCode = 0 } } + + $Cache = Join-Path $TestDrive "Chocolatey" + New-Item -ItemType Directory -Path $Cache -Force | Out-Null + New-Item -ItemType File -Path (Join-Path $Cache "firefox.1.0.0.nupkg") -Force | Out-Null + $Context = [pscustomobject]@{ Workspace = [pscustomobject]@{ PackagesChocolatey = $Cache } } + $Action = [pscustomobject]@{ Name = "firefox"; Version = "1.0.0"; Mode = "Offline" } + + $null = Invoke-ChocolateyPackage -Context $Context -Action $Action + Should -Invoke Invoke-ChocolateyCli -Times 1 -Exactly -ParameterFilter { $Arguments -match "--source=$([regex]::Escape($Cache))$" } + } + + It "Utilise Community en mode Online" { + Mock Test-ChocolateyAvailable { $true } + Mock Get-Command { [pscustomobject]@{ Source = "choco.exe" } } -ParameterFilter { $Name -eq "choco.exe" } + Mock Invoke-ChocolateyCli { [pscustomobject]@{ ExitCode = 0 } } + + $Context = [pscustomobject]@{ Workspace = [pscustomobject]@{ PackagesChocolatey = (Join-Path $TestDrive "Chocolatey") } } + $Action = [pscustomobject]@{ Name = "firefox"; Version = "1.0.0"; Mode = "Online" } + + $null = Invoke-ChocolateyPackage -Context $Context -Action $Action + Should -Invoke Invoke-ChocolateyCli -Times 1 -Exactly -ParameterFilter { $Arguments -match "--source=https://community\.chocolatey\.org/api/v2/" } + } + + It "Continue sur un échec lorsque FailurePolicy vaut Continue" { + $Cache = Join-Path $TestDrive "Chocolatey" + $Context = [pscustomobject]@{ + Workspace = [pscustomobject]@{ + PackagesChocolatey = $Cache + } + } + $Catalog = Join-Path $TestDrive "Chocolatey.json" + @{ + Packages = @( + @{ Id = 'broken-package'; Enabled = $true; Mode = 'Online'; FailurePolicy = 'Continue' } + @{ Id = 'next-package'; Enabled = $true; Mode = 'Online'; FailurePolicy = 'Continue' } + ) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 + + $script:Calls = 0 + Mock Invoke-ChocolateyPackage { + $script:Calls++ + if ($Action.Name -eq 'broken-package') { + throw "checksum mismatch" + } + $Context + } + + $Result = @(Invoke-ChocolateyCatalog -Context $Context -CatalogPath $Catalog -RuntimeCachePath $Cache) + + $Result.Count | Should -Be 2 + $Result[0].Status | Should -Be 'Failed' + $Result[0].FailurePolicy | Should -Be 'Continue' + $Result[1].Status | Should -Be 'Installed' + $script:Calls | Should -Be 2 + } + + It "Arrête le catalogue sur un échec lorsque FailurePolicy vaut Stop" { + $Cache = Join-Path $TestDrive "Chocolatey" + $Context = [pscustomobject]@{ + Workspace = [pscustomobject]@{ + PackagesChocolatey = $Cache + } + } + $Catalog = Join-Path $TestDrive "Chocolatey.json" + @{ + Packages = @( + @{ Id = 'broken-package'; Enabled = $true; Mode = 'Online'; FailurePolicy = 'Stop' } + @{ Id = 'next-package'; Enabled = $true; Mode = 'Online'; FailurePolicy = 'Continue' } + ) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 + + Mock Invoke-ChocolateyPackage { throw "checksum mismatch" } + + { Invoke-ChocolateyCatalog -Context $Context -CatalogPath $Catalog -RuntimeCachePath $Cache } | + Should -Throw "*checksum mismatch*" + } + } diff --git a/Tests/Unit/Modules/ChocolateyCache.Tests.ps1 b/Tests/Unit/Modules/ChocolateyCache.Tests.ps1 index 6054a52..17dd5a5 100644 --- a/Tests/Unit/Modules/ChocolateyCache.Tests.ps1 +++ b/Tests/Unit/Modules/ChocolateyCache.Tests.ps1 @@ -52,15 +52,15 @@ Describe "Chocolatey cache" { It "Détecte un package déjà présent et ne le télécharge pas" { $Cache = Join-Path $TestDrive "Chocolatey" New-Item -ItemType Directory -Path $Cache -Force | Out-Null - $Existing = Join-Path $Cache "firefox.1.0.0.nupkg" + $Existing = Join-Path $Cache "cached-firefox.1.0.0.nupkg" New-Item -ItemType File -Path $Existing -Force | Out-Null Mock Invoke-WebRequest { throw 'Le téléchargement ne doit pas être appelé.' } $Package = [pscustomobject]@{ - Id = 'firefox' - Enabled = $true - Version = '1.0.0' + Id = 'cached-firefox' + Enabled = $true + Version = '1.0.0' } $Result = Save-ChocolateyPackageToCache ` @@ -73,12 +73,13 @@ Describe "Chocolatey cache" { Should -Invoke Invoke-WebRequest -Times 0 -Exactly } + It "Télécharge un package absent dans le cache" { $Cache = Join-Path $TestDrive "Chocolatey" $Package = [pscustomobject]@{ - Id = 'firefox' - Enabled = $true - Version = '1.0.0' + Id = 'download-firefox' + Enabled = $true + Version = '1.0.0' } Mock Invoke-WebRequest { @@ -95,7 +96,7 @@ Describe "Chocolatey cache" { Test-Path $Result.Path | Should -BeTrue Should -Invoke Invoke-WebRequest -Times 1 -Exactly -ParameterFilter { - $Uri -eq 'https://example.test/api/v2/package/firefox/1.0.0' + $Uri -eq 'https://example.test/api/v2/package/download-firefox/1.0.0' } } @@ -105,8 +106,8 @@ Describe "Chocolatey cache" { @{ Packages = @( - @{ Id = 'firefox'; Enabled = $true; Version = '1.0.0' } - @{ Id = 'vlc'; Enabled = $true; Version = '2.0.0' } + @{ Id = 'cache-firefox'; Enabled = $true; Mode = 'Offline'; Version = '1.0.0' } + @{ Id = 'cache-vlc'; Enabled = $true; Mode = 'Offline'; Version = '2.0.0' } ) } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 @@ -136,7 +137,7 @@ Describe "Chocolatey cache" { @{ Packages = @( - @{ Id = 'firefox'; Enabled = $true; Version = '1.0.0' } + @{ Id = 'cache-second-firefox'; Enabled = $true; Mode = 'Offline'; Version = '1.0.0' } ) } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 @@ -158,4 +159,78 @@ Describe "Chocolatey cache" { $Second.AlreadyCached | Should -Be 1 Should -Invoke Invoke-WebRequest -Times 1 -Exactly } + It "Exige le bootstrap Chocolatey dans le cache Build" { + $Cache = Join-Path $TestDrive "Chocolatey" + New-Item -ItemType Directory -Path $Cache -Force | Out-Null + + { Test-ChocolateyBootstrapPackage -CachePath $Cache } | + Should -Throw "*bootstrap Chocolatey est absent*" + } + + It "Valide un bootstrap Chocolatey contenant chocolateyInstall.ps1" { + $Cache = Join-Path $TestDrive "Chocolatey" + New-Item -ItemType Directory -Path $Cache -Force | Out-Null + $Nupkg = Join-Path $Cache "chocolatey.nupkg" + + Add-Type -AssemblyName System.IO.Compression.FileSystem + $Archive = [System.IO.Compression.ZipFile]::Open($Nupkg, [System.IO.Compression.ZipArchiveMode]::Create) + try { + $Entry = $Archive.CreateEntry('tools/chocolateyInstall.ps1') + $Writer = [System.IO.StreamWriter]::new($Entry.Open()) + try { $Writer.Write('Write-Host test') } finally { $Writer.Dispose() } + } + finally { $Archive.Dispose() } + + $Result = Test-ChocolateyBootstrapPackage -CachePath $Cache + + $Result.Present | Should -BeTrue + $Result.Name | Should -Be 'chocolatey.nupkg' + $Result.Path | Should -Be $Nupkg + } + + It "Ne télécharge que les packages en Mode Offline" { + $Catalog = Join-Path $TestDrive "Chocolatey.json" + $Cache = Join-Path $TestDrive "Chocolatey" + + @{ + Packages = @( + @{ Id = 'offline-pkg'; Enabled = $true; Mode = 'Offline'; Version = '1.0.0' } + @{ Id = 'online-pkg'; Enabled = $true; Mode = 'Online'; Version = '2.0.0' } + ) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 + + $Context = [pscustomobject]@{ Workspace = [pscustomobject]@{ PackagesChocolatey = $Cache } } + Mock Invoke-WebRequest { New-Item -ItemType File -Path $OutFile -Force | Out-Null } + + $Result = Initialize-ChocolateyCache -Context $Context -CatalogPath $Catalog + + $Result.Total | Should -Be 1 + $Result.Results[0].Id | Should -Be 'offline-pkg' + Should -Invoke Invoke-WebRequest -Times 1 -Exactly + } + + It "Utilise Stop comme FailurePolicy par défaut" { + $Catalog = Join-Path $TestDrive "Chocolatey.json" + @{ + Packages = @( + @{ Id = 'default-policy'; Enabled = $true; Mode = 'Online' } + ) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 + + $Result = @(Get-ChocolateyPackageDefinitions -Path $Catalog) + $Result[0].Id | Should -Be 'default-policy' + } + + It "Refuse une FailurePolicy invalide" { + $Catalog = Join-Path $TestDrive "Chocolatey.json" + @{ + Packages = @( + @{ Id = 'bad-policy'; Enabled = $true; Mode = 'Online'; FailurePolicy = 'Ignore' } + ) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Catalog -Encoding utf8 + + { Get-ChocolateyPackageDefinitions -Path $Catalog } | + Should -Throw "*FailurePolicy invalide*" + } + } diff --git a/Tests/Unit/Modules/ChocolateyCatalog.Tests.ps1 b/Tests/Unit/Modules/ChocolateyCatalog.Tests.ps1 new file mode 100644 index 0000000..98bb4c7 --- /dev/null +++ b/Tests/Unit/Modules/ChocolateyCatalog.Tests.ps1 @@ -0,0 +1,51 @@ +BeforeAll { + $ProjectRoot = (Resolve-Path "$PSScriptRoot\..\..\..").Path + Import-Module "$ProjectRoot\Modules\PimsOS.psd1" -Force + . "$ProjectRoot\Modules\Package\ChocolateyCatalog.ps1" +} + +Describe "Chocolatey Catalog" { + BeforeEach { + $Root = Join-Path $TestDrive 'PimsOS' + New-Item -ItemType Directory -Path (Join-Path $Root 'Config\Packages') -Force | Out-Null + $Catalog = [ordered]@{ + Provider='Chocolatey'; Version='1.0'; Description='Test' + Packages=@( + [ordered]@{Id='chocolatey';Enabled=$true;Category='Chocolatey';Mode='Offline';Version=$null}, + [ordered]@{Id='vlc';Enabled=$true;Category='Media';Mode='Online';Version='3.0.23'} + ) + } + $Catalog | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $Root 'Config\Packages\Chocolatey.json') -Encoding utf8 + Mock Write-Log {} + $Context = [pscustomobject]@{ Project = [pscustomobject]@{ Root = $Root } } + } + + It "ajoute un package Online" { + Add-ChocolateyCatalogPackage -Context $Context -Id '7zip' -Version '25.01' -Mode Online -Category 'Tools' | Out-Null + (Read-ChocolateyCatalog -Context $Context).Packages.Id | Should -Contain '7zip' + } + + It "ajoute un package Offline" { + Add-ChocolateyCatalogPackage -Context $Context -Id 'keepass.install' -Mode Offline | Out-Null + $p = @((Read-ChocolateyCatalog -Context $Context).Packages | Where-Object Id -eq 'keepass.install')[0] + $p.Mode | Should -Be 'Offline' + $p.Enabled | Should -BeTrue + } + + It "refuse un doublon" { + { Add-ChocolateyCatalogPackage -Context $Context -Id 'vlc' } | Should -Throw '*existe déjà*' + } + + It "refuse de supprimer chocolatey" { + { Remove-ChocolateyCatalogPackage -Context $Context -Id 'chocolatey' } | Should -Throw '*obligatoire*' + } + + It "supprime un package existant" { + Remove-ChocolateyCatalogPackage -Context $Context -Id 'vlc' | Should -BeTrue + (Read-ChocolateyCatalog -Context $Context).Packages.Id | Should -Not -Contain 'vlc' + } + + It "refuse un identifiant invalide" { + { Add-ChocolateyCatalogPackage -Context $Context -Id 'Bad Package!' } | Should -Throw '*invalide*' + } +} diff --git a/Tests/Unit/Modules/DriverEngine.Tests.ps1 b/Tests/Unit/Modules/DriverEngine.Tests.ps1 index 05af0df..0806d4e 100644 --- a/Tests/Unit/Modules/DriverEngine.Tests.ps1 +++ b/Tests/Unit/Modules/DriverEngine.Tests.ps1 @@ -134,6 +134,67 @@ Describe "DriverEngine" { } + It "Ajoute les propriétés de résultat à une action dynamique" { + + $DynamicAction = [pscustomobject]@{ + + Id = "Driver.Dynamic" + + Name = "DynamicDriver" + + } + + $null = Invoke-DriverAction ` + -Context $script:Context ` + -Action $DynamicAction + + $DynamicAction.PSObject.Properties.Name | + Should -Contain "Success" + + $DynamicAction.PSObject.Properties.Name | + Should -Contain "Duration" + + $DynamicAction.PSObject.Properties.Name | + Should -Contain "Error" + + $DynamicAction.Success | + Should -BeTrue + + } + + + It "Gère une erreur avec une action dynamique" { + + Mock Invoke-Driver { + throw "Erreur dynamique" + } + + $DynamicAction = [pscustomobject]@{ + + Id = "Driver.DynamicError" + + Name = "DynamicDriver" + + } + + { + + Invoke-DriverAction ` + -Context $script:Context ` + -Action $DynamicAction + + } | + Should -Throw + + $DynamicAction.Success | + Should -BeFalse + + $DynamicAction.Error | + Should -Be "Erreur dynamique" + + } + + It "Réinitialise Error après une réussite" { $script:Action.Error = "Ancienne erreur" diff --git a/Tests/Unit/Modules/PostInstall/Bootstrap.Tests.ps1 b/Tests/Unit/Modules/PostInstall/Bootstrap.Tests.ps1 index 3fed277..f46ed8d 100644 --- a/Tests/Unit/Modules/PostInstall/Bootstrap.Tests.ps1 +++ b/Tests/Unit/Modules/PostInstall/Bootstrap.Tests.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Tests : PostInstall Bootstrap # Projet : PimsOS Builder # ========================================== @@ -47,10 +47,22 @@ Describe "PostInstall Bootstrap" { $script:RuntimePath ` "UI.ps1" + $script:DriverCheckPath = Join-Path ` + $script:RuntimePath ` + "DriverCheck.ps1" + + $script:ChocolateyPath = Join-Path ` + $script:RuntimePath ` + "Chocolatey.ps1" + $script:PostInstallPath = Join-Path ` $script:RuntimePath ` "PostInstall.ps1" + $script:FinalizePath = Join-Path ` + $script:RuntimePath ` + "Finalize.ps1" + # -------------------------------------------------- # Logger de test # -------------------------------------------------- @@ -92,7 +104,22 @@ function Write-Log { Set-Content ` -Path $script:StatePath ` - -Value 'function New-BootstrapState {}' ` + -Value @' +function New-BootstrapState { + return [pscustomobject]@{ StatePath = $null } +} + +function Save-PostInstallState { + param( + [Parameter(Mandatory)] + [psobject]$State, + + [string]$StatePath + ) + + return $State +} +'@ ` -Encoding UTF8 # -------------------------------------------------- @@ -113,13 +140,38 @@ function Write-Log { -Value 'function Show-BootstrapUI {}' ` -Encoding UTF8 + # -------------------------------------------------- + # DriverCheck + # -------------------------------------------------- + + Set-Content ` + -Path $script:DriverCheckPath ` + -Value 'function Test-PostInstallDrivers { return [pscustomobject]@{ Available = $true; Problems = @() } }' ` + -Encoding UTF8 + + # -------------------------------------------------- + # Chocolatey + # -------------------------------------------------- + + Set-Content ` + -Path $script:ChocolateyPath ` + -Value 'function Invoke-ChocolateyCatalog { return $true }' ` + -Encoding UTF8 + # -------------------------------------------------- # PostInstall # -------------------------------------------------- Set-Content ` -Path $script:PostInstallPath ` - -Value 'function Invoke-PostInstall { return "OK" }' ` + -Value 'function Invoke-PostInstall { return [pscustomobject]@{ StatePath = "state.json" } }' ` + -Encoding UTF8 + +$script:FinalizeCalled = $false + + Set-Content ` + -Path $script:FinalizePath ` + -Value 'function Complete-PimsOSPostInstall { param($State,$RuntimePath) $script:FinalizeCalled = $true; return [pscustomobject]@{ State = $State; Cleanup = [pscustomobject]@{ Scheduled = $true; DelaySeconds = 10 } } }' ` -Encoding UTF8 # -------------------------------------------------- @@ -213,8 +265,8 @@ function Write-Log { Start-PimsOSPostInstall ` -RuntimePath $script:RuntimePath - $Result | - Should -Be "OK" + $Result.StatePath | + Should -Be "state.json" } @@ -267,11 +319,30 @@ function Write-Log { } + + It "Effectue la vérification finale après le PostInstall" { + + $BootstrapContent = Get-Content ` + -LiteralPath $script:BootstrapPath ` + -Raw ` + -Encoding UTF8 + + $BootstrapContent = $BootstrapContent -replace ` + '(?ms)\r?\n# --------------------------------------------------\r?\n# Point d''entrée\r?\n# --------------------------------------------------\r?\n\r?\nStart-PimsOSPostInstall\s*$', '' + + . ([scriptblock]::Create($BootstrapContent)) + + $null = Start-PimsOSPostInstall -RuntimePath $script:RuntimePath + + $script:FinalizeCalled | Should -BeTrue + + } + It "Transmet WaitForNetwork" { Set-Content ` -Path $script:PostInstallPath ` - -Value 'function Invoke-PostInstall { param([switch]$WaitForNetwork,[int]$NetworkTimeoutMinutes) if (-not $WaitForNetwork) { throw "WaitForNetwork absent" } return $true }' ` + -Value 'function Invoke-PostInstall { param([switch]$WaitForNetwork,[int]$NetworkTimeoutMinutes) if (-not $WaitForNetwork) { throw "WaitForNetwork absent" } return [pscustomobject]@{ StatePath = "state.json" } }' ` -Encoding UTF8 $BootstrapContent = Get-Content ` @@ -289,8 +360,8 @@ function Write-Log { -RuntimePath $script:RuntimePath ` -WaitForNetwork - $Result | - Should -BeTrue + $Result.StatePath | + Should -Be "state.json" } @@ -299,7 +370,7 @@ function Write-Log { Set-Content ` -Path $script:PostInstallPath ` - -Value 'function Invoke-PostInstall { param([switch]$WaitForNetwork,[int]$NetworkTimeoutMinutes) if ($NetworkTimeoutMinutes -ne 15) { throw "Timeout incorrect" } return $true }' ` + -Value 'function Invoke-PostInstall { param([switch]$WaitForNetwork,[int]$NetworkTimeoutMinutes) if ($NetworkTimeoutMinutes -ne 15) { throw "Timeout incorrect" } return [pscustomobject]@{ StatePath = "state.json" } }' ` -Encoding UTF8 $BootstrapContent = Get-Content ` @@ -318,8 +389,8 @@ function Write-Log { -WaitForNetwork ` -NetworkTimeoutMinutes 15 - $Result | - Should -BeTrue + $Result.StatePath | + Should -Be "state.json" } diff --git a/Tests/Unit/Modules/PostInstall/DeploymentValidation.Tests.ps1 b/Tests/Unit/Modules/PostInstall/DeploymentValidation.Tests.ps1 index 5c9f506..0d8ad09 100644 --- a/Tests/Unit/Modules/PostInstall/DeploymentValidation.Tests.ps1 +++ b/Tests/Unit/Modules/PostInstall/DeploymentValidation.Tests.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Tests : PostInstall DeploymentValidation # Projet : PimsOS Builder # ========================================== @@ -26,14 +26,23 @@ Describe "PostInstall DeploymentValidation" { "Network.ps1" "UI.ps1" "DriverCheck.ps1" + "Chocolatey.ps1" "PostInstall.ps1" "State.ps1" + "Finalize.ps1" ) | ForEach-Object { - New-Item -ItemType File -Path (Join-Path $script:PostInstallPath $_) -Force | Out-Null + Set-Content -LiteralPath (Join-Path $script:PostInstallPath $_) -Value "# Test runtime" -Encoding UTF8 } + Set-Content -LiteralPath (Join-Path $script:PostInstallPath "Bootstrap.ps1") -Value @' +# Test bootstrap +. "$PSScriptRoot\DriverCheck.ps1" +. "$PSScriptRoot\Chocolatey.ps1" +. "$PSScriptRoot\Finalize.ps1" +'@ -Encoding UTF8 + } It "Valide un déploiement PostInstall complet" { @@ -51,6 +60,9 @@ Describe "PostInstall DeploymentValidation" { $Result.FirstLogonCommands | Should -BeTrue $Result.BootstrapReferenced | Should -BeTrue $Result.RunOnceReferenced | Should -BeFalse + $Result.BootstrapLoadsDriverCheck | Should -BeTrue + $Result.BootstrapLoadsChocolatey | Should -BeTrue + $Result.BootstrapLoadsFinalize | Should -BeTrue } diff --git a/Tests/Unit/Modules/PostInstall/Finalize.Tests.ps1 b/Tests/Unit/Modules/PostInstall/Finalize.Tests.ps1 new file mode 100644 index 0000000..0e122fe --- /dev/null +++ b/Tests/Unit/Modules/PostInstall/Finalize.Tests.ps1 @@ -0,0 +1,188 @@ +# ========================================== +# Tests : PostInstall Finalize +# Projet : PimsOS Builder +# ========================================== + +BeforeAll { + + $ProjectRoot = (Resolve-Path "$PSScriptRoot\..\..\..\..").Path + + . "$ProjectRoot\Modules\PostInstall\Finalize.ps1" + +} + +Describe "PostInstall Finalize" { + + BeforeEach { + + $script:State = [PSCustomObject]@{ + Status = "Completed" + Completed = $true + Failed = $false + CurrentPhase = $null + CompletedTasks = @( + "Initialize", + "Network", + "DriverCheck", + "Chocolatey", + "Applications", + "MicrosoftStore", + "Configuration", + "Cleanup" + ) + Verification = [PSCustomObject]@{ + Verified = $false + VerifiedAt = $null + MissingTasks = @() + } + Cleanup = [PSCustomObject]@{ + Status = "Pending" + Scheduled = $false + ScheduledAt = $null + RemovedItems = @() + PreservedItems = @() + Errors = @() + } + } + + $script:RuntimePath = Join-Path $TestDrive "PostInstall" + New-Item -ItemType Directory -Path $script:RuntimePath -Force | Out-Null + + foreach ($FileName in @( + "Bootstrap.ps1", + "Finalize.ps1", + "Logger.ps1", + "Network.ps1", + "UI.ps1", + "DriverCheck.ps1", + "Chocolatey.ps1", + "PostInstall.ps1", + "State.ps1" + )) { + New-Item -ItemType File -Path (Join-Path $script:RuntimePath $FileName) -Force | Out-Null + } + + $script:UnattendPath = Join-Path $TestDrive "unattend.xml" + New-Item -ItemType File -Path $script:UnattendPath -Force | Out-Null + + } + + Context "Test-PimsOSPostInstallCompletion" { + + It "Valide un état PostInstall complet" { + + $Result = Test-PimsOSPostInstallCompletion -State $script:State + + $Result.Success | Should -BeTrue + $Result.MissingTasks | Should -HaveCount 0 + + } + + It "Détecte une tâche obligatoire manquante" { + + $script:State.CompletedTasks = @( + "Initialize", + "Network" + ) + + $Result = Test-PimsOSPostInstallCompletion -State $script:State + + $Result.Success | Should -BeFalse + $Result.MissingTasks | Should -Contain "DriverCheck" + $Result.MissingTasks | Should -Contain "Cleanup" + + } + + It "Refuse un état non terminé" { + + $script:State.Status = "Running" + + $Result = Test-PimsOSPostInstallCompletion -State $script:State + + $Result.Success | Should -BeFalse + + } + + } + + Context "Invoke-PimsOSPostInstallCleanup" { + + It "Programme le nettoyage dans un processus séparé" { + + Mock Start-Process { + return [PSCustomObject]@{ Id = 1234 } + } + + $Result = Invoke-PimsOSPostInstallCleanup ` + -RuntimePath $script:RuntimePath ` + -UnattendPath $script:UnattendPath ` + -DelaySeconds 5 + + $Result.Scheduled | Should -BeTrue + $Result.ProcessId | Should -Be 1234 + $Result.DelaySeconds | Should -Be 5 + $Result.RemovedItems | Should -Contain (Join-Path $script:RuntimePath "Bootstrap.ps1") + $Result.RemovedItems | Should -Contain $script:UnattendPath + $Result.PreservedItems | Should -Contain (Join-Path $script:RuntimePath "state.json") + $Result.PreservedItems | Should -Contain (Join-Path $script:RuntimePath "PostInstall.log") + $Result.PreservedItems | Should -Contain (Join-Path $script:RuntimePath "Chocolatey") + + Should -Invoke Start-Process -Times 1 -Exactly + + } + + It "Refuse un délai nul" { + + { + Invoke-PimsOSPostInstallCleanup ` + -RuntimePath $script:RuntimePath ` + -DelaySeconds 0 + } | Should -Throw "*délai de nettoyage*" + + } + + } + + Context "Complete-PimsOSPostInstall" { + + It "Vérifie puis programme le nettoyage" { + + Mock Start-Process { + return [PSCustomObject]@{ Id = 5678 } + } + + $Result = Complete-PimsOSPostInstall ` + -State $script:State ` + -RuntimePath $script:RuntimePath ` + -UnattendPath $script:UnattendPath ` + -DelaySeconds 5 + + $Result.Verification.Success | Should -BeTrue + $Result.State.Verification.Verified | Should -BeTrue + $Result.State.Cleanup.Status | Should -Be "Scheduled" + $Result.State.Cleanup.Scheduled | Should -BeTrue + $Result.Cleanup.ProcessId | Should -Be 5678 + + } + + It "Ne programme pas le nettoyage si la vérification échoue" { + + $script:State.Failed = $true + + Mock Start-Process { + throw "Start-Process ne doit pas être appelé." + } + + { + Complete-PimsOSPostInstall ` + -State $script:State ` + -RuntimePath $script:RuntimePath + } | Should -Throw "*Vérification finale du PostInstall échouée*" + + Should -Invoke Start-Process -Times 0 -Exactly + + } + + } + +} diff --git a/Tests/Unit/Modules/PostInstall/Installer.Tests.ps1 b/Tests/Unit/Modules/PostInstall/Installer.Tests.ps1 index 60bd66a..8dff645 100644 --- a/Tests/Unit/Modules/PostInstall/Installer.Tests.ps1 +++ b/Tests/Unit/Modules/PostInstall/Installer.Tests.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Tests : PostInstall Installer # Projet : PimsOS Builder # ========================================== @@ -36,6 +36,7 @@ Describe "PostInstall Installer" { "UI.ps1" "DriverCheck.ps1" "PostInstall.ps1" + "Finalize.ps1" "State.ps1" )) { @@ -50,6 +51,19 @@ Describe "PostInstall Installer" { } + # -------------------------------------------------- + # Provider Chocolatey simulé + # -------------------------------------------------- + + $script:ChocolateyProviderPath = Join-Path ` + $TestDrive ` + "Chocolatey.ps1" + + Set-Content ` + -LiteralPath $script:ChocolateyProviderPath ` + -Value "# Test Chocolatey provider" ` + -Encoding UTF8 + # -------------------------------------------------- # Image montée simulée # -------------------------------------------------- @@ -77,7 +91,8 @@ Describe "PostInstall Installer" { $Result = Install-PimsOSPostInstallRuntime ` -MountPath $script:MountPath ` - -SourcePath $script:SourcePath + -SourcePath $script:SourcePath ` + -ChocolateyProviderPath $script:ChocolateyProviderPath $Destination = Join-Path ` $script:MountPath ` @@ -94,12 +109,13 @@ Describe "PostInstall Installer" { } - It "Copie les sept fichiers du runtime" { + It "Copie les neuf fichiers du runtime" { $null = Install-PimsOSPostInstallRuntime ` -MountPath $script:MountPath ` - -SourcePath $script:SourcePath + -SourcePath $script:SourcePath ` + -ChocolateyProviderPath $script:ChocolateyProviderPath $Destination = Join-Path ` $script:MountPath ` @@ -111,7 +127,9 @@ Describe "PostInstall Installer" { "Network.ps1" "UI.ps1" "DriverCheck.ps1" - "PostInstall.ps1" + "Chocolatey.ps1" + "PostInstall.ps1" + "Finalize.ps1" "State.ps1" )) { @@ -134,7 +152,8 @@ Describe "PostInstall Installer" { $null = Install-PimsOSPostInstallRuntime ` -MountPath $script:MountPath ` - -SourcePath $script:SourcePath + -SourcePath $script:SourcePath ` + -ChocolateyProviderPath $script:ChocolateyProviderPath $LoggerPath = Join-Path ` $script:MountPath ` @@ -153,7 +172,8 @@ Describe "PostInstall Installer" { $Result = Install-PimsOSPostInstallRuntime ` -MountPath $script:MountPath ` - -SourcePath $script:SourcePath + -SourcePath $script:SourcePath ` + -ChocolateyProviderPath $script:ChocolateyProviderPath $Expected = Join-Path ` $script:MountPath ` diff --git a/Tests/Unit/Modules/PostInstall/PostInstall.Tests.ps1 b/Tests/Unit/Modules/PostInstall/PostInstall.Tests.ps1 index 466a5df..cce41e8 100644 --- a/Tests/Unit/Modules/PostInstall/PostInstall.Tests.ps1 +++ b/Tests/Unit/Modules/PostInstall/PostInstall.Tests.ps1 @@ -14,6 +14,8 @@ BeforeAll { . "$ProjectRoot\Modules\PostInstall\State.ps1" . "$ProjectRoot\Modules\PostInstall\Network.ps1" . "$ProjectRoot\Modules\PostInstall\DriverCheck.ps1" + . "$ProjectRoot\Modules\Infrastructure\Logger.ps1" + . "$ProjectRoot\Modules\Package\Chocolatey.ps1" . "$ProjectRoot\Modules\PostInstall\PostInstall.ps1" } @@ -33,6 +35,19 @@ Describe "PostInstall" { $script:State = New-PostInstallState ` -StatePath $script:StatePath + Mock Write-Log {} + + # Runtime Chocolatey simulé pour les tests de la phase Chocolatey. + $script:RuntimeRoot = Split-Path -Parent $script:StatePath + $script:ChocolateyRoot = Join-Path $script:RuntimeRoot "Chocolatey" + $script:ChocolateyCachePath = Join-Path $script:ChocolateyRoot "Cache" + New-Item -ItemType Directory -Path $script:ChocolateyCachePath -Force | Out-Null + Set-Content -LiteralPath (Join-Path $script:ChocolateyCachePath "chocolatey.nupkg") -Value "test" -Encoding UTF8 + Set-Content -LiteralPath (Join-Path $script:ChocolateyRoot "Chocolatey.json") -Value '{"Packages":[]}' -Encoding UTF8 + + Mock Install-ChocolateyBootstrap {} + Mock Invoke-ChocolateyCatalog { @() } + } # ================================================== diff --git a/Tests/Unit/Modules/PostInstall/State.Tests.ps1 b/Tests/Unit/Modules/PostInstall/State.Tests.ps1 index fa257c9..e02aec2 100644 --- a/Tests/Unit/Modules/PostInstall/State.Tests.ps1 +++ b/Tests/Unit/Modules/PostInstall/State.Tests.ps1 @@ -1,4 +1,4 @@ -# ========================================== +# ========================================== # Tests : PostInstall State # Projet : PimsOS Builder # ========================================== @@ -37,6 +37,15 @@ Describe "PostInstall State" { $State.NetworkAvailable | Should -BeFalse + $State.Verification.Verified | + Should -BeFalse + + $State.Cleanup.Status | + Should -Be "Pending" + + $State.Cleanup.Scheduled | + Should -BeFalse + } It "Utilise le chemin d'état fourni" { diff --git a/Tests/Unit/Modules/PostInstallBootstrap.Tests.ps1 b/Tests/Unit/Modules/PostInstallBootstrap.Tests.ps1 new file mode 100644 index 0000000..1f8325a --- /dev/null +++ b/Tests/Unit/Modules/PostInstallBootstrap.Tests.ps1 @@ -0,0 +1,46 @@ +# ========================================== +# Tests : PostInstall Bootstrap +# Projet : PimsOS Builder +# ========================================== + +BeforeAll { + $ProjectRoot = (Resolve-Path "$PSScriptRoot\..\..\..").Path + . "$ProjectRoot\Modules\PostInstall\DeploymentValidation.ps1" +} + +Describe "PostInstall deployment validation" { + + It "Exige que Bootstrap charge DriverCheck et Chocolatey" { + $Runtime = Join-Path $TestDrive "PostInstall" + New-Item -ItemType Directory -Path $Runtime -Force | Out-Null + @( + 'DriverCheck.ps1' + 'Chocolatey.ps1' + 'Bootstrap.ps1' + 'Logger.ps1' + 'Network.ps1' + 'UI.ps1' + 'PostInstall.ps1' + 'State.ps1' + 'Finalize.ps1' + ) | ForEach-Object { New-Item -ItemType File -Path (Join-Path $Runtime $_) -Force | Out-Null } + + Set-Content -LiteralPath (Join-Path $Runtime 'Bootstrap.ps1') -Value @' +. "$PSScriptRoot\DriverCheck.ps1" +. "$PSScriptRoot\Chocolatey.ps1" +. "$PSScriptRoot\Finalize.ps1" +'@ -Encoding utf8 + + $Unattend = Join-Path $TestDrive 'unattend.xml' + Set-Content -LiteralPath $Unattend -Value @' +1powershell.exe -File "C:\ProgramData\PimsOS\PostInstall\Bootstrap.ps1" +'@ -Encoding utf8 + + $Result = Test-PostInstallDeployment -PostInstallPath $Runtime -UnattendPath $Unattend + + $Result.Success | Should -BeTrue + $Result.BootstrapLoadsDriverCheck | Should -BeTrue + $Result.BootstrapLoadsChocolatey | Should -BeTrue + $Result.BootstrapLoadsFinalize | Should -BeTrue + } +} diff --git a/Tests/Unit/Modules/Wizard.Tests.ps1 b/Tests/Unit/Modules/Wizard.Tests.ps1 index ae1910a..abb1b27 100644 --- a/Tests/Unit/Modules/Wizard.Tests.ps1 +++ b/Tests/Unit/Modules/Wizard.Tests.ps1 @@ -217,13 +217,43 @@ Context "Show-PimsOSDriverMenu" { } +# ================================================== +# Show-PimsOSChocolateyPackageMenu +# ================================================== + +Context "Show-PimsOSChocolateyPackageMenu" { + + It "Retourne immédiatement avec le choix 0" { + + InModuleScope PimsOS { + + $Root = Join-Path $TestDrive "PimsOS" + New-Item -ItemType Directory -Path (Join-Path $Root "Config\Packages") -Force | Out-Null + $Catalog = @{ Provider="Chocolatey"; Version="1.0"; Description="Test"; Packages=@(@{ Id="chocolatey"; Enabled=$true; Category="Chocolatey"; Mode="Offline"; Version=$null }) } + $Catalog | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $Root "Config\Packages\Chocolatey.json") -Encoding utf8 + + $TestContext = [pscustomobject]@{ + Project = [pscustomobject]@{ Root = $Root } + } + + Mock Read-Host { return "0" } + Mock Write-Log {} + + Show-PimsOSChocolateyPackageMenu -Context $TestContext + + Test-Path -LiteralPath (Join-Path $Root "Config\Packages\Chocolatey.json") | Should -BeTrue + } + } +} + + # ================================================== # Show-PimsOSBuildWizard # ================================================== Context "Show-PimsOSBuildWizard" { - It "Retourne le contexte lorsque le choix 6 est sélectionné" { + It "Retourne le contexte lorsque le choix 7 est sélectionné" { InModuleScope PimsOS { @@ -245,7 +275,7 @@ Context "Show-PimsOSBuildWizard" { } Mock Read-Host { - return "6" + return "7" } Mock Write-Log {} @@ -292,7 +322,7 @@ Context "Show-PimsOSBuildWizard" { } - return "6" + return "7" } @@ -349,7 +379,7 @@ Context "Show-PimsOSBuildWizard" { } - return "6" + return "7" } @@ -375,7 +405,7 @@ Context "Show-PimsOSBuildWizard" { } - It "Lance le résumé lorsque le choix 5 est sélectionné" { + It "Lance le résumé lorsque le choix 6 est sélectionné" { InModuleScope PimsOS { @@ -402,11 +432,11 @@ Context "Show-PimsOSBuildWizard" { if (-not $script:SummaryCalled) { - return "5" + return "6" } - return "6" + return "7" } diff --git a/Tools/Migration/Migration.psd1 b/Tools/Migration/Migration.psd1 deleted file mode 100644 index 33b7f09..0000000 --- a/Tools/Migration/Migration.psd1 +++ /dev/null @@ -1,108 +0,0 @@ -@{ - -# ============================================================================ -# PimsOS Migration Framework -# Module Manifest -# ============================================================================ - -# Script module associé au manifeste. -RootModule = 'Migration.psm1' - -# Version du module. -ModuleVersion = '1.0.0' - -# Identifiant unique du module (à remplacer par un GUID généré avec New-Guid). -GUID = 'f6f1e0af-5f84-4ef3-b07f-2d07dd2c96fa' - -# Auteur. -Author = 'Pims' - -# Société. -CompanyName = 'PimsOS' - -# Copyright. -Copyright = '(c) Pims. Tous droits réservés.' - -# Description. -Description = 'Framework de migration et de refactoring PowerShell pour PimsOS Builder.' - -# Version minimale de PowerShell. -PowerShellVersion = '7.6' - -# Éditions compatibles. -CompatiblePSEditions = @( - 'Core' -) - -# Architecture processeur. -ProcessorArchitecture = 'None' - -# Les sous-modules sont chargés dynamiquement par Migration.psm1. -NestedModules = @() - -# Modules requis. -RequiredModules = @() - -# Assemblies requises. -RequiredAssemblies = @() - -# Scripts exécutés lors de l'import. -ScriptsToProcess = @() - -# Formats personnalisés. -FormatsToProcess = @() - -# Types personnalisés. -TypesToProcess = @() - -# Fonctions exportées. -FunctionsToExport = @( - 'Invoke-Migration' -) - -# Cmdlets exportées. -CmdletsToExport = @() - -# Variables exportées. -VariablesToExport = @() - -# Alias exportés. -AliasesToExport = @() - -# Ressources DSC exportées. -DscResourcesToExport = @() - -# Informations privées. -PrivateData = @{ - - PSData = @{ - - Tags = @( - 'PimsOS' - 'Migration' - 'Refactoring' - 'PowerShell' - 'Framework' - ) - - LicenseUri = '' - - ProjectUri = '' - - IconUri = '' - - ReleaseNotes = 'Version initiale du framework Migration.' - - Prerelease = '' - - } - -} - -# URI de l'aide en ligne. -HelpInfoURI = '' - -# Préfixe par défaut des commandes. -DefaultCommandPrefix = '' - -} \ No newline at end of file diff --git a/Tools/Migration/Migration.psm1 b/Tools/Migration/Migration.psm1 deleted file mode 100644 index a46d325..0000000 --- a/Tools/Migration/Migration.psm1 +++ /dev/null @@ -1,109 +0,0 @@ -<# -.SYNOPSIS - Lance le framework de migration PimsOS. - -.DESCRIPTION - Initialise le contexte de migration, découvre les règles disponibles - puis exécute le pipeline de migration. - -.NOTES - Framework : Migration - Projet : PimsOS Builder -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Chargement des classes -#============================================================================== - -$ClassesPath = Join-Path $PSScriptRoot 'Classes' - -if (Test-Path -LiteralPath $ClassesPath) -{ - Get-ChildItem ` - -Path $ClassesPath ` - -Filter '*.ps1' ` - -File | - Sort-Object Name | - ForEach-Object { - - . $_.FullName - - } -} - -#============================================================================== -# Chargement des modules internes -#============================================================================== - -$Modules = @( - 'Modules\Common.psm1' - 'Modules\Scanner.psm1' - 'Modules\Ast.psm1' - 'Modules\Replace.psm1' - 'Modules\Backup.psm1' - 'Modules\Report.psm1' -) - -foreach ($Module in $Modules) -{ - $ModulePath = Join-Path $PSScriptRoot $Module - - if (-not (Test-Path -LiteralPath $ModulePath)) - { - throw "Module introuvable : '$Module'." - } - - Import-Module ` - -Name $ModulePath ` - -Force ` - -DisableNameChecking -} - -#============================================================================== -# Chargement des fonctions privées -#============================================================================== - -$PrivatePath = Join-Path $PSScriptRoot 'Private' - -if (Test-Path -LiteralPath $PrivatePath) -{ - Get-ChildItem ` - -Path $PrivatePath ` - -Filter '*.ps1' ` - -File | - Sort-Object Name | - ForEach-Object { - - . $_.FullName - - } -} - -#============================================================================== -# Chargement des fonctions publiques -#============================================================================== - -$PublicPath = Join-Path $PSScriptRoot 'Public' - -if (Test-Path -LiteralPath $PublicPath) -{ - Get-ChildItem ` - -Path $PublicPath ` - -Filter '*.ps1' ` - -File | - Sort-Object Name | - ForEach-Object { - - . $_.FullName - - } -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function 'Invoke-Migration' \ No newline at end of file diff --git a/Tools/Migration/Modules/Ast.psm1 b/Tools/Migration/Modules/Ast.psm1 deleted file mode 100644 index f012ce5..0000000 --- a/Tools/Migration/Modules/Ast.psm1 +++ /dev/null @@ -1,1005 +0,0 @@ -<# -.SYNOPSIS - Analyseur AST du framework de migration PimsOS. - -.DESCRIPTION - Ce module encapsule l'API AST de PowerShell afin de fournir - une interface stable aux règles de migration. - -.NOTES - - Projet : PimsOS - Module : Ast - Version : 1.1.0 - -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Analyse un script PowerShell -#============================================================================== - -function Get-ScriptAst { - - [CmdletBinding()] - [OutputType([hashtable])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File - - ) - - if (-not (Test-Path -LiteralPath $File.FullName -PathType Leaf)) - { - throw "Le fichier '$($File.FullName)' est introuvable." - } - - $Tokens = $null - $Errors = $null - - $Ast = [System.Management.Automation.Language.Parser]::ParseFile( - $File.FullName, - [ref]$Tokens, - [ref]$Errors - ) - - return @{ - - File = $File - Ast = $Ast - Tokens = @($Tokens) - Errors = @($Errors) - - } - -} - -#============================================================================== -# Retourne les erreurs de parsing -#============================================================================== - -function Get-ParseErrors { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.ParseError[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - if ($null -eq $Script.Errors) - { - return @() - } - - return @($Script.Errors) - -} - -#============================================================================== -# Vérifie si le script contient des erreurs -#============================================================================== - -function Test-ParseErrors { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return (@(Get-ParseErrors -Script $Script).Count -gt 0) - -} - -#============================================================================== -# Retourne les tokens -#============================================================================== - -function Get-Tokens { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.Token[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - if ($null -eq $Script.Tokens) - { - return @() - } - - return @($Script.Tokens) - -} - -#============================================================================== -# Recherche des commandes -#============================================================================== - -function Get-Commands { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.CommandAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.CommandAst] - - }, $true) - ) - -} - -#============================================================================== -# Recherche des commandes par nom -#============================================================================== - -function Find-Commands { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.CommandAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Name - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.CommandAst] -and - $Node.GetCommandName() -eq $Name - - }, $true) - ) - -} - -#============================================================================== -# Retourne le nom d'une commande -#============================================================================== - -function Get-CommandName { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.CommandAst] - $Command - - ) - - return $Command.GetCommandName() - -} - -#============================================================================== -# Retourne les éléments d'une commande -#============================================================================== - -function Get-CommandElements { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.CommandElementAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.CommandAst] - $Command - - ) - - if ($null -eq $Command.CommandElements) - { - return @() - } - - return @($Command.CommandElements) - -} - -#============================================================================== -# Retourne les arguments d'une commande -#============================================================================== - -function Get-CommandArguments { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.CommandElementAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.CommandAst] - $Command - - ) - - $Elements = @(Get-CommandElements -Command $Command) - - if ($Elements.Count -le 1) - { - return @() - } - - return @( - $Elements | - Select-Object -Skip 1 - ) - -} - -#============================================================================== -# Retourne le texte exact d'un argument -#============================================================================== - -function Get-ArgumentText { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.CommandElementAst] - $Argument - - ) - - if ($Argument -is [System.Management.Automation.Language.VariableExpressionAst]) { - return '$' + $Argument.VariablePath.UserPath - } - - return $Argument.Extent.Text - -} - -#============================================================================== -# Retourne tous les textes des arguments -#============================================================================== - -function Get-ArgumentTexts { - - [CmdletBinding()] - [OutputType([string[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.CommandAst] - $Command - - ) - - $Arguments = foreach ($Argument in (Get-CommandArguments -Command $Command) ) - { - Get-ArgumentText -Argument $Argument - } - - return ,([string[]]$Arguments) - -} - -#============================================================================== -# Recherche des fonctions PowerShell -#============================================================================== - -function Get-Functions { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.FunctionDefinitionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.FunctionDefinitionAst] - - }, $true) - ) - -} - -#============================================================================== -# Recherche une fonction par son nom -#============================================================================== - -function Find-Functions { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.FunctionDefinitionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Name - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and - $Node.Name -eq $Name - - }, $true) - ) - -} - -#============================================================================== -# Retourne le nom d'une fonction -#============================================================================== - -function Get-FunctionName { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.FunctionDefinitionAst] - $Function - - ) - - return $Function.Name - -} - -#============================================================================== -# Recherche des classes -#============================================================================== - -function Get-Classes { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.TypeDefinitionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.TypeDefinitionAst] - - }, $true) - ) - -} - -#============================================================================== -# Recherche une classe par son nom -#============================================================================== - -function Find-Classes { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.TypeDefinitionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Name - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.TypeDefinitionAst] -and - $Node.Name -eq $Name - - }, $true) - ) - -} - -#============================================================================== -# Retourne le nom d'une classe -#============================================================================== - -function Get-ClassName { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.TypeDefinitionAst] - $Class - - ) - - return $Class.Name - -} - -#============================================================================== -# Recherche toutes les variables -#============================================================================== - -function Get-Variables { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.VariableExpressionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.VariableExpressionAst] - - }, $true) - ) - -} - -#============================================================================== -# Recherche une variable -#============================================================================== - -function Find-Variables { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.VariableExpressionAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Name - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.VariableExpressionAst] -and - $Node.VariablePath.UserPath -eq $Name - - }, $true) - ) - -} - -#============================================================================== -# Retourne le nom d'une variable -#============================================================================== - -function Get-VariableName { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.VariableExpressionAst] - $Variable - - ) - - return $Variable.VariablePath.UserPath - -} - -#============================================================================== -# Recherche les blocs Param() -#============================================================================== - -function Get-ParameterBlocks { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.ParamBlockAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node -is [System.Management.Automation.Language.ParamBlockAst] - - }, $true) - ) - -} - -#============================================================================== -# Retourne tous les paramètres -#============================================================================== - -function Get-Parameters { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.ParameterAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return @( - foreach ($Block in (Get-ParameterBlocks -Script $Script)) - { - if ($null -ne $Block.Parameters) - { - $Block.Parameters - } - } - ) - -} - -#============================================================================== -# Recherche un paramètre -#============================================================================== - -function Find-Parameters { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.ParameterAst[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Name - - ) - - return @( - Get-Parameters -Script $Script | - Where-Object { - - $_.Name.VariablePath.UserPath -eq $Name - - } - ) - -} - -#============================================================================== -# Retourne le nom d'un paramètre -#============================================================================== - -function Get-ParameterName { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.ParameterAst] - $Parameter - - ) - - return $Parameter.Name.VariablePath.UserPath - -} - -#============================================================================== -# Retourne l'Extent d'un nœud AST -#============================================================================== - -function Get-Extent { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.IScriptExtent])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return $Node.Extent - -} - -#============================================================================== -# Retourne la position de début -#============================================================================== - -function Get-StartOffset { - - [CmdletBinding()] - [OutputType([int])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return (Get-Extent -Node $Node).StartOffset - -} - -#============================================================================== -# Retourne la position de fin -#============================================================================== - -function Get-EndOffset { - - [CmdletBinding()] - [OutputType([int])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return (Get-Extent -Node $Node).EndOffset - -} - -#============================================================================== -# Retourne le texte exact d'un nœud -#============================================================================== - -function Get-Text { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return (Get-Extent -Node $Node).Text - -} - -#============================================================================== -# Retourne la longueur du texte -#============================================================================== - -function Get-TextLength { - - [CmdletBinding()] - [OutputType([int])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return ( - (Get-EndOffset -Node $Node) - - (Get-StartOffset -Node $Node) - ) - -} - -#============================================================================== -# Retourne le type .NET d'un nœud -#============================================================================== - -function Get-NodeType { - - [CmdletBinding()] - [OutputType([System.Type])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return $Node.GetType() - -} - -#============================================================================== -# Indique si un nœud possède des enfants -#============================================================================== - -function Test-HasChildren { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return (@($Node.FindAll({ $true }, $false)).Count -gt 0) - -} - -#============================================================================== -# Retourne tous les enfants directs d'un nœud -#============================================================================== - -function Get-ChildNodes { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.Ast[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.Management.Automation.Language.Ast] - $Node - - ) - - return @( - $Node.FindAll({ $true }, $false) - ) - -} - -#============================================================================== -# Recherche des nœuds d'un type donné -#============================================================================== - -function Find-Nodes { - - [CmdletBinding()] - [OutputType([System.Management.Automation.Language.Ast[]])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script, - - [Parameter(Mandatory)] - [ValidateNotNull()] - [Type] - $NodeType - - ) - - return @( - $Script.Ast.FindAll({ - - param($Node) - - $Node.GetType() -eq $NodeType - - }, $true) - ) - -} - -#============================================================================== -# Retourne des statistiques sur l'AST -#============================================================================== - -function Get-AstStatistics { - - [CmdletBinding()] - [OutputType([pscustomobject])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Script - - ) - - return [PSCustomObject]@{ - - Commands = @(Get-Commands -Script $Script).Count - Functions = @(Get-Functions -Script $Script).Count - Classes = @(Get-Classes -Script $Script).Count - Variables = @(Get-Variables -Script $Script).Count - Parameters = @(Get-Parameters -Script $Script).Count - Errors = @(Get-ParseErrors -Script $Script).Count - Tokens = @(Get-Tokens -Script $Script).Count - - } - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - Get-ScriptAst, - Get-ParseErrors, - Test-ParseErrors, - Get-Tokens, - Get-Commands, - Find-Commands, - Get-CommandName, - Get-CommandElements, - Get-CommandArguments, - Get-ArgumentText, - Get-ArgumentTexts, - Get-Functions, - Find-Functions, - Get-FunctionName, - Get-Classes, - Find-Classes, - Get-ClassName, - Get-Variables, - Find-Variables, - Get-VariableName, - Get-ParameterBlocks, - Get-Parameters, - Find-Parameters, - Get-ParameterName, - Get-Extent, - Get-StartOffset, - Get-EndOffset, - Get-Text, - Get-TextLength, - Get-NodeType, - Test-HasChildren, - Get-ChildNodes, - Find-Nodes, - Get-AstStatistics \ No newline at end of file diff --git a/Tools/Migration/Modules/Backup.psm1 b/Tools/Migration/Modules/Backup.psm1 deleted file mode 100644 index 162a154..0000000 --- a/Tools/Migration/Modules/Backup.psm1 +++ /dev/null @@ -1,581 +0,0 @@ -<# -.SYNOPSIS - Gestionnaire des sauvegardes du framework de migration PimsOS. - -.DESCRIPTION - Ce module gère la création, la restauration et la suppression - des sauvegardes des fichiers modifiés. - -.NOTES - - Projet : PimsOS - Module : Backup - Version : 1.1.0 - -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Retourne le dossier racine des sauvegardes -#============================================================================== - -function Get-BackupRoot { - - [CmdletBinding()] - [OutputType([string])] - - param() - - return (Join-Path ` - -Path (Get-MigrationRoot) ` - -ChildPath "Backups") - -} - -#============================================================================== -# Retourne le chemin relatif d'un fichier du projet -#============================================================================== - -function Get-RelativeProjectPath { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File - - ) - - $ProjectRoot = Get-ProjectRoot - - if (-not $File.FullName.StartsWith( - $ProjectRoot, - [System.StringComparison]::OrdinalIgnoreCase)) - { - throw "Le fichier '$($File.FullName)' n'appartient pas au projet." - } - - return $File.FullName.Substring( - $ProjectRoot.Length - ).TrimStart('\') - -} - -#============================================================================== -# Retourne le dossier d'une session -#============================================================================== - -function Get-BackupPath { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [hashtable] - $Session - - ) - - return $Session.BackupRoot - -} - -#============================================================================== -# Retourne le chemin d'une sauvegarde -#============================================================================== - -function Get-BackupFile { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [hashtable] - $Session - - ) - - $RelativePath = Get-RelativeProjectPath -File $File - - return Join-Path ` - -Path (Get-BackupPath -Session $Session) ` - -ChildPath $RelativePath - -} - -#============================================================================== -# Vérifie une session de sauvegarde -#============================================================================== - -function Test-BackupSession { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [hashtable] - $Session - - ) - - if ($null -eq $Session) - { - return $false - } - - foreach ($Property in @( - 'Id' - 'Started' - 'BackupRoot' - )) - { - if ($Session[$Property] -eq $null) - { - return $false - } - } - - return (Test-Path ` - -LiteralPath $Session.BackupRoot ` - -PathType Container) - -} - -#============================================================================== -# Crée une nouvelle session -#============================================================================== - -function New-BackupSession { - - [CmdletBinding()] - [OutputType([hashtable])] - - param() - - $Id = Get-Date -Format "yyyy-MM-dd_HH-mm-ss-fff" - - $Folder = Join-Path ` - -Path (Get-BackupRoot) ` - -ChildPath $Id - - if (-not (Test-Path -LiteralPath $Folder)) - { - New-Item ` - -ItemType Directory ` - -Path $Folder ` - -Force | Out-Null - } - - return @{ - - Id = $Id - - Started = (Get-Date).ToUniversalTime() - - BackupRoot = $Folder - - } - -} - -#============================================================================== -# Retourne tous les fichiers sauvegardés d'une session -#============================================================================== - -function Get-BackupFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [Parameter(Mandatory)] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - return Get-ChildItem ` - -LiteralPath $Session.BackupRoot ` - -File ` - -Recurse - -} -#============================================================================== -# Crée la sauvegarde d'un fichier -#============================================================================== - -function New-Backup { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - if (-not $File.Exists) - { - throw "Le fichier '$($File.FullName)' est introuvable." - } - - $Destination = Get-BackupFile ` - -File $File ` - -Session $Session - - $DestinationFolder = Split-Path ` - -Path $Destination ` - -Parent - - if (-not (Test-Path -LiteralPath $DestinationFolder)) - { - New-Item ` - -ItemType Directory ` - -Path $DestinationFolder ` - -Force | Out-Null - } - - Copy-Item ` - -LiteralPath $File.FullName ` - -Destination $Destination ` - -Force - -} - -#============================================================================== -# Restaure un fichier sauvegardé -#============================================================================== - -function Restore-Backup { - - [CmdletBinding(SupportsShouldProcess)] - [OutputType([System.IO.FileInfo])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - $BackupFile = Get-BackupFile ` - -File $File ` - -Session $Session - - if (-not (Test-Path -LiteralPath $BackupFile)) - { - throw "Aucune sauvegarde trouvée pour '$($File.FullName)'." - } - - if ($PSCmdlet.ShouldProcess( - $File.FullName, - "Restaurer le fichier")) - { - Copy-Item ` - -LiteralPath $BackupFile ` - -Destination $File.FullName ` - -Force - } - - return Get-Item -LiteralPath $File.FullName - -} - -#============================================================================== -# Supprime la sauvegarde d'un fichier -#============================================================================== - -function Remove-Backup { - - [CmdletBinding(SupportsShouldProcess)] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - $BackupFile = Get-BackupFile ` - -File $File ` - -Session $Session - - if (-not (Test-Path -LiteralPath $BackupFile)) - { - return - } - - if ($PSCmdlet.ShouldProcess( - $BackupFile, - "Supprimer la sauvegarde")) - { - Remove-Item ` - -LiteralPath $BackupFile ` - -Force - } - -} - -#============================================================================== -# Vérifie qu'une sauvegarde existe -#============================================================================== - -function Test-Backup { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - return $false - } - - return (Test-Path ` - -LiteralPath ( - Get-BackupFile ` - -File $File ` - -Session $Session - )) - -} - -#============================================================================== -# Retourne des statistiques sur une session -#============================================================================== - -function Get-BackupStatistics { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - $Files = @(Get-BackupFiles -Session $Session) - - $TotalSize = 0 - - foreach ($File in $Files) - { - $TotalSize += $File.Length - } - - [PSCustomObject]@{ - - Session = $Session.Id - - Started = $Session.Started - - Files = $Files.Count - - Size = $TotalSize - - BackupRoot = $Session.BackupRoot - - } - -} -#============================================================================== -# Retourne les sessions de sauvegarde -#============================================================================== - -function Get-BackupSessions { - - [CmdletBinding()] - [OutputType([System.IO.DirectoryInfo[]])] - - param() - - $BackupRoot = Get-BackupRoot - - if (-not (Test-Path -LiteralPath $BackupRoot)) - { - return @() - } - - return Get-ChildItem ` - -LiteralPath $BackupRoot ` - -Directory | - Sort-Object Name -Descending - -} - -#============================================================================== -# Supprime une session complète -#============================================================================== - -function Remove-BackupSession { - - [CmdletBinding(SupportsShouldProcess)] - - param( - - [Parameter(Mandatory)] - [ValidateNotNull()] - [hashtable] - $Session - - ) - - if (-not (Test-BackupSession -Session $Session)) - { - throw "Session de sauvegarde invalide." - } - - if ($PSCmdlet.ShouldProcess( - $Session.BackupRoot, - "Supprimer la session de sauvegarde")) - { - Remove-Item ` - -LiteralPath $Session.BackupRoot ` - -Recurse ` - -Force - } - -} - -#============================================================================== -# Nettoie les anciennes sauvegardes -#============================================================================== - -function Clear-Backups { - - [CmdletBinding(SupportsShouldProcess)] - - param( - - [ValidateRange(1,[int]::MaxValue)] - [int] - $Keep = 10 - - ) - - $Sessions = @(Get-BackupSessions) - - if ($Sessions.Count -le $Keep) - { - return - } - - $Sessions | - Select-Object -Skip $Keep | - ForEach-Object { - - if ($PSCmdlet.ShouldProcess( - $_.FullName, - "Supprimer la session de sauvegarde")) - { - Remove-Item ` - -LiteralPath $_.FullName ` - -Recurse ` - -Force - } - - } - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - Get-BackupRoot, - Get-RelativeProjectPath, - Get-BackupPath, - Get-BackupFile, - Test-BackupSession, - New-BackupSession, - Get-BackupFiles, - New-Backup, - Restore-Backup, - Remove-Backup, - Test-Backup, - Get-BackupStatistics, - Get-BackupSessions, - Remove-BackupSession, - Clear-Backups \ No newline at end of file diff --git a/Tools/Migration/Modules/Common.psm1 b/Tools/Migration/Modules/Common.psm1 deleted file mode 100644 index da49935..0000000 --- a/Tools/Migration/Modules/Common.psm1 +++ /dev/null @@ -1,494 +0,0 @@ -<# -.SYNOPSIS - Fonctions communes du framework de migration PimsOS. - -.DESCRIPTION - Ce module fournit les fonctions utilitaires partagées - par tous les modules du framework de migration. - -.NOTES - - Projet : PimsOS - Module : Common - Version : 1.1.0 - -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Variables privées -#============================================================================== - -$script:ProjectRoot = ( - Resolve-Path ( - Join-Path $PSScriptRoot "..\..\.." - ) -).Path - -$script:MigrationRoot = ( - Resolve-Path ( - Join-Path $PSScriptRoot ".." - ) -).Path - -$script:FrameworkName = "PimsOS Migration Framework" - -$script:FrameworkVersion = "1.1.0" - -$script:ProjectExtensions = @( - "*.ps1", - "*.psm1", - "*.psd1" -) - -$script:ExcludedFolders = @( - ".git", - ".github", - ".vs", - ".vscode", - "Logs", - "Output", - "Mount", - "ISO", - "Packages", - "bin", - "obj" -) - -$script:Colors = @{ - - Title = "White" - Section = "Magenta" - - Info = "Cyan" - Success = "Green" - Warning = "Yellow" - Error = "Red" - - Debug = "DarkGray" - Verbose = "Gray" - -} - -#============================================================================== -# Informations du framework -#============================================================================== - -function Get-ProjectRoot { - - [CmdletBinding()] - param() - - return $script:ProjectRoot - -} - -function Get-MigrationRoot { - - [CmdletBinding()] - param() - - return $script:MigrationRoot - -} - -function Get-FrameworkName { - - [CmdletBinding()] - param() - - return $script:FrameworkName - -} - -function Get-FrameworkVersion { - - [CmdletBinding()] - param() - - return $script:FrameworkVersion - -} - -function Get-ProjectExtensions { - - [CmdletBinding()] - param() - - return $script:ProjectExtensions - -} - -function Get-ExcludedFolders { - - [CmdletBinding()] - param() - - return $script:ExcludedFolders - -} - -#============================================================================== -# Outils généraux -#============================================================================== - -function Test-PowerShellVersion { - - [CmdletBinding()] - param( - - [Version] - $MinimumVersion = "7.6" - - ) - - return ( - $PSVersionTable.PSVersion -ge $MinimumVersion - ) - -} - -function Test-MigrationPath { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Path - - ) - - return ( - Test-Path ` - -LiteralPath $Path ` - -PathType Any - ) - -} - -function Get-RelativePath { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $BasePath, - - [Parameter(Mandatory)] - [string] - $Path - - ) - - return [System.IO.Path]::GetRelativePath( - $BasePath, - $Path - ) - -} - -#============================================================================== -# Chronomètre -#============================================================================== - -function New-Stopwatch { - - [CmdletBinding()] - param() - - return [System.Diagnostics.Stopwatch]::StartNew() - -} - -function Get-ElapsedTime { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [System.Diagnostics.Stopwatch] - $Stopwatch - - ) - - return $Stopwatch.Elapsed - -} - -#============================================================================== -# Objets métier -#============================================================================== - -function New-MigrationRule { - - [CmdletBinding()] - param() - - return [PSCustomObject]@{ - - ObjectType = "MigrationRule" - - Name = "" - Description = "" - - Enabled = $true - Priority = 100 - - Script = $null - - } - -} - -function New-MigrationContext { - - [CmdletBinding()] - param() - - return [PSCustomObject]@{ - - ObjectType = "MigrationContext" - - # Informations du projet - ProjectName = "" - ProjectRoot = "" - - # Chemins - ToolsPath = "" - MigrationPath = "" - ModulesPath = "" - TestsPath = "" - - # Exécution - Rule = "" - AnalyzeOnly = $false - ExecuteAll = $false - - # Résultats - Results = [System.Collections.Generic.List[object]]::new() - - } - -} - -#============================================================================== -# Résultats -#============================================================================== - -function New-MigrationResult { - - [CmdletBinding()] - param( - - [string] - $File, - - [string] - $Rule, - - [bool] - $Modified = $false, - - [string] - $Message = "" - - ) - - return [PSCustomObject]@{ - - ObjectType = "MigrationResult" - - File = $File - Rule = $Rule - Modified = $Modified - Message = $Message - - } - -} - -function New-MigrationError { - - [CmdletBinding()] - param( - - [string] - $File, - - [string] - $Rule, - - [string] - $Message - - ) - - return [PSCustomObject]@{ - - ObjectType = "MigrationError" - - File = $File - Rule = $Rule - Message = $Message - - } - -} - -#============================================================================== -# Affichage -#============================================================================== - -function Write-Blank { - - [CmdletBinding()] - param() - - Write-Host "" - -} - -function Write-Banner { - - [CmdletBinding()] - param( - - [string] - $Title = (Get-FrameworkName), - - [string] - $Version = (Get-FrameworkVersion) - - ) - - Write-Blank - - Write-Host "==================================================" ` - -ForegroundColor Cyan - - Write-Host (" {0}" -f $Title) ` - -ForegroundColor $script:Colors.Title - - Write-Host (" Version : {0}" -f $Version) ` - -ForegroundColor DarkGray - - Write-Host "==================================================" ` - -ForegroundColor Cyan - - Write-Blank - -} - -function Write-Section { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Title - - ) - - Write-Blank - - Write-Host ("[{0}]" -f $Title) ` - -ForegroundColor $script:Colors.Section - -} - -function Write-Info { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Message - - ) - - Write-Host $Message ` - -ForegroundColor $script:Colors.Info - -} - -function Write-Success { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Message - - ) - - Write-Host $Message ` - -ForegroundColor $script:Colors.Success - -} - -function Write-WarningMessage { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Message - - ) - - Write-Host $Message ` - -ForegroundColor $script:Colors.Warning - -} - -function Write-ErrorMessage { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [string] - $Message - - ) - - Write-Host $Message ` - -ForegroundColor $script:Colors.Error - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - Get-ProjectRoot, - Get-MigrationRoot, - Get-FrameworkName, - Get-FrameworkVersion, - Get-ProjectExtensions, - Get-ExcludedFolders, - Test-PowerShellVersion, - Test-MigrationPath, - Get-RelativePath, - New-Stopwatch, - Get-ElapsedTime, - New-MigrationRule, - New-MigrationContext, - New-MigrationResult, - New-MigrationError, - Write-Blank, - Write-Banner, - Write-Section, - Write-Info, - Write-Success, - Write-WarningMessage, - Write-ErrorMessage \ No newline at end of file diff --git a/Tools/Migration/Modules/Logger.psm1 b/Tools/Migration/Modules/Logger.psm1 deleted file mode 100644 index d830578..0000000 --- a/Tools/Migration/Modules/Logger.psm1 +++ /dev/null @@ -1,179 +0,0 @@ -Set-StrictMode -Version Latest - -#================================================== -# Recherche les appels Write-Log -#================================================== - -function Find-LoggerCommands { - - [CmdletBinding()] - param( - [Parameter(Mandatory)] - $Script - ) - - return Find-Commands ` - -Script $Script ` - -Name "Write-Log" - -} - -#================================================== -# Analyse un appel Write-Log -#================================================== - -function Get-LoggerCallInfo { - - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [System.Management.Automation.Language.CommandAst] - $Command - ) - - $Info = [ordered]@{ - - MessageExpression = $null - - Level = "INFO" - - Start = $Command.Extent.StartOffset - - End = $Command.Extent.EndOffset - - } - - $Elements = $Command.CommandElements - - for ($i = 1; $i -lt $Elements.Count; $i++) { - - $Current = $Elements[$i] - - if ($Current -is [System.Management.Automation.Language.CommandParameterAst]) { - - switch ($Current.ParameterName) { - - "Message" { - - if ($i + 1 -lt $Elements.Count) { - $Info.MessageExpression = - $Elements[$i + 1].Extent.Text - } - - } - - "Level" { - - if ($i + 1 -lt $Elements.Count) { - $Info.Level = - $Elements[$i + 1].Extent.Text.ToUpper() - } - - } - - } - - continue - - } - - } - - if (-not $Info.MessageExpression) { - - if ($Elements.Count -ge 2) { - $Info.MessageExpression = - $Elements[1].Extent.Text - } - - } - - if ($Info.Level -eq "INFO") { - - if ($Elements.Count -ge 3) { - - $Candidate = - $Elements[2].Extent.Text.ToUpper() - - if ($Candidate -match "SUCCESS|WARNING|ERROR|DEBUG") { - $Info.Level = $Candidate - } - - } - - } - - return [PSCustomObject]$Info - -} - -#================================================== -# Détermine la nouvelle commande -#================================================== - -function Get-NewLoggerCommand { - - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [pscustomobject] - $Call - ) - - switch ($Call.Level) { - - "SUCCESS" { - $Command = "Write-Success" - } - - "WARNING" { - $Command = "Write-WarningLog" - } - - "ERROR" { - $Command = "Write-ErrorLog" - } - - "DEBUG" { - $Command = "Write-DebugLog" - } - - default { - $Command = "Write-Info" - } - - } - - return "$Command $($Call.MessageExpression)" - -} - -#================================================== -# Crée un remplacement Logger -#================================================== - -function New-LoggerReplacement { - - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [System.Management.Automation.Language.CommandAst] - $Command - ) - - $Call = Get-LoggerCallInfo -Command $Command - - $ReplacementText = Get-NewLoggerCommand -Call $Call - - return New-Replacement ` - -Start $Call.Start ` - -End $Call.End ` - -Text $ReplacementText - -} - -Export-ModuleMember -Function ` - Find-LoggerCommands, ` - Get-LoggerCallInfo, ` - Get-NewLoggerCommand, ` - New-LoggerReplacement \ No newline at end of file diff --git a/Tools/Migration/Modules/Replace.psm1 b/Tools/Migration/Modules/Replace.psm1 deleted file mode 100644 index 7e30949..0000000 --- a/Tools/Migration/Modules/Replace.psm1 +++ /dev/null @@ -1,808 +0,0 @@ -<# -.SYNOPSIS - Moteur de remplacement du framework de migration PimsOS. - -.DESCRIPTION - Ce module gère la préparation des remplacements de texte. - Les modifications ne sont pas appliquées immédiatement, - mais stockées afin d'être validées puis exécutées. - -.NOTES - - Projet : PimsOS - Module : Replace - Version : 1.0.0 - -#> - -Set-StrictMode -Version Latest - -#================================================== -# Crée une nouvelle collection de remplacements -#================================================== - -<# -.SYNOPSIS - Crée une collection vide de remplacements. - -.OUTPUTS - System.Collections.Generic.List[object] - -#> - -function New-ReplacementCollection { - - [CmdletBinding()] - [OutputType([System.Collections.Generic.List[object]])] - - param() - - return ,([System.Collections.Generic.List[object]]::new()) - -} - -#================================================== -# Crée un remplacement -#================================================== - -<# -.SYNOPSIS - Crée un remplacement. - -.OUTPUTS - PSCustomObject - -#> - -function New-Replacement { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [ValidateRange(0,[int]::MaxValue)] - [int] - $Start, - - [Parameter(Mandatory)] - [ValidateRange(0,[int]::MaxValue)] - [int] - $End, - - [Parameter(Mandatory)] - [AllowEmptyString()] - [string] - $Original, - - [Parameter(Mandatory)] - [AllowEmptyString()] - [string] - $Replacement, - - [string] - $Description = "" - - ) - - if ($End -lt $Start) - { - throw "La position de fin doit être supérieure ou égale à la position de début." - } - - return [PSCustomObject]@{ - - PSTypeName = 'PimsOS.Migration.Replacement' - - Start = $Start - - End = $End - - Length = $End - $Start - - Original = $Original - - Replacement = $Replacement - - Description = $Description - - Applied = $false - - } - -} - -#================================================== -# Ajoute un remplacement -#================================================== - -<# -.SYNOPSIS - Ajoute un remplacement à une collection. - -#> - -function Add-Replacement { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [object] - $Collection, - - [Parameter(Mandatory)] - [PSCustomObject] - $Replacement - - ) - - if (-not (Test-Replacement -Replacement $Replacement)) - { - throw "Le remplacement fourni est invalide." - } - - $Collection.Add($Replacement) - -} - -#================================================== -# Retourne le nombre de remplacements -#================================================== - -function Get-ReplacementCount { - - [CmdletBinding()] - [OutputType([int])] - - param( - - [Parameter(Mandatory)] - [object] - $Collection - - ) - - return $Collection.Count - -} - -#================================================== -# Vérifie un remplacement -#================================================== - -<# -.SYNOPSIS - Vérifie qu'un remplacement est valide. - -.OUTPUTS - System.Boolean - -#> - -function Test-Replacement { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Replacement - - ) - - - $RequiredProperties = @( - 'Start', - 'End', - 'Length', - 'Original', - 'Replacement', - 'Description', - 'Applied' - ) - - foreach ($Property in $RequiredProperties) - { - if ($Replacement.PSObject.Properties[$Property] -eq $null) - { - return $false - } - } - - if ($Replacement.Start -lt 0) - { - return $false - } - - if ($Replacement.End -lt $Replacement.Start) - { - return $false - } - - if ($Replacement.Length -ne ($Replacement.End - $Replacement.Start)) - { - return $false - } - - if ($null -eq $Replacement.Replacement) - { - return $false - } - - return $true - - -} - -#================================================== -# Vérifie une collection de remplacements -#================================================== - -<# -.SYNOPSIS - Vérifie qu'une collection est valide. - -.DESCRIPTION - Vérifie : - - - chaque remplacement - - - les chevauchements - -.OUTPUTS - System.Boolean - -#> - -function Test-Replacements { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [AllowEmptyCollection()] - [System.Collections.Generic.List[object]] - $Collection - - ) - - foreach ($Replacement in $Collection) - { - if (-not (Test-Replacement -Replacement $Replacement)) - { - return $false - } - } - - $Sorted = @( - $Collection | - Sort-Object Start - ) - - for ($i = 1; $i -lt $Sorted.Count; $i++) - { - if ($Sorted[$i].Start -lt $Sorted[$i-1].End) - { - return $false - } - } - - return $true - -} - -#================================================== -# Trie les remplacements -#================================================== - -<# -.SYNOPSIS - Trie les remplacements par ordre décroissant. - -.DESCRIPTION - Les remplacements sont triés du dernier vers le premier - afin de préserver les offsets lors de l'application. - -.OUTPUTS - PSCustomObject[] - -#> - -function Sort-Replacements { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [AllowEmptyCollection()] - [System.Collections.Generic.List[object]] - $Collection - - ) - - return $Collection | - Sort-Object ` - @{Expression='Start';Descending=$true}, - @{Expression='End';Descending=$true} - -} - -#================================================== -# Applique les remplacements en mémoire -#================================================== - -<# -.SYNOPSIS - Produit le nouveau contenu du fichier. - -.OUTPUTS - System.String - -#> - -function Convert-Replacements { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [string] - $Content, - - [Parameter(Mandatory)] - [AllowEmptyCollection()] - [System.Collections.Generic.List[object]] - $Collection - - ) - - if (-not (Test-Replacements -Collection $Collection)) - { - throw "La collection de remplacements est invalide." - } - - $Result = $Content - - foreach ($Replacement in (Sort-Replacements -Collection $Collection)) - { - if ($Replacement.End -gt $Result.Length) - { - throw "Le remplacement dépasse la taille du contenu." - } - - $Current = $Result.Substring( - $Replacement.Start, - $Replacement.Length - ) - - if ($Current -ne $Replacement.Original) - { - $Message = @( - "Le remplacement ne peut pas être appliqué." - "" - "Description : $($Replacement.Description)" - "" - "Position : $($Replacement.Start)" - "" - "Attendu :" - $Replacement.Original - "" - "Trouvé :" - $Current - ) -join [Environment]::NewLine - - throw $Message - } - - $Result = $Result.Remove( - $Replacement.Start, - $Replacement.Length - ).Insert( - $Replacement.Start, - $Replacement.Replacement - ) - } - - return $Result - -} - -#================================================== -# Applique les remplacements à un fichier -#================================================== - -<# -.SYNOPSIS - Applique les remplacements sur un fichier. - -.DESCRIPTION - Lit le contenu du fichier, applique les remplacements - puis réécrit le fichier sur le disque. -#> - -function Invoke-Replacements { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - if (-not $File.Exists) - { - throw "Le fichier '$($File.FullName)' est introuvable." - } - - $Content = Get-Content ` - -Path $File.FullName ` - -Raw - - $NewContent = Convert-Replacements ` - -Content $Content ` - -Collection $Collection - - [System.IO.File]::WriteAllText( - $File.FullName, - $NewContent, - [System.Text.UTF8Encoding]::new($false) - ) - - foreach($Replacement in $Collection) - { - $Replacement.Applied = $true - } - -} -#============================================================================== -# Trie les remplacements -#============================================================================== - - - -#============================================================================== -# Retourne les remplacements triés par ordre croissant -#============================================================================== - -function Get-Replacements { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - return $Collection | - Sort-Object Start, End - -} - -#============================================================================== -# Retourne les remplacements déjà appliqués -#============================================================================== - -function Get-AppliedReplacements { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - return $Collection | - Where-Object Applied - -} - -#============================================================================== -# Retourne les remplacements non appliqués -#============================================================================== - -function Get-PendingReplacements { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - return $Collection | - Where-Object { -not $_.Applied } - -} - -#============================================================================== -# Remet les remplacements à l'état initial -#============================================================================== - -function Reset-Replacements { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - foreach ($Replacement in $Collection) - { - $Replacement.Applied = $false - } - -} - -#============================================================================== -# Retourne des statistiques -#============================================================================== - -function Get-ReplacementStatistics { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - $Applied = Get-AppliedReplacements -Collection $Collection - - $Pending = Get-PendingReplacements -Collection $Collection - - [PSCustomObject]@{ - - Total = $Collection.Count - - Applied = $Applied.Count - - Pending = $Pending.Count - - Valid = Test-Replacements -Collection $Collection - - } - -} - -#============================================================================== -# Lit le contenu d'un fichier -#============================================================================== - -function Get-FileContent { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File - - ) - - if (-not $File.Exists) - { - throw "Le fichier '$($File.FullName)' est introuvable." - } - - return Get-Content ` - -LiteralPath $File.FullName ` - -Raw - -} - -#============================================================================== -# Écrit le contenu d'un fichier en UTF-8 sans BOM -#============================================================================== - -function Set-FileContent { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [string] - $Content - - ) - - [System.IO.File]::WriteAllText( - - $File.FullName, - - $Content, - - [System.Text.UTF8Encoding]::new($false) - - ) - -} -#============================================================================== -# Applique les remplacements en mémoire -#============================================================================== - -function Convert-Replacements { - - [CmdletBinding()] - [OutputType([string])] - - param( - - [Parameter(Mandatory)] - [string] - $Content, - - [Parameter(Mandatory)] - [AllowEmptyCollection()] - [System.Collections.Generic.List[object]] - $Collection - - ) - - if (-not (Test-Replacements -Collection $Collection)) - { - throw "La collection de remplacements est invalide." - } - - $Result = $Content - - foreach ($Replacement in (Sort-Replacements -Collection $Collection)) - { - if ($Replacement.End -gt $Result.Length) - { - throw "Le remplacement dépasse la taille du contenu." - } - - $Current = $Result.Substring( - $Replacement.Start, - $Replacement.Length - ) - - if ($Current -ne $Replacement.Original) - { - $Message = @( - "Le remplacement ne peut pas être appliqué." - "" - "Description : $($Replacement.Description)" - "" - "Position : $($Replacement.Start)" - "" - "Attendu :" - $Replacement.Original - "" - "Trouvé :" - $Current - ) -join [Environment]::NewLine - - throw $Message - } - - $Result = $Result.Remove( - $Replacement.Start, - $Replacement.Length - ).Insert( - $Replacement.Start, - $Replacement.Replacement - ) - } - - return $Result - -} - -#============================================================================== -# Applique les remplacements sur un fichier -#============================================================================== - -function Invoke-Replacements { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File, - - [Parameter(Mandatory)] - [System.Collections.Generic.List[object]] - $Collection - - ) - - if (-not $File.Exists) - { - throw "Le fichier '$($File.FullName)' est introuvable." - } - - if (-not (Test-Replacements -Collection $Collection)) - { - throw "La collection de remplacements est invalide." - } - - $Content = Get-FileContent -File $File - - $NewContent = Convert-Replacements ` - -Content $Content ` - -Collection $Collection - - Set-FileContent ` - -File $File ` - -Content $NewContent - - foreach ($Replacement in $Collection) - { - $Replacement.Applied = $true - } - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - New-ReplacementCollection, - New-Replacement, - Add-Replacement, - Remove-Replacement, - Clear-Replacements, - Get-ReplacementCount, - Find-Replacement, - Test-Replacement, - Test-Replacements, - Sort-Replacements, - Get-Replacements, - Get-AppliedReplacements, - Get-PendingReplacements, - Reset-Replacements, - Get-ReplacementStatistics, - Get-FileContent, - Set-FileContent, - Convert-Replacements, - Invoke-Replacements \ No newline at end of file diff --git a/Tools/Migration/Modules/Report.psm1 b/Tools/Migration/Modules/Report.psm1 deleted file mode 100644 index 935b36e..0000000 --- a/Tools/Migration/Modules/Report.psm1 +++ /dev/null @@ -1,788 +0,0 @@ -<# -.SYNOPSIS - Gestion des rapports du framework de migration PimsOS. - -.DESCRIPTION - Ce module crée et gère les rapports de migration. - Les rapports centralisent les informations produites par les - différents modules du framework. - -.NOTES - - Projet : PimsOS - Module : Report - Version : 1.1.0 - -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Crée un nouveau rapport -#============================================================================== - -function New-Report { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param() - - return [PSCustomObject]@{ - - PSTypeName = 'PimsOS.Migration.Report' - - ReportId = [guid]::NewGuid() - - StartTime = Get-Date - - EndTime = $null - - Duration = [TimeSpan]::Zero - - Files = [System.Collections.Generic.List[object]]::new() - - Replacements = [System.Collections.Generic.List[object]]::new() - - Messages = [System.Collections.Generic.List[object]]::new() - - Warnings = [System.Collections.Generic.List[object]]::new() - - Errors = [System.Collections.Generic.List[object]]::new() - - } - -} - -#============================================================================== -# Vérifie qu'un rapport est valide -#============================================================================== - -function Test-Report { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - $RequiredProperties = @( - 'ReportId' - 'StartTime' - 'EndTime' - 'Duration' - 'Files' - 'Replacements' - 'Messages' - 'Warnings' - 'Errors' - ) - - foreach ($Property in $RequiredProperties) - { - if ($Report.PSObject.Properties[$Property] -eq $null) - { - return $false - } - } - - return $true - -} - -#============================================================================== -# Vide un rapport -#============================================================================== - -function Clear-Report { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Report.Files.Clear() - - $Report.Replacements.Clear() - - $Report.Messages.Clear() - - $Report.Warnings.Clear() - - $Report.Errors.Clear() - - $Report.EndTime = $null - - $Report.Duration = [TimeSpan]::Zero - -} - -#============================================================================== -# Crée une entrée de rapport -#============================================================================== - -function New-ReportEntry { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [ValidateSet( - 'Message', - 'Warning', - 'Error' - )] - [string] - $Type, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Text - - ) - - return [PSCustomObject]@{ - - PSTypeName = 'PimsOS.Migration.ReportEntry' - - Time = Get-Date - - Type = $Type - - Text = $Text - - } - -} - -#============================================================================== -# Ajoute un message -#============================================================================== - -function Add-ReportMessage { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Text - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Report.Messages.Add( - - (New-ReportEntry ` - -Type Message ` - -Text $Text) - - ) - -} - -#============================================================================== -# Ajoute un avertissement -#============================================================================== - -function Add-ReportWarning { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Text - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Report.Warnings.Add( - - (New-ReportEntry ` - -Type Warning ` - -Text $Text) - - ) - -} - -#============================================================================== -# Ajoute une erreur -#============================================================================== - -function Add-ReportError { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Text - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Report.Errors.Add( - - (New-ReportEntry ` - -Type Error ` - -Text $Text) - - ) - -} -#============================================================================== -# Crée une entrée de fichier -#============================================================================== - -function New-ReportFile { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Path - - ) - - return [PSCustomObject]@{ - - PSTypeName = 'PimsOS.Migration.ReportFile' - - Path = $Path - - Time = Get-Date - - Exists = Test-Path -LiteralPath $Path - - } - -} - -#============================================================================== -# Ajoute un fichier au rapport -#============================================================================== - -function Add-ReportFile { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] - $Path - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Report.Files.Add( - - (New-ReportFile -Path $Path) - - ) - -} - -#============================================================================== -# Ajoute un remplacement au rapport -#============================================================================== - -function Add-ReportReplacement { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [PSCustomObject] - $Replacement - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - if (-not (Test-Replacement -Replacement $Replacement)) - { - throw "Le remplacement fourni est invalide." - } - - $Report.Replacements.Add($Replacement) - -} - -#============================================================================== -# Retourne les messages -#============================================================================== - -function Get-ReportMessages { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - return $Report.Messages - -} - -#============================================================================== -# Retourne les avertissements -#============================================================================== - -function Get-ReportWarnings { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - return $Report.Warnings - -} - -#============================================================================== -# Retourne les erreurs -#============================================================================== - -function Get-ReportErrors { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - return $Report.Errors - -} - -#============================================================================== -# Retourne les fichiers -#============================================================================== - -function Get-ReportFiles { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - return $Report.Files - -} - -#============================================================================== -# Retourne les remplacements -#============================================================================== - -function Get-ReportReplacements { - - [CmdletBinding()] - [OutputType([PSCustomObject[]])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - return $Report.Replacements - -} -#============================================================================== -# Termine un rapport -#============================================================================== - -function Complete-Report { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - if ($null -ne $Report.EndTime) - { - throw "Le rapport est déjà terminé." - } - - $Report.EndTime = Get-Date - - $Report.Duration = - $Report.EndTime - $Report.StartTime - -} - -#============================================================================== -# Retourne la durée d'un rapport -#============================================================================== - -function Get-ReportDuration { - - [CmdletBinding()] - [OutputType([TimeSpan])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - if ($null -eq $Report.EndTime) - { - return (Get-Date) - $Report.StartTime - } - - return $Report.Duration - -} - -#============================================================================== -# Retourne les statistiques -#============================================================================== - -function Get-ReportStatistics { - - [CmdletBinding()] - [OutputType([PSCustomObject])] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Files = @(Get-ReportFiles -Report $Report) - $Replacements = @(Get-ReportReplacements -Report $Report) - $Messages = @(Get-ReportMessages -Report $Report) - $Warnings = @(Get-ReportWarnings -Report $Report) - $Errors = @(Get-ReportErrors -Report $Report) - - return [PSCustomObject]@{ - - ReportId = $Report.ReportId - - Files = $Files.Count - - Replacements = $Replacements.Count - - Messages = $Messages.Count - - Warnings = $Warnings.Count - - Errors = $Errors.Count - - Duration = Get-ReportDuration -Report $Report - - Completed = ($null -ne $Report.EndTime) - - } - -} - -#============================================================================== -# Exporte le rapport au format JSON -#============================================================================== - -function Export-ReportJson { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Json = $Report | - ConvertTo-Json ` - -Depth 10 - - [System.IO.File]::WriteAllText( - - $File.FullName, - - $Json, - - [System.Text.UTF8Encoding]::new($false) - - ) - -} - -#============================================================================== -# Exporte le rapport au format texte -#============================================================================== - -function Export-ReportText { - - [CmdletBinding()] - - param( - - [Parameter(Mandatory)] - [PSCustomObject] - $Report, - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File - - ) - - if (-not (Test-Report -Report $Report)) - { - throw "Le rapport est invalide." - } - - $Stats = Get-ReportStatistics -Report $Report - - $Lines = [System.Collections.Generic.List[string]]::new() - - $Lines.Add("========================================") - $Lines.Add(" Rapport de migration PimsOS") - $Lines.Add("========================================") - $Lines.Add("") - $Lines.Add("Rapport : $($Report.ReportId)") - $Lines.Add("Début : $($Report.StartTime)") - $Lines.Add("Fin : $($Report.EndTime)") - $Lines.Add("Durée : $($Stats.Duration)") - $Lines.Add("") - $Lines.Add("Fichiers : $($Stats.Files)") - $Lines.Add("Remplacements : $($Stats.Replacements)") - $Lines.Add("Messages : $($Stats.Messages)") - $Lines.Add("Avertissements: $($Stats.Warnings)") - $Lines.Add("Erreurs : $($Stats.Errors)") - $Lines.Add("") - - if ($Report.Messages.Count) - { - $Lines.Add("Messages") - $Lines.Add("----------------------------------------") - - foreach ($Entry in $Report.Messages) - { - $Lines.Add("[$($Entry.Time)] $($Entry.Text)") - } - - $Lines.Add("") - } - - if ($Report.Warnings.Count) - { - $Lines.Add("Avertissements") - $Lines.Add("----------------------------------------") - - foreach ($Entry in $Report.Warnings) - { - $Lines.Add("[$($Entry.Time)] $($Entry.Text)") - } - - $Lines.Add("") - } - - if ($Report.Errors.Count) - { - $Lines.Add("Erreurs") - $Lines.Add("----------------------------------------") - - foreach ($Entry in $Report.Errors) - { - $Lines.Add("[$($Entry.Time)] $($Entry.Text)") - } - - $Lines.Add("") - } - - [System.IO.File]::WriteAllLines( - - $File.FullName, - - $Lines, - - [System.Text.UTF8Encoding]::new($false) - - ) - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - New-Report, - Test-Report, - Clear-Report, - New-ReportEntry, - Add-ReportMessage, - Add-ReportWarning, - Add-ReportError, - New-ReportFile, - Add-ReportFile, - Add-ReportReplacement, - Get-ReportMessages, - Get-ReportWarnings, - Get-ReportErrors, - Get-ReportFiles, - Get-ReportReplacements, - Complete-Report, - Get-ReportDuration, - Get-ReportStatistics, - Export-ReportJson, - Export-ReportText \ No newline at end of file diff --git a/Tools/Migration/Modules/Scanner.psm1 b/Tools/Migration/Modules/Scanner.psm1 deleted file mode 100644 index b289dc9..0000000 --- a/Tools/Migration/Modules/Scanner.psm1 +++ /dev/null @@ -1,300 +0,0 @@ -<# -.SYNOPSIS - Scanner du framework de migration PimsOS. - -.DESCRIPTION - Ce module est responsable de la découverte des fichiers - utilisés par le framework de migration. - -.NOTES - - Projet : PimsOS - Module : Scanner - Version : 1.1.0 - -#> - -Set-StrictMode -Version Latest - -#============================================================================== -# Vérifie si un fichier est situé dans un dossier exclu -#============================================================================== - -function Test-IsExcluded { - - [CmdletBinding()] - [OutputType([bool])] - - param( - - [Parameter(Mandatory)] - [System.IO.FileInfo] - $File, - - [string[]] - $ExcludedFolders = (Get-ExcludedFolders) - - ) - - foreach ($Folder in $ExcludedFolders) - { - if ($File.FullName -match "\\$([Regex]::Escape($Folder))(\\|$)") - { - return $true - } - } - - return $false - -} - -#============================================================================== -# Retourne tous les fichiers PowerShell -#============================================================================== - -function Get-PowerShellFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [string] - $Root = (Get-ProjectRoot) - - ) - - Get-ProjectFiles ` - -Root $Root | - Where-Object { - - $_.Extension -in ".ps1", ".psm1", ".psd1" - - } - -} - -#============================================================================== -# Retourne les fichiers Markdown -#============================================================================== - -function Get-MarkdownFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [string] - $Root = (Get-ProjectRoot) - - ) - - if (-not (Test-MigrationPath $Root)) - { - return @() - } - - Get-ChildItem ` - -LiteralPath $Root ` - -Filter "*.md" ` - -File ` - -Recurse - -} - -#============================================================================== -# Retourne les fichiers JSON -#============================================================================== - -function Get-JsonFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [string] - $Root = (Get-ProjectRoot) - - ) - - if (-not (Test-MigrationPath $Root)) - { - return @() - } - - Get-ChildItem ` - -LiteralPath $Root ` - -Filter "*.json" ` - -File ` - -Recurse - -} - -#============================================================================== -# Retourne tous les fichiers du projet -#============================================================================== - -function Get-ProjectFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [string] - $Root = (Get-ProjectRoot), - - [string[]] - $Extensions = (Get-ProjectExtensions) - - ) - - if (-not (Test-MigrationPath $Root)) - { - throw "Le dossier '$Root' est introuvable." - } - - $Files = foreach ($Extension in $Extensions) - { - Get-ChildItem ` - -LiteralPath $Root ` - -Recurse ` - -Filter $Extension ` - -File - } - - $Files | - Sort-Object FullName -Unique - -} - -#============================================================================== -# Retourne les fichiers pouvant être migrés -#============================================================================== - -function Get-MigrationFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param( - - [string] - $Root = (Get-ProjectRoot) - - ) - - Get-ProjectFiles ` - -Root $Root | - Where-Object { - - -not (Test-IsExcluded $_) - - } | - Sort-Object FullName - -} - -#============================================================================== -# Retourne les règles de migration -#============================================================================== - -function Get-RuleFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param() - - $RulesFolder = Join-Path ` - (Get-MigrationRoot) ` - "Rules" - - if (-not (Test-MigrationPath $RulesFolder)) - { - return @() - } - - Get-ChildItem ` - -LiteralPath $RulesFolder ` - -Filter "*.ps1" ` - -File | - Sort-Object Name - -} - -#============================================================================== -# Retourne les modules du framework -#============================================================================== - -function Get-ModuleFiles { - - [CmdletBinding()] - [OutputType([System.IO.FileInfo[]])] - - param() - - $ModulesFolder = Join-Path ` - (Get-MigrationRoot) ` - "Modules" - - if (-not (Test-MigrationPath $ModulesFolder)) - { - return @() - } - - Get-ChildItem ` - -LiteralPath $ModulesFolder ` - -Filter "*.psm1" ` - -File | - Sort-Object Name - -} - -#============================================================================== -# Inventaire complet du projet -#============================================================================== - -function Get-ProjectInventory { - - [CmdletBinding()] - [OutputType([pscustomobject])] - - param( - - [string] - $Root = (Get-ProjectRoot) - - ) - - [PSCustomObject]@{ - - PowerShell = Get-PowerShellFiles -Root $Root - Markdown = Get-MarkdownFiles -Root $Root - Json = Get-JsonFiles -Root $Root - - Migration = Get-MigrationFiles -Root $Root - Rules = Get-RuleFiles - Modules = Get-ModuleFiles - - } - -} - -#============================================================================== -# Export -#============================================================================== - -Export-ModuleMember ` - -Function ` - Test-IsExcluded, - Get-PowerShellFiles, - Get-MarkdownFiles, - Get-JsonFiles, - Get-ProjectFiles, - Get-MigrationFiles, - Get-RuleFiles, - Get-ModuleFiles, - Get-ProjectInventory \ No newline at end of file diff --git a/Tools/Migration/Private/Get-MigrationRules.ps1 b/Tools/Migration/Private/Get-MigrationRules.ps1 deleted file mode 100644 index 92a6c80..0000000 --- a/Tools/Migration/Private/Get-MigrationRules.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -function Get-MigrationRules { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - $Context - - ) - - if (-not $script:MigrationRules) { - return @() - } - - return $script:MigrationRules | - Sort-Object Priority, Name - -} \ No newline at end of file diff --git a/Tools/Migration/Private/Import-MigrationRules.ps1 b/Tools/Migration/Private/Import-MigrationRules.ps1 deleted file mode 100644 index 8cca2ce..0000000 --- a/Tools/Migration/Private/Import-MigrationRules.ps1 +++ /dev/null @@ -1,82 +0,0 @@ -<# -.SYNOPSIS - Importe les règles de migration. - -.DESCRIPTION - Charge tous les fichiers PowerShell présents dans le - dossier Rules. - -.PARAMETER Context - Contexte de migration. - -.OUTPUTS - System.Boolean - -.NOTES - Les fichiers sont importés par dot-sourcing afin de - rendre leurs fonctions et variables disponibles. -#> - -function Import-MigrationRules { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - $Context - - ) - - $RulesPath = Join-Path $Context.MigrationPath "Rules" - - if (-not (Test-Path -LiteralPath $RulesPath -PathType Container)) { - - Write-Verbose "Dossier Rules introuvable : $RulesPath" - return $false - - } - - # Réinitialise la collection des règles chargées - $script:MigrationRules = @() - - $RuleFiles = Get-ChildItem ` - -Path $RulesPath ` - -Filter "*.ps1" ` - -File | - Sort-Object Name - - foreach ($File in $RuleFiles) { - - $Rule = . $File.FullName - - if ($null -eq $Rule) { - - Write-Warning "La règle '$($File.Name)' n'a retourné aucun objet de migration." - continue - - } - - # Vérifie qu'il s'agit bien d'une règle créée par New-MigrationRule - if ( - $Rule -isnot [pscustomobject] -or - -not ($Rule.PSObject.Properties.Match('Name').Count) -or - -not ($Rule.PSObject.Properties.Match('Description').Count) -or - -not ($Rule.PSObject.Properties.Match('Enabled').Count) -or - -not ($Rule.PSObject.Properties.Match('Priority').Count) -or - -not ($Rule.PSObject.Properties.Match('Script').Count) - ) { - - Write-Warning "Le fichier '$($File.Name)' n'a pas retourné une règle de migration valide." - continue - - } - - $script:MigrationRules += $Rule - - Write-Verbose "Règle importée : $($Rule.Name)" - - } - - return $true - -} \ No newline at end of file diff --git a/Tools/Migration/Private/Invoke-MigrationRules.ps1 b/Tools/Migration/Private/Invoke-MigrationRules.ps1 deleted file mode 100644 index 6c248cd..0000000 --- a/Tools/Migration/Private/Invoke-MigrationRules.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -function Invoke-MigrationRules { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - $Context - - ) - - $Rules = Get-MigrationRules -Context $Context - - foreach ($Rule in $Rules) { - - $Result = New-MigrationResult - - $Result.Rule = $Rule.Name - $Result.File = $Rule.FullName - - try { - - . $Rule.FullName - - $Result.Modified = $true - - } - catch { - - $Result.Modified = $false - $Result.Message = $_.Exception.Message - - } - - $Context.Results.Add($Result) - - } - - return $Context.Results - -} \ No newline at end of file diff --git a/Tools/Migration/Private/Test-MigrationProject.ps1 b/Tools/Migration/Private/Test-MigrationProject.ps1 deleted file mode 100644 index dbc81eb..0000000 --- a/Tools/Migration/Private/Test-MigrationProject.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# -.SYNOPSIS - Vérifie que le projet de migration est valide. - -.DESCRIPTION - Contrôle la présence des principaux dossiers utilisés - par le framework de migration. - -.PARAMETER Context - Contexte de migration. - -.OUTPUTS - System.Boolean - -.NOTES - Cette fonction est appelée au début de Invoke-Migration. -#> - -function Test-MigrationProject { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - [PSObject]$Context - - ) - - # Vérification du dossier racine - if (-not (Test-Path -LiteralPath $Context.ProjectRoot -PathType Container)) { - - Write-Verbose "ProjectRoot introuvable : $($Context.ProjectRoot)" - return $false - - } - - # Vérification du dossier Tools - if (-not (Test-Path -LiteralPath $Context.ToolsPath -PathType Container)) { - - Write-Verbose "ToolsPath introuvable : $($Context.ToolsPath)" - return $false - - } - - # Vérification du dossier Migration - if (-not (Test-Path -LiteralPath $Context.MigrationPath -PathType Container)) { - - Write-Verbose "MigrationPath introuvable : $($Context.MigrationPath)" - return $false - - } - - # Vérification du dossier Modules - if (-not (Test-Path -LiteralPath $Context.ModulesPath -PathType Container)) { - - Write-Verbose "ModulesPath introuvable : $($Context.ModulesPath)" - return $false - - } - - # Vérification du dossier Tests - if (-not (Test-Path -LiteralPath $Context.TestsPath -PathType Container)) { - - Write-Verbose "TestsPath introuvable : $($Context.TestsPath)" - return $false - - } - - return $true - -} \ No newline at end of file diff --git a/Tools/Migration/Private/Write-MigrationReport.ps1 b/Tools/Migration/Private/Write-MigrationReport.ps1 deleted file mode 100644 index 96f9bd9..0000000 --- a/Tools/Migration/Private/Write-MigrationReport.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -function Write-MigrationReport { - - [CmdletBinding()] - param( - - [Parameter(Mandatory)] - $Context - - ) - - Write-Host "" - Write-Host "==================================================" ` - -ForegroundColor Cyan - - Write-Host " Migration terminée" ` - -ForegroundColor Cyan - - Write-Host "==================================================" ` - -ForegroundColor Cyan - - Write-Host "" - - $TotalRules = $Context.Results.Count - - $Succeeded = @( - $Context.Results | - Where-Object Modified - ).Count - - $Failed = @( - $Context.Results | - Where-Object { -not $_.Modified } - ).Count - - Write-Host ("Projet : {0}" -f $Context.ProjectName) - Write-Host ("Règles exécutées : {0}" -f $TotalRules) - Write-Host ("Modifiées : {0}" -f $Succeeded) - Write-Host ("En erreur : {0}" -f $Failed) - - Write-Host "" - - foreach ($Result in $Context.Results) { - - if ($Result.Modified) { - - Write-Host ("[ OK ] {0}" -f $Result.Rule) ` - -ForegroundColor Green - - } - else { - - Write-Host ("[ KO ] {0}" -f $Result.Rule) ` - -ForegroundColor Red - - if (-not [string]::IsNullOrWhiteSpace($Result.Message)) { - - Write-Host (" {0}" -f $Result.Message) ` - -ForegroundColor DarkRed - - } - - } - - } - - Write-Host "" - -} \ No newline at end of file diff --git a/Tools/Migration/Public/Invoke-Migration.ps1 b/Tools/Migration/Public/Invoke-Migration.ps1 deleted file mode 100644 index 4d58ce7..0000000 --- a/Tools/Migration/Public/Invoke-Migration.ps1 +++ /dev/null @@ -1,92 +0,0 @@ -<# -.SYNOPSIS - Lance une migration PimsOS. - -.DESCRIPTION - Point d'entrée public du framework de migration. - -.PARAMETER Rule - Nom d'une règle à exécuter. - -.PARAMETER AnalyzeOnly - Analyse uniquement les fichiers sans les modifier. - -.PARAMETER ExecuteAll - Exécute toutes les règles disponibles. -#> - -function Invoke-Migration { - - [CmdletBinding(SupportsShouldProcess = $true)] - param( - - [string]$Rule, - - [switch]$AnalyzeOnly, - - [switch]$ExecuteAll - - ) - - Write-Verbose "Initialisation de la migration..." - - $Context = New-MigrationContext - - $Context.ProjectName = "PimsOS" - - $Context.ProjectRoot = Resolve-Path ( - Join-Path $PSScriptRoot "..\.." - ) - - $Context.ProjectRoot = $Context.ProjectRoot.Path - - $Context.ToolsPath = Join-Path ` - $Context.ProjectRoot ` - "Tools" - - $Context.MigrationPath = Join-Path ` - $Context.ToolsPath ` - "Migration" - - $Context.ModulesPath = Join-Path ` - $Context.MigrationPath ` - "Modules" - - $Context.TestsPath = Join-Path ` - $Context.MigrationPath ` - "Tests" - - $Context.Rule = $Rule - - $Context.AnalyzeOnly = $AnalyzeOnly.IsPresent - - $Context.ExecuteAll = $ExecuteAll.IsPresent - - Write-Verbose "Projet : $($Context.ProjectRoot)" - Write-Verbose "Migration : $($Context.MigrationPath)" - - if (-not (Test-MigrationProject -Context $Context)) { - throw "Le projet de migration n'est pas valide." - } - - if (-not (Import-MigrationRules -Context $Context)) { - throw "Impossible d'importer les règles." - } - - $Rules = Get-MigrationRules -Context $Context - - if ($Rules.Count -eq 0) { - Write-Warning "Aucune règle disponible." - return - } - - Write-Verbose ("{0} règle(s) chargée(s)." -f $Rules.Count) - - Invoke-MigrationRules ` - -Context $Context ` - -Rules $Rules - - Write-MigrationReport ` - -Context $Context - -} \ No newline at end of file diff --git a/Tools/Migration/Rules/Context.ps1 b/Tools/Migration/Rules/Context.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Migration/Rules/Logger.ps1 b/Tools/Migration/Rules/Logger.ps1 deleted file mode 100644 index 2a13078..0000000 --- a/Tools/Migration/Rules/Logger.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -<# -.SYNOPSIS - Règle de migration Logger. - -.DESCRIPTION - Convertit les appels Write-Log vers la nouvelle API Logger v2. -#> - -$Rule = New-MigrationRule - -$Rule.Name = "Logger" -$Rule.Description = "Migration de l'ancienne API Logger vers Logger v2." -$Rule.Priority = 100 -$Rule.Enabled = $true - -$Rule.Script = { - - param( - [Parameter(Mandatory)] - $Context - ) - - $Files = Get-MigrationFiles -Root $Context.ProjectRoot - - foreach ($File in $Files) { - - Write-Verbose "Analyse : $($File.FullName)" - - $Script = Get-ScriptAst -File $File - - if (Test-ParseErrors -Script $Script) { - continue - } - - $Commands = Find-LoggerCommands -Script $Script - - if ($Commands.Count -eq 0) { - continue - } - - $Replacements = New-ReplacementCollection - - foreach ($Command in $Commands) { - - $Replacement = New-LoggerReplacement -Command $Command - - if ($null -ne $Replacement) { - Add-Replacement ` - -Collection $Replacements ` - -Replacement $Replacement - } - - } - - if ($Replacements.Count -eq 0) { - continue - } - - Invoke-Replacements ` - -File $File ` - -Replacements $Replacements ` - -Context $Context - - } - -} - -return $Rule \ No newline at end of file diff --git a/Tools/Migration/Rules/Registry.ps1 b/Tools/Migration/Rules/Registry.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Migration/Rules/Workflow.ps1 b/Tools/Migration/Rules/Workflow.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Quality/Invoke-Formatting.ps1 b/Tools/Quality/Invoke-Formatting.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Quality/Invoke-Lint.ps1 b/Tools/Quality/Invoke-Lint.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Quality/Invoke-Pester.ps1 b/Tools/Quality/Invoke-Pester.ps1 deleted file mode 100644 index 4765dba..0000000 --- a/Tools/Quality/Invoke-Pester.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -# ========================================== -# PimsOS Builder -# Runner Pester -# ========================================== - -[CmdletBinding()] -param( - [ValidateSet("All", "Unit", "Integration", "Legacy")] - [string]$Scope = "All", - - [switch]$Detailed -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -$ProjectRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") - -$Paths = switch ($Scope) { - - "Unit" { - @( - Join-Path $ProjectRoot "Tests\Unit" - ) - } - - "Integration" { - @( - Join-Path $ProjectRoot "Tests\Integration" - ) - } - - "Legacy" { - @( - Join-Path $ProjectRoot "Tests\Legacy" - ) - } - - "All" { - @( - Join-Path $ProjectRoot "Tests\Unit" - Join-Path $ProjectRoot "Tests\Integration" - ) - } -} - -Write-Host "==========================================" -ForegroundColor Cyan -Write-Host " PimsOS - Pester Test Runner" -ForegroundColor Cyan -Write-Host "==========================================" -ForegroundColor Cyan -Write-Host "" -Write-Host "Scope : $Scope" -Write-Host "" - -$Config = New-PesterConfiguration - -$Config.Run.Path = $Paths -$Config.Run.Exit = $false -$Config.Run.PassThru = $true - -if ($Detailed) { - $Config.Output.Verbosity = "Detailed" -} -else { - $Config.Output.Verbosity = "Normal" -} - -Write-Host "Tests exécutés :" -ForegroundColor Yellow - -foreach ($Path in $Paths) { - Write-Host " $Path" -} - -Write-Host "" - -$Result = Invoke-Pester -Configuration $Config - -Write-Host "" -Write-Host "==========================================" -ForegroundColor Cyan -Write-Host " Résultat" -ForegroundColor Cyan -Write-Host "==========================================" -ForegroundColor Cyan - -Write-Host "Tests : $($Result.TotalCount)" -Write-Host "Réussis : $($Result.PassedCount)" -Write-Host "Échecs : $($Result.FailedCount)" -Write-Host "Ignorés : $($Result.SkippedCount)" -Write-Host "" - -if ($Result.FailedCount -gt 0) { - Write-Host "Pester : ÉCHEC" -ForegroundColor Red - exit 1 -} - -Write-Host "Pester : OK" -ForegroundColor Green -exit 0 \ No newline at end of file diff --git a/Tools/Release/Build-Release.ps1 b/Tools/Release/Build-Release.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Release/Publish-GitHub.ps1 b/Tools/Release/Publish-GitHub.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Release/Sign-Module.ps1 b/Tools/Release/Sign-Module.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Utilities/Backup-Project.ps1 b/Tools/Utilities/Backup-Project.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Utilities/Clean-Project.ps1 b/Tools/Utilities/Clean-Project.ps1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tools/Utilities/Update-Version.ps1 b/Tools/Utilities/Update-Version.ps1 deleted file mode 100644 index e69de29..0000000