Ton dossier Scripts déborde ? Dans ce tutoriel, tu construis ScriptPro, un gestionnaire de scripts IT en Java qui range, exécute et trace tes scripts PowerShell, CMD, Batch, Bash et Python. Tout le code est expliqué étape par étape, avec le pourquoi de chaque choix, et le projet complet est disponible sur GitHub.
Tu as un dossier Scripts qui déborde ? Un .ps1 sur le bureau, trois .bat sur une clé USB, un script Python qu’un collègue t’a envoyé par mail… et le jour où tu en as besoin, impossible de remettre la main dessus. Sans parler de savoir qui l’a lancé, quand, et ce qu’il a affiché.
Dans ce tutoriel, on construit ensemble ScriptPro, un gestionnaire de scripts de bureau qui range, exécute et trace tes scripts PowerShell, CMD, Batch, Bash et Python. On part de zéro et on arrive à une application complète : base de données, exécution en direct, contrôle de sécurité, historique, sauvegardes, thème sombre et animations.
Surtout, on ne se contente pas d’empiler du code : à chaque étape, je t’explique pourquoi on fait ce choix plutôt qu’un autre. C’est ça qui te servira sur tes propres projets.

Chaque section indique son niveau :
- 🟢 Débutant : les notions de base, expliquées depuis zéro.
- 🟡 Intermédiaire : l’organisation du code et les bonnes pratiques.
- 🔴 Confirmé : les détails techniques pointus (encodage, processus, concurrence).
Débutant ? Lis les 🟢 en entier et survole le reste : tu y reviendras plus tard. Confirmé ? Va directement aux 🔴, c’est là que se cachent les vrais pièges.
Ce que fait ce gestionnaire de scripts
🟢 DébutantScriptPro est un gestionnaire de scripts, un vrai coffre-fort pour tes scripts IT. Concrètement, il permet de :
| Besoin | Ce que fait ScriptPro |
|---|---|
| Ranger | Bibliothèque avec catégories, tags, favoris, archives et recherche instantanée |
| Écrire | Éditeur avec coloration syntaxique, numéros de ligne, rechercher/remplacer |
| Réutiliser | Variables {{IP}}, {{HOSTNAME}}… demandées au moment de l’exécution |
| Exécuter | Console intégrée, sortie en direct, bouton Arrêter, délai maximal |
| Sécuriser | Alerte avant toute commande dangereuse (rm -rf, format, shutdown…) |
| Tracer | Historique complet : sortie, erreurs, code retour, durée, utilisateur |
| Protéger | Variables sensibles masquées, sauvegarde et restauration de la base |
Ce gestionnaire de scripts tourne entièrement en local : pas de serveur, pas de compte, pas d’abonnement. Tes scripts restent sur ton poste.
Tu découvres Java en venant d’un autre langage ? Lis d’abord notre guide Java pour développeur PHP et notre tutoriel sur les collections Java (ArrayList, HashMap, HashSet) : tu seras plus à l’aise pour suivre la construction de ce gestionnaire de scripts.
Les technologies et pourquoi elles
🟢 DébutantVoici la pile technique du gestionnaire de scripts, avec pour chaque brique l’alternative qu’on a écartée et pourquoi.
| Couche | Technologie | Pourquoi ce choix | Alternative écartée |
|---|---|---|---|
| Langage | Java 21 | Version LTS (supportée longtemps), records, switch modernes, très stable | Java 8 : trop ancien, sans les syntaxes modernes |
| Interface | JavaFX 21 | Moderne, stylable en CSS, animations intégrées | Swing : vieillissant, stylage pénible |
| Éditeur de code | RichTextFX | Coloration syntaxique et numéros de ligne prêts à l’emploi | TextArea : une seule couleur pour tout le texte |
| Icônes | Ikonli + Font Awesome 5 | Icônes vectorielles, colorables en CSS | Images PNG : floues une fois agrandies, une image par couleur |
| Notifications | ControlsFX | Notifications « toast » élégantes | À recoder soi-même |
| Base de données | SQLite | Un simple fichier, aucune installation | MySQL : nécessite un serveur, trop lourd pour une appli de bureau |
| Build | Maven | Standard, déclaratif, très documenté | Gradle : plus souple mais plus complexe pour débuter |
| Tests | JUnit 5 | La référence en Java, tests paramétrés | — |
Pour lancer un script PowerShell sur ton poste, il faut… être sur ton poste. Une application web devrait passer par un agent local, gérer l’authentification, sécuriser l’accès distant. Ici, l’application est l’agent : plus simple et plus sûr.
Prérequis
🟢 Débutant| Outil | Version | À quoi il sert |
|---|---|---|
| Windows | 10 ou 11 | ScriptPro cible les administrateurs Windows |
| JDK | 21 ou plus | Compiler et exécuter le Java |
| Maven | 3.9 ou plus | Télécharger les librairies et lancer l’application |
| Un éditeur | IntelliJ IDEA, VS Code… | Lire et modifier le code |
| Facultatif | Git Bash, Python | Pour exécuter des scripts Bash et Python |
Tape java -version. Si tu vois 11 ou 17, Maven compilera avec la mauvaise version. Pointe JAVA_HOME vers ton JDK 21+ :
$env:JAVA_HOME = "C:\Program Files\Java\jdk-21"Installer et lancer en 5 minutes
🟢 Débutant1. Récupérer le projet
git clone https://github.com/LM-Code-Be/script-pro.git
cd script-proPowerShell2. Lancer l’application
mvn clean javafx:runPowerShellLa première fois, Maven télécharge toutes les librairies (une ou deux minutes). Ensuite, la fenêtre s’ouvre.
3. Premier démarrage
Au premier lancement, ScriptPro crée un fichier scriptpro.db vide à côté du projet : pas de script ni d’historique, seulement quelques catégories, des variables usuelles et les paramètres par défaut. Pour démarrer vite, ouvre l’écran Modèles et clique sur « Créer depuis ce modèle ».
4. Lancer les tests
mvn testPowerShellL’architecture de l’application
L’arborescence
🟢 DébutantScriptPro/
├── pom.xml ← configuration Maven (dépendances, plugins)
├── src/main/java/com/scriptpro/
│ ├── app/ ScriptProApplication.java ← point d'entrée, assemble tout
│ ├── models/ Script, ExecutionRecord… ← les données
│ ├── database/ DatabaseManager ← schéma, migrations, sauvegarde
│ ├── repositories/ ScriptRepository… ← lecture/écriture SQL
│ ├── services/ ScriptRunner, SecurityService… ← la logique métier
│ ├── controllers/ DashboardView, EditorView… ← un écran = une classe
│ ├── ui/ MainWindow, UiFactory, Animations ← composants visuels
│ └── utils/ ConsoleCharsets, DateFormatter… ← petits outils
├── src/main/resources/
│ ├── styles/app.css ← tout le design
│ └── images/scriptpro-icon.png
└── src/test/java/ ← tests JUnitTexteLes couches, expliquées simplement

L’application est découpée en couches. Chaque couche ne parle qu’à celle juste en dessous :
┌──────────────────────────────┐
│ controllers / ui (écrans) │ ← ce que l'utilisateur voit et clique
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ services (logique métier) │ ← « exécuter », « vérifier la sécurité »…
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ repositories (accès SQL) │ ← INSERT, SELECT, UPDATE
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ database (SQLite) │ ← le fichier scriptpro.db
└──────────────────────────────┘TextePourquoi s’embêter avec des couches ? Imagine qu’on écrive le SQL directement dans le bouton « Sauvegarder ». Ça marche… jusqu’au jour où trois boutons différents sauvegardent un script : tu as trois copies du même SQL à maintenir. Avec les couches :
- un bug SQL se corrige à un seul endroit (le dépôt) ;
- la logique d’exécution (
ScriptRunner) se teste sans ouvrir de fenêtre ; - on pourrait remplacer JavaFX par une interface web sans toucher aux services.
L’injection de dépendances « à la main »
🟡 IntermédiaireToutes les pièces sont créées et branchées au même endroit, dans ScriptProApplication.start() :
DatabaseManager databaseManager = new DatabaseManager();
databaseManager.initialize();
ScriptRepository scriptRepository = new ScriptRepository(databaseManager);
// … les autres dépôts
ScriptService scriptService = new ScriptService(scriptRepository, categoryRepository);
SecurityService securityService = new SecurityService(settingsService::customSecurityPatterns);
executionService = new ExecutionService(historyRepository, variableService, settingsService,
new ScriptRunner(interpreterLocator));Java📄 Fichier : src/main/java/com/scriptpro/app/ScriptProApplication.java
Chaque classe reçoit ce dont elle a besoin dans son constructeur, au lieu de le créer elle-même. C’est ce qu’on appelle l’injection de dépendances.
Spring fait exactement ça automatiquement… mais il ajoute des dizaines de Mo, rallonge le démarrage et cache le câblage derrière des annotations. Pour une quinzaine de classes, 30 lignes de new sont plus lisibles et plus rapides. Et pour les tests, c’est idéal : on passe une fausse dépendance au constructeur, et le tour est joué.
Pourquoi pas de FXML ?
🟡 IntermédiaireJavaFX permet de décrire les écrans en XML (FXML) avec l’outil Scene Builder. Ici, on construit les écrans en Java. Raisons :
- les écrans sont très dynamiques (cartes générées depuis la base, badges selon le statut) : en FXML il faudrait de toute façon beaucoup de code Java ;
- tout est au même endroit, et le compilateur vérifie les noms (une faute de frappe dans un
fx:idne se voit qu’à l’exécution) ; - le style reste séparé, dans le CSS, ce qui est le vrai intérêt de la séparation.
Étape 1 : le projet Maven
🟢 DébutantMaven, c’est le « gestionnaire de projet » : tu déclares les librairies dont tu as besoin dans pom.xml, il les télécharge et sait compiler, tester et lancer l’application.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<javafx.version>21.0.6</javafx.version>
<richtextfx.version>0.11.4</richtextfx.version>
<sqlite.version>3.46.1.3</sqlite.version>
<junit.version>5.11.4</junit.version>
</properties>XML📄 Fichier : pom.xml
sourceEncodingUTF-8 : sans ça, sur un Windows français, les accents des commentaires et des textes de l’interface peuvent être compilés de travers.maven.compiler.release21 : on compile pour Java 21, peu importe le JDK installé (tant qu’il est ≥ 21).- Les versions en propriétés : un seul endroit à modifier pour mettre à jour une librairie.
Le plugin JavaFX permet de lancer l’application avec mvn javafx:run :
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.scriptpro.app.ScriptProApplication</mainClass>
<options>
<option>--enable-native-access=javafx.graphics,ALL-UNNAMED</option>
</options>
</configuration>
</plugin>XMLC’est pour ça qu’on l’ajoute comme une dépendance Maven ordinaire : chaque projet choisit sa version de JavaFX.
Étape 2 : la base de données SQLite
C’est quoi, SQLite ?
🟢 DébutantSQLite est une base de données SQL complète… stockée dans un seul fichier. Pas de serveur à installer ni de mot de passe à configurer : le pilote sqlite-jdbc contient tout le moteur. C’est la base la plus utilisée au monde (ton téléphone en contient des dizaines).
Le schéma
🟢 Débutant| Table | Contenu |
|---|---|
scripts | Nom, langage, catégorie, tags, description, contenu, auteur, dates, favori, archivé |
execution_history | Chaque exécution : script, statut, date, durée, code retour, sortie, erreurs, utilisateur |
variables | Variables {{NOM}} : valeur par défaut, description, intégrée ou non, sensible ou non |
categories | Catégories et leur couleur |
favorites | Scripts favoris |
settings | Paramètres clé/valeur (thème, délai, dossier de travail…) |
Le gestionnaire de base
🟡 IntermédiaireToute la base passe par DatabaseManager. Point important : on n’ouvre pas une connexion pour toute la durée de l’application, mais une connexion courte par opération :
public Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath);
try (Statement statement = connection.createStatement()) {
statement.execute("PRAGMA foreign_keys = ON");
}
return connection;
}Java📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java
Pourquoi des connexions courtes ? Avec SQLite, ouvrir une connexion coûte presque rien (c’est un fichier local). En échange, aucune connexion ne reste ouverte « par erreur », et on peut remplacer la base à chaud lors d’une restauration (étape 12). Chaque dépôt utilise le motif try-with-resources, qui ferme automatiquement la connexion, même en cas d’erreur :
try (Connection connection = databaseManager.getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE scripts SET archived = ?, updated_at = ? WHERE id = ?")) {
statement.setInt(1, archived ? 1 : 0);
statement.setString(2, Instant.now().toString());
statement.setLong(3, id);
statement.executeUpdate();
}Java📄 Fichier : src/main/java/com/scriptpro/repositories/ScriptRepository.java
PreparedStatement avec des ?N’écris jamais "... WHERE name = '" + nom + "'". Si le nom contient une apostrophe (L'inventaire), la requête plante ; et si quelqu’un écrit '; DROP TABLE scripts; --, c’est ta base qui part. C’est l’injection SQL. Avec ?, le pilote traite la valeur comme une donnée, jamais comme du code.
2026-10-25T13:05:00Z) ?SQLite n’a pas de vrai type « date ». Le format ISO 8601 en UTC (Instant.toString()) a trois avantages : il est lisible, il se trie correctement par ordre alphabétique, et il ne dépend pas du fuseau horaire du poste. La conversion vers l’heure locale ne se fait qu’à l’affichage.
Les migrations : faire évoluer la base sans perdre de données
🟡 IntermédiaireQuand on a ajouté les tags et les variables sensibles, des utilisateurs avaient déjà une base sans ces colonnes. Leur demander de tout supprimer ? Hors de question. À chaque démarrage, ScriptPro met donc le schéma à niveau :
private void migrate(Connection connection) throws SQLException {
addColumnIfMissing(connection, "scripts", "tags", "TEXT NOT NULL DEFAULT ''");
addColumnIfMissing(connection, "variables", "secret", "INTEGER NOT NULL DEFAULT 0");
}
private void addColumnIfMissing(Connection connection, String table, String column, String definition) throws SQLException {
try (Statement statement = connection.createStatement();
ResultSet columns = statement.executeQuery("PRAGMA table_info(" + table + ")")) {
while (columns.next()) {
if (column.equalsIgnoreCase(columns.getString("name"))) {
return; // la colonne existe déjà : rien à faire
}
}
}
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
}
}Java📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java
Le mot clé, c’est idempotent : on peut exécuter la migration 1 fois ou 100 fois, le résultat est le même. PRAGMA table_info liste les colonnes existantes ; on n’ajoute que ce qui manque. Le DEFAULT remplit automatiquement les anciennes lignes.
sur un gros projet, on utiliserait un outil comme Flyway ou Liquibase, avec des fichiers de migration numérotés (V1__init.sql, V2__tags.sql…). Pour deux colonnes, une méthode maison de 15 lignes suffit largement.
Étape 3 : les modèles et les dépôts
Les modèles
🟢 DébutantUn modèle représente une donnée de l’application. Script contient le nom, le langage, le contenu, etc. Le langage est une énumération, pas une simple chaîne :
public enum ScriptLanguage {
POWERSHELL("PowerShell", ".ps1"),
CMD("CMD", ".cmd"),
BATCH("Batch", ".bat"),
BASH("Bash", ".sh"),
PYTHON("Python", ".py");
// …
}Java📄 Fichier : src/main/java/com/scriptpro/models/ScriptLanguage.java
Pourquoi une enum ? Avec une chaîne, rien n’empêche d’écrire "Powershel". Avec une énumération, le compilateur refuse toute valeur inconnue, et chaque switch sur le langage doit traiter tous les cas : si on ajoute un langage demain, le compilateur signale chaque endroit à compléter.
Les records, pour les petites données
🟡 IntermédiairePour les données simples qui ne changent pas, Java 16+ offre les records :
public record SecurityWarning(String rule, int lineNumber, String excerpt) {
}JavaEn une ligne, tu obtiens le constructeur, les accesseurs (rule(), lineNumber()…), equals, hashCode et toString. Un record est immuable : une fois créé, impossible de le modifier par erreur.
Script n’est pas un record ?Parce qu’un script est modifié dans l’éditeur (nom, contenu, tags…) et que son id n’est connu qu’après l’insertion en base. Une classe avec des setters est plus naturelle ici.
Les dépôts (Repository pattern)
🟡 IntermédiaireUn dépôt cache le SQL derrière des méthodes parlantes : findAll(), findById(), save(), delete(). La méthode save choisit seule entre INSERT et UPDATE :
public Script save(Script script) {
Instant now = Instant.now();
if (script.getCreatedAt() == null) {
script.setCreatedAt(now);
}
script.setUpdatedAt(now);
if (script.getId() == null) {
return insert(script); // nouveau script : pas encore d'identifiant
}
return update(script);
}Java📄 Fichier : src/main/java/com/scriptpro/repositories/ScriptRepository.java
Le reste du code n’a jamais à se demander « est-ce que ce script existe déjà ? » : il appelle save.
Étape 4 : exécuter un script depuis Java
C’est le cœur du gestionnaire de scripts. Tout se passe dans ScriptRunner, une classe volontairement indépendante de JavaFX pour pouvoir la tester seule.
Le principe
🟢 DébutantPour exécuter un script, on :
- écrit son contenu dans un fichier temporaire (
scriptpro-123.ps1) ; - lance l’interpréteur (
powershell.exe,cmd.exe,bash.exe…) sur ce fichier ; - lit ce qu’il affiche, ligne par ligne, en direct ;
- attend la fin (ou le délai maximal), récupère le code retour, supprime le fichier.
ProcessBuilder plutôt que Runtime.exec
🟡 IntermédiairescriptFile = Files.createTempFile("scriptpro-", language.getExtension());
Files.write(scriptFile, ScriptFiles.encode(language, content));
ProcessBuilder builder = new ProcessBuilder(ScriptFiles.command(language, interpreter.get(), scriptFile));
builder.directory(workingDirectory.toFile());
ScriptFiles.configureEnvironment(language, builder.environment());
Process started = builder.start();
process = started;
// Aucun clavier n'est relié au script : un Read-Host ou input() reçoit « fin de fichier »
// au lieu de bloquer jusqu'au timeout.
started.getOutputStream().close();Java📄 Fichier : src/main/java/com/scriptpro/services/ScriptRunner.java
ProcessBuilderet nonRuntime.exec("..."): on passe la commande sous forme de liste d’arguments. Avec une seule chaîne, un chemin contenant des espaces (C:\Program Files\...) est mal découpé.ProcessBuilderpermet aussi de choisir le dossier de travail et les variables d’environnement.- Pourquoi un fichier temporaire et pas le script en argument (
powershell -Command "...") ? Les guillemets, les retours à la ligne et la longueur maximale d’une ligne de commande Windows (environ 8 000 caractères) rendraient tout fragile. - Fermer l’entrée standard : si un script attend une saisie (
Read-Host,input(),pause), personne ne pourra jamais répondre. En fermant l’entrée, le script reçoit « fin de fichier » immédiatement au lieu de rester bloqué.
Une commande différente par langage
🔴 ConfirméChaque interpréteur a ses manies. On les regroupe dans une petite classe ScriptFiles :
static List<String> command(ScriptLanguage language, InterpreterLocator.Interpreter interpreter, Path file) {
List<String> command = new ArrayList<>();
command.add(interpreter.executable().toString());
command.addAll(interpreter.extraArgs());
String path = file.toAbsolutePath().toString();
switch (language) {
case POWERSHELL -> {
command.addAll(List.of("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"));
command.add("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
+ "$OutputEncoding = [System.Text.Encoding]::UTF8; "
+ "& '" + path.replace("'", "''") + "'; exit $LASTEXITCODE");
}
// /d : ignore les scripts AutoRun du registre, qui pourraient polluer la sortie.
case CMD, BATCH -> command.addAll(List.of("/d", "/c", path));
case BASH -> command.add(interpreter.wsl() ? toWslPath(file) : path.replace('\\', '/'));
case PYTHON -> command.add(path);
}
return command;
}Java| Option | Pourquoi |
|---|---|
-NoProfile | Ne charge pas le profil PowerShell de l’utilisateur : démarrage plus rapide, résultat identique sur tous les postes |
-NonInteractive | Interdit les invites interactives |
-ExecutionPolicy Bypass | Sinon, la stratégie par défaut de Windows refuse d’exécuter un .ps1 |
exit $LASTEXITCODE | Remonte le vrai code retour du script (un exit 3 donne bien 3) |
cmd /d | Ignore les commandes « AutoRun » du registre |
/mnt/c/... | Le bash de WSL ne comprend pas C:\ : on convertit le chemin |
Lire la sortie sans bloquer
🔴 ConfirméUn processus a deux flux de sortie : la sortie standard (stdout) et les erreurs (stderr). Piège classique : si on lit d’abord stdout jusqu’au bout, puis stderr, et que le script écrit beaucoup d’erreurs, le tampon de stderr se remplit, le script se bloque en attendant qu’on le vide… et nous, on attend la fin de stdout. Interblocage.
La solution : un thread de lecture par flux.
OutputBuffer stdout = new OutputBuffer();
OutputBuffer stderr = new OutputBuffer();
Thread outPump = startPump(started.getInputStream(), stdout, false, fallback);
Thread errPump = startPump(started.getErrorStream(), stderr, true, fallback);
boolean finished = started.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);JavaChaque « pompe » lit les octets, les découpe en lignes et transmet chaque ligne à un listener : c’est ce qui permet l’affichage en direct. La sortie conservée est plafonnée à 2 millions de caractères, pour qu’un script qui boucle à l’infini ne remplisse pas la mémoire ni la base.
Arrêter un script… et ses enfants
🔴 ConfirméSi ton script lance ping -t, puis que tu cliques sur Arrêter, tuer cmd.exe ne suffit pas : sous Windows, ping continue de tourner, orphelin. On tue donc tout l’arbre de processus :
static void killTree(Process process) {
List<ProcessHandle> descendants = new ArrayList<>();
try {
process.descendants().forEach(descendants::add);
} catch (RuntimeException ignored) {
// Énumération impossible (droits) : on tue au moins le processus principal.
}
process.destroyForcibly();
descendants.forEach(ProcessHandle::destroyForcibly);
}JavaOn liste les descendants avant de tuer le parent : une fois le parent mort, le lien de parenté est perdu.
le résultat d’une exécution est un record Result(outcome, exitCode, stdout, stderr, message) avec un outcome parmi COMPLETED, TIMEOUT, CANCELLED, NOT_STARTED. Plutôt que de lancer des exceptions, on renvoie un résultat qui décrit tous les cas, et l’appelant fait un switch dessus.
Étape 5 : le casse-tête des accents
C’est quoi, une page de code ?
🟢 DébutantUn ordinateur ne stocke que des nombres. Une page de code (ou encodage) est la table qui dit « le nombre 233 = é ». Le problème : il en existe plusieurs.
| Encodage | Où on le trouve | é s’écrit |
|---|---|---|
| UTF-8 | Le standard moderne (web, Java, Linux) | 2 octets : C3 A9 |
| Windows-1252 | Les fichiers texte « ANSI » de Windows | E9 |
| OEM 850 | La console Windows en français (cmd) | 82 |
Si un programme écrit en 850 et qu’on lit en UTF-8, Opération réussie devient Op�ration r�ussie. C’était le bug de la première version de ScriptPro.
La solution en deux temps
🔴 Confirmé1. Demander l’UTF-8 à chaque interpréteur, chacun à sa façon :
static byte[] encode(ScriptLanguage language, String content) {
return switch (language) {
// Windows PowerShell 5.1 lit un .ps1 sans BOM en ANSI : le BOM garantit l'UTF-8.
case POWERSHELL -> concat(UTF8_BOM, content.getBytes(StandardCharsets.UTF_8));
// cmd.exe lit le fichier ligne par ligne dans la page de code courante : on la passe
// en UTF-8 dès la première ligne. Les fins de ligne CRLF évitent les bugs de GOTO/labels.
case CMD, BATCH -> ("@chcp 65001 >nul\r\n" + toCrLf(content)).getBytes(StandardCharsets.UTF_8);
// Bash refuse les retours chariot Windows (« $'\r': command not found »).
case BASH -> content.replace("\r\n", "\n").getBytes(StandardCharsets.UTF_8);
case PYTHON -> content.getBytes(StandardCharsets.UTF_8);
};
}Java📄 Fichier : src/main/java/com/scriptpro/services/ScriptRunner.java
Et pour Python, des variables d’environnement : PYTHONIOENCODING=utf-8, PYTHONUTF8=1, et PYTHONUNBUFFERED=1 (sans ce dernier, Python garde sa sortie en mémoire et rien ne s’affiche en direct).
2. Décoder intelligemment, ligne par ligne. Même avec tout ça, certains vieux programmes (ping, ipconfig) écrivent toujours en OEM 850. On tente donc l’UTF-8 strict, et en cas d’échec on bascule sur la page OEM du poste :
public static String decode(byte[] bytes, int length, Charset fallback) {
try {
return StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(bytes, 0, length))
.toString();
} catch (CharacterCodingException notUtf8) {
return new String(bytes, 0, length, fallback);
}
}Java📄 Fichier : src/main/java/com/scriptpro/utils/ConsoleCharsets.java
Pourquoi ça marche ? L’UTF-8 a une structure très reconnaissable : un octet 82 isolé (le é en OEM 850) est invalide en UTF-8. Le décodeur strict (REPORT) le détecte et lève une exception, et on sait alors qu’il faut utiliser l’autre encodage. Comme on décide ligne par ligne, une sortie qui mélange les deux reste lisible.
La page OEM du poste (850 en France, 437 aux États-Unis…) est lue une seule fois dans le registre Windows (HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage, valeur OEMCP), puis gardée en cache.
Étape 6 : trouver les interpréteurs installés
🟡 IntermédiaireAvant d’exécuter un script Python, encore faut-il savoir où est Python. InterpreterLocator fait la même recherche que le terminal Windows : il parcourt les dossiers du PATH en essayant les extensions de PATHEXT (.exe, .cmd…).
Quelques choix :
- Bash : Git Bash avant WSL. Git Bash comprend les chemins Windows ; WSL demande de les convertir. On cherche donc d’abord Git Bash à ses emplacements habituels.
- Python : le lanceur
py -3en priorité. Installé avec Python sur Windows, il choisit la dernière version 3, même si plusieurs sont présentes. - Un cache : la recherche n’est faite qu’une fois par langage. Le bouton « Actualiser la détection » des Paramètres le vide (utile après avoir installé Python).
public synchronized Optional<Interpreter> find(ScriptLanguage language) {
return cache.computeIfAbsent(language, this::detect);
}Java📄 Fichier : src/main/java/com/scriptpro/services/InterpreterLocator.java
Optional ?La méthode peut ne rien trouver. Plutôt que de renvoyer null, qui finit tôt ou tard en NullPointerException, Optional oblige l’appelant à traiter le cas « absent ». L’éditeur affiche alors un message clair (« Python est introuvable. Installez-le depuis python.org en cochant Add python.exe to PATH ») avant de lancer quoi que ce soit.
Étape 7 : variables dynamiques et secrets
Les variables {{NOM}}
🟢 Débutant
Un même script de diagnostic peut viser n’importe quelle machine : on écrit ping {{IP}}, et ScriptPro demande la valeur de IP avant chaque exécution. La détection se fait avec une expression régulière :
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{([A-Za-z0-9_-]+)}}");
public String applyVariables(String content, Map<String, String> values) {
Matcher matcher = VARIABLE_PATTERN.matcher(content == null ? "" : content);
StringBuilder buffer = new StringBuilder();
while (matcher.find()) {
String value = values.get(matcher.group(1).toUpperCase(Locale.ROOT));
matcher.appendReplacement(buffer, Matcher.quoteReplacement(value == null ? matcher.group(0) : value));
}
matcher.appendTail(buffer);
return buffer.toString();
}Java📄 Fichier : src/main/java/com/scriptpro/services/VariableService.java
quoteReplacement.Dans appendReplacement, les caractères $ et \ ont un sens spécial ($1 = « groupe 1 »). Une valeur comme C:\$Temp ferait planter le remplacement ou donnerait n’importe quoi. Matcher.quoteReplacement les neutralise. Un test vérifie exactement ce cas.
Les variables inconnues sont laissées telles quelles ({{INCONNUE}}) plutôt que remplacées par du vide : le problème se voit tout de suite au lieu de produire une commande silencieusement fausse.
Les variables sensibles
🟡 IntermédiaireUn mot de passe saisi dans {{MOT_DE_PASSE_ADMIN}} ne doit jamais apparaître en clair à l’écran ni dans l’historique. Trois protections :
- la saisie se fait dans un
PasswordField(des points à la place des caractères) ; - la sortie en direct passe par un filtre de masquage avant d’être affichée ;
- la sortie enregistrée en base est masquée aussi.
public static String mask(String text, Collection<String> secrets) {
if (text == null || text.isEmpty() || secrets.isEmpty()) {
return text;
}
String masked = text;
List<String> ordered = secrets.stream()
.filter(secret -> secret != null && !secret.isEmpty())
.sorted(Comparator.comparingInt(String::length).reversed())
.toList();
for (String secret : ordered) {
masked = masked.replace(secret, VariableEntry.MASK);
}
return masked;
}JavaPourquoi trier du plus long au plus court ? Avec les secrets abc et abc123, remplacer abc en premier transformerait abc123 en ••••••123 : il resterait 123 en clair. En commençant par le plus long, tout est masqué.
la valeur par défaut d’une variable est stockée en clair dans la base. Pour un vrai mot de passe, laisse-la vide : il sera demandé à chaque exécution. Un vrai coffre-fort utiliserait le gestionnaire d’identifiants Windows (DPAPI) ; c’est une piste d’amélioration.
Étape 8 : le contrôle de sécurité
🟡 IntermédiaireAvant chaque exécution, le gestionnaire de scripts analyse le script et demande confirmation s’il trouve une commande dangereuse. La première version cherchait du texte simple (contains("rm -rf")). Problème : rm -fr, rm -r -f ou Remove-Item -Force -Recurse passaient entre les mailles. On utilise donc des expressions régulières, une par règle :
private static final List<Rule> BUILT_IN_RULES = List.of(
Rule.of("Suppression récursive de fichiers (rm)",
"\\brm\\s+(?:\\S+\\s+)*?(?:-[a-z]*r[a-z]*|--recursive)\\b"),
Rule.of("Suppression de dossier avec sous-dossiers (rd /s)",
"\\b(?:rd|rmdir)\\b[^\\r\\n]*\\s/s\\b"),
Rule.of("Formatage de volume",
"\\bformat(?:\\.com)?\\s+[a-z]:|\\bFormat-Volume\\b"),
Rule.of("Désactivation de l'antivirus Defender",
"\\bSet-MpPreference\\b[^\\r\\n]*-Disable\\w*\\s+(?:\\$true|1)\\b"),
Rule.of("Téléchargement exécuté directement (curl | sh)",
"\\b(?:curl|wget)\\b[^\\r\\n|]*\\|\\s*(?:sudo\\s+)?(?:ba|z)?sh\\b")
// … 19 règles au total
);Java📄 Fichier : src/main/java/com/scriptpro/services/SecurityService.java
Décortiquons la première pour les débutants :
| Morceau | Signification |
|---|---|
\b | Début de mot : rm mais pas farm |
rm\s+ | rm suivi d’au moins un espace |
(?:\S+\s+)*? | Éventuellement d’autres arguments avant |
-[a-z]*r[a-z]* | Une option contenant un r : -r, -rf, -fr, -Rf… |
|--recursive | … ou la forme longue |
Les règles sont insensibles à la casse et appliquées ligne par ligne, ce qui permet d’indiquer à l’utilisateur la ligne exacte et la règle déclenchée. Les lignes de commentaire (#, REM, ::) sont ignorées : elles ne s’exécutent pas.
Un script malveillant peut toujours masquer une commande (variable, base64…). Ce contrôle protège surtout contre l’erreur humaine : lancer le mauvais script, ou un script récupéré sans l’avoir relu. Les Paramètres permettent d’ajouter tes propres motifs ; chacun est validé à l’enregistrement pour qu’une regex invalide ne casse pas l’analyse.
Étape 9 : threads et interface réactive
La règle d’or de JavaFX
🟢 DébutantJavaFX dessine l’interface sur un seul thread, le JavaFX Application Thread. Deux règles en découlent :
- Ne jamais bloquer ce thread. Si on y lance un script de 3 minutes, la fenêtre gèle 3 minutes (« Ne répond pas »).
- Ne modifier l’interface que depuis ce thread. Sinon, plantages aléatoires.
Exécuter en arrière-plan avec Task
🟡 IntermédiaireExecutionService lance chaque exécution dans une Task JavaFX, sur un pool de threads :
Task<ExecutionRecord> task = new Task<>() {
@Override
protected ExecutionRecord call() { // ← tourne en arrière-plan
Instant startedAt = Instant.now();
ScriptRunner.Result result = execution.run();
return historyRepository.save(toRecord(script, startedAt, result, secrets));
}
};
executorService.submit(task);Java📄 Fichier : src/main/java/com/scriptpro/services/ExecutionService.java
Côté éditeur, on réagit à la fin de la tâche avec setOnSucceeded, que JavaFX appelle automatiquement sur le bon thread :
task.setOnSucceeded(event -> {
ExecutionRecord record = task.getValue();
finishExecution();
appendToConsole("\n────────────────────────────\n"
+ record.getStatus().getDisplayName()
+ " · Code retour : " + record.getExitCode()
+ " · Durée : " + DateFormatter.duration(record.getDurationMillis()) + "\n");
});JavaLe pool crée ses threads avec setDaemon(true). Un thread démon n’empêche pas la JVM de s’arrêter : si l’utilisateur ferme l’application, elle se ferme vraiment, au lieu de rester en mémoire à attendre un script.
Afficher la sortie en direct sans saturer l’interface
🔴 ConfirméLes lignes arrivent depuis les threads de lecture. Pour les afficher, il faut passer par Platform.runLater(...). Mais si un script affiche 10 000 lignes par seconde, 10 000 runLater noient le thread JavaFX et l’interface rame. On regroupe donc les lignes :
private final class LiveConsole {
private final StringBuilder pending = new StringBuilder();
private boolean flushScheduled;
void append(String text) {
synchronized (pending) {
pending.append(text);
if (flushScheduled) {
return; // un affichage est déjà prévu : il prendra ce texte aussi
}
flushScheduled = true;
}
Platform.runLater(this::flush);
}
void flush() {
String text;
synchronized (pending) {
text = pending.toString();
pending.setLength(0);
flushScheduled = false;
}
if (!text.isEmpty()) {
appendToConsole(text);
}
}
}Java📄 Fichier : src/main/java/com/scriptpro/controllers/EditorView.java
Tant qu’un affichage est en attente, les nouvelles lignes s’accumulent dans le tampon. Résultat : au plus un runLater en attente à la fois, quel que soit le débit du script. Le synchronized protège le tampon, partagé entre le thread de lecture et le thread JavaFX.
Étape 10 : l’interface JavaFX
La fenêtre principale
🟢 DébutantMainWindow construit la structure fixe avec un BorderPane, qui découpe la fenêtre en cinq zones :
┌──────────────────────────────────────────────┐
│ TOP : titre de la page, recherche, thème │
├──────────┬───────────────────────────────────┤
│ LEFT │ CENTER │
│ menu │ l'écran affiché │
│ │ │
├──────────┴───────────────────────────────────┤
│ BOTTOM : copyright, état de la base │
└──────────────────────────────────────────────┘TexteSeule la zone centrale change quand on navigue.
Un écran = une classe
🟡 IntermédiaireLa première version avait un MainController de 1 200 lignes qui contenait tous les écrans. Illisible, et chaque modification risquait de casser un autre écran. On l’a découpé : DashboardView, LibraryView, EditorView… Chaque écran reçoit un ViewContext qui lui donne accès aux services et aux utilitaires communs (confirmation, erreurs, notifications) :
void setView(String navigationKey, String title, String subtitle, Node content) {
window.setActiveNavigation(navigationKey);
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.getStyleClass().add("content-scroll");
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
window.setContent(title, subtitle, scrollPane);
updateQuickStats();
}Java📄 Fichier : src/main/java/com/scriptpro/controllers/ViewContext.java
Et pour passer d’un écran à l’autre (ex. « Ouvrir » depuis le tableau de bord), les écrans passent par une petite interface Navigator au lieu de se connaître directement. Ça évite les dépendances circulaires : l’éditeur n’a pas besoin de connaître la bibliothèque, et inversement.
Une fabrique de composants
🟢 DébutantPour que tous les boutons, badges et cartes se ressemblent, on ne les crée jamais « à la main » : on passe par UiFactory.
public static Button button(String text, String iconLiteral, String styleClass) {
Button button = new Button(text);
if (iconLiteral != null && !iconLiteral.isBlank()) {
button.setGraphic(icon(iconLiteral));
}
button.getStyleClass().add(styleClass);
Animations.hoverScale(button, "nav-button".equals(styleClass) ? 1.02 : 1.04);
return button;
}Java📄 Fichier : src/main/java/com/scriptpro/ui/UiFactory.java
L’intérêt : le jour où on a voulu ajouter une animation au survol de tous les boutons, il a suffi d’une ligne ici.
Des badges colorés dans les tableaux
🟡 IntermédiairePar défaut, une cellule de TableView affiche du texte. Pour afficher un badge (Succès en vert, Échec en rouge), on fournit une cell factory qui place un composant graphique dans la cellule :
public static <T> TableColumn<T, String> badgeColumn(String title, Function<T, String> mapper,
Function<T, String> colorClass) {
TableColumn<T, String> column = column(title, mapper);
column.setCellFactory(ignored -> new TableCell<>() {
@Override
protected void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
T row = empty || getTableRow() == null ? null : getTableRow().getItem();
setText(null);
setGraphic(row == null ? null : badge(value, colorClass.apply(row)));
}
});
return column;
}Javaempty ?Les TableView recyclent leurs cellules : en faisant défiler, une même cellule affiche successivement plusieurs lignes, puis parfois aucune. Si on oublie de vider la cellule quand empty est vrai, on voit d’anciens badges réapparaître sur des lignes vides.
Étape 11 : le design, les couleurs et les animations
Tout le style dans le CSS
🟢 DébutantJavaFX comprend une variante du CSS (propriétés préfixées par -fx-). Tout le design de ScriptPro est dans src/main/resources/styles/app.css : le code Java se contente de poser des classes ("kpi-card", "badge-green"…).
Pourquoi ? Changer une couleur ne demande pas de recompiler, et un graphiste peut modifier le design sans toucher au Java.
Des couleurs nommées et le thème sombre
🟡 IntermédiaireLe CSS de JavaFX permet de définir des couleurs nommées sur la racine et de les réutiliser partout :
.app-root {
-text-strong: #0f172a;
-text-muted: #64748b;
-surface: #ffffff;
-surface-border: #e4e9f5;
}
.app-root.dark {
-text-strong: #f8fafc;
-text-muted: #94a3b8;
-surface: #121a2c;
-surface-border: #26324a;
}
.card {
-fx-background-color: -surface;
-fx-border-color: -surface-border;
}CSS📄 Fichier : src/main/resources/styles/app.css
Le thème sombre consiste juste à ajouter la classe dark sur la racine : toutes les cartes changent de couleur d’un coup, sans dupliquer chaque règle.
Une couleur par sens
🟢 DébutantLes couleurs ne sont pas décoratives, elles portent du sens, et toujours le même partout :
| Élément | Couleur |
|---|---|
| Succès / Échec / Avertissement / Arrêté | Vert / Rouge / Orange / Gris |
| PowerShell / CMD / Batch / Bash / Python | Bleu / Ardoise / Orange / Vert / Jaune |
| Chaque section du menu | Une couleur d’icône propre (on repère l’écran d’un coup d’œil) |
| Actions principales | Dégradé indigo → violet |
Un utilisateur qui voit un badge rouge dans l’historique comprend avant même de lire.
Les animations
🟡 IntermédiaireToutes les animations sont regroupées dans la classe Animations. Exemple, l’entrée d’un élément (fondu + léger glissement vers le haut) :
/** Courbe « ease-out » : démarre vite et ralentit, ce qui paraît plus naturel qu'un mouvement linéaire. */
private static final Interpolator EASE_OUT = Interpolator.SPLINE(0.16, 1, 0.3, 1);
public static void enter(Node node, Duration delay) {
node.setOpacity(0);
node.setTranslateY(18);
FadeTransition fade = new FadeTransition(Duration.millis(420), node);
fade.setToValue(1);
TranslateTransition slide = new TranslateTransition(Duration.millis(520), node);
slide.setToY(0);
slide.setInterpolator(EASE_OUT);
ParallelTransition transition = new ParallelTransition(fade, slide);
transition.setDelay(delay);
transition.play();
}
public static void stagger(List<? extends Node> nodes) {
for (int index = 0; index < nodes.size(); index++) {
// Au-delà de 8 éléments, on plafonne le délai pour ne pas faire attendre l'utilisateur.
enter(nodes.get(index), Duration.millis(60L * Math.min(index, 8)));
}
}Java📄 Fichier : src/main/java/com/scriptpro/ui/Animations.java
| Animation | Où | Pourquoi |
|---|---|---|
| Entrée en cascade | Chaque changement de page | L’œil suit l’ordre de lecture, la page « se construit » |
| Compteur qui défile | Indicateurs du tableau de bord | Attire l’attention sur les chiffres clés |
| Barres qui se remplissent | Scripts par langage | Rend la comparaison visuelle immédiate |
| Zoom au survol / à l’appui | Tous les boutons, cartes KPI | Retour visuel : « oui, c’est cliquable » |
- courte (moins de 600 ms) : elle guide l’œil, elle ne fait jamais attendre ;
- ease-out : un mouvement qui ralentit en arrivant paraît naturel, un mouvement linéaire paraît mécanique ;
- utile : chaque animation répond à une question (« qu’est-ce qui vient d’apparaître ? », « est-ce cliquable ? »).
Le compteur animé, par exemple, s’appuie sur une Timeline qui fait varier une propriété numérique ; un listener recopie la valeur dans le texte :
public static void countUp(Label label, long target) {
SimpleLongProperty value = new SimpleLongProperty(0);
value.addListener((observable, oldValue, newValue) -> label.setText(String.valueOf(newValue)));
label.setText("0");
new Timeline(new KeyFrame(Duration.millis(900), new KeyValue(value, target, EASE_OUT))).play();
}JavaC’est le principe des propriétés observables de JavaFX : on anime une valeur, et tout ce qui l’observe se met à jour.
Étape 12 : sauvegarde et restauration
🟡 IntermédiaireCopier scriptpro.db pendant que l’application l’utilise peut produire un fichier corrompu (copie au milieu d’une écriture). SQLite propose deux outils fiables :
// Sauvegarde : copie cohérente et compacte, même en cours d'utilisation.
statement.execute("VACUUM INTO '" + absoluteTarget.toString().replace("'", "''") + "'");
// Restauration : API « online backup » de SQLite, via une commande du pilote sqlite-jdbc.
statement.executeUpdate("restore from \"" + absoluteSource + "\"");Java📄 Fichier : src/main/java/com/scriptpro/database/DatabaseManager.java
Avant de restaurer, trois sécurités :
- le fichier est vérifié (c’est bien une base SQLite contenant les tables ScriptPro) ;
- la base actuelle est copiée dans
sauvegardes/: une restauration malheureuse se rattrape ; - après restauration, le schéma est remis à niveau (migration), au cas où la sauvegarde viendrait d’une version plus ancienne.
PreparedStatement alors ?VACUUM INTO n’accepte pas de paramètre ?. Le chemin vient d’une boîte de dialogue (pas d’un utilisateur distant), et on double les apostrophes pour qu’un chemin comme C:\Sauvegardes d'avril reste valide.
Étape 13 : les tests automatisés
🟢 DébutantUn test automatisé est un petit programme qui vérifie que ton code fait ce qu’il doit. On les lance avec mvn test. ScriptPro en compte 57.
Tester sans toucher à la vraie base
🟡 IntermédiaireJUnit 5 fournit @TempDir : un dossier temporaire créé avant chaque test et supprimé après. Chaque test a sa propre base, jamais la tienne :
@TempDir
Path directory;
@Test
void newDatabaseStartsWithoutScriptsOrHistory() {
DatabaseManager databaseManager = newDatabase("neuve.db");
assertTrue(new ScriptRepository(databaseManager).findAll(true).isEmpty());
assertTrue(new ExecutionHistoryRepository(databaseManager).findAll().isEmpty());
assertFalse(new CategoryRepository(databaseManager).findAllNames().isEmpty());
}Java📄 Fichier : src/test/java/com/scriptpro/database/DatabaseManagerTest.java
Les tests paramétrés
🟡 IntermédiairePour vérifier 22 commandes dangereuses, inutile d’écrire 22 tests : un seul test paramétré suffit.
@ParameterizedTest
@ValueSource(strings = {
"rm -rf /tmp/test",
"rm -fr /var/log",
"Remove-Item -Force -Recurse C:\\Temp",
"rd /s /q C:\\Temp",
"shutdown /r /t 0",
// …
})
void detectsDangerousCommands(String command) {
assertFalse(service.detectWarnings(command).isEmpty(), "Non détecté : " + command);
}Java📄 Fichier : src/test/java/com/scriptpro/services/SecurityServiceTest.java
On teste aussi l’inverse : Get-ChildItem -Recurse ou echo Le formatage est terminé ne doivent pas déclencher d’alerte. Un garde-fou qui crie au loup tout le temps finit par être ignoré.
Tester l’exécution réelle
🔴 ConfirméScriptRunnerTest lance de vrais scripts PowerShell et CMD et vérifie : les accents, le code retour, le délai maximal, l’arrêt (aucun processus enfant ne doit survivre). C’est possible parce que ScriptRunner ne dépend pas de JavaFX : c’est la récompense de la séparation en couches vue dans l’architecture. Ces tests sont marqués @EnabledOnOs(OS.WINDOWS) pour ne pas échouer sur Linux.
src/test/java/com/scriptpro/tools/DemoScreenshots.java crée une base de démonstration fictive, parcourt chaque écran et en prend une capture avec scene.snapshot(...). Après un changement de design, une commande suffit à refaire toutes les images du README.
Visite guidée du gestionnaire de scripts
Le tableau de bord

La page d’accueil du gestionnaire de scripts répond en un coup d’œil à « où en est-on ? » :
- une bannière d’accueil avec deux raccourcis (nouveau script, bibliothèque) ;
- six indicateurs animés : scripts, favoris, exécutions du jour, échecs, catégories, historique ;
- l’activité récente avec des badges de statut ;
- la répartition par langage sous forme de barres ;
- le taux de réussite avec une barre segmentée (succès, échecs, avertissements, arrêts) ;
- les derniers scripts, ouvrables en un clic.
La bibliothèque

Le catalogue complet de ton gestionnaire de scripts. La recherche cherche dans le nom, le contenu et les tags à chaque frappe. Les filtres se combinent : langage, catégorie, tag, favoris uniquement, date de modification. Les boutons couvrent tout le cycle de vie : créer, modifier, dupliquer, archiver, supprimer, importer un fichier existant, exporter le source ou un PDF.
L’import détecte l’encodage du fichier (UTF-8 avec ou sans BOM, UTF-16, ANSI) : tes vieux .bat gardent leurs accents.
L’éditeur

À gauche, la liste des scripts ; à droite, l’éditeur RichTextFX avec coloration syntaxique (mots-clés, chaînes, commentaires, et les variables {{…}} en jaune) et la console d’exécution en dessous.
| Raccourci | Action |
|---|---|
Ctrl+S | Enregistrer |
F5 | Exécuter |
Maj+F5 | Arrêter |
Ctrl+F | Rechercher |
Au clic sur Exécuter, dans l’ordre : enregistrement, vérification de l’interpréteur, contrôle de sécurité, saisie des variables, puis lancement avec la sortie en direct. Les lignes d’erreur sont préfixées par [ERR].
Les exécutions

Toutes les exécutions, avec des badges de langage et de résultat. Sélectionne une ligne pour voir le détail complet (sortie standard, erreurs, code retour, durée) dans le panneau du bas. Relancer rouvre le script dans l’éditeur et l’exécute à nouveau.
Les favoris

Tes scripts du quotidien, sous forme de cartes avec deux boutons : Ouvrir et Exécuter. Idéal pour le « script de diagnostic » que tu lances dix fois par jour.
Les modèles

Des scripts prêts à adapter (informations système, services, utilisateurs locaux, diagnostic IP, disque, mémoire…). Un clic sur « Créer depuis ce modèle » copie le modèle dans ta bibliothèque et l’ouvre dans l’éditeur. C’est le point de départ idéal avec une base vide.
Les variables

Les variables {{NOM}} et leurs valeurs par défaut. Les variables intégrées (IP, HOSTNAME, USERNAME, PATH, SERVICE) ne peuvent pas être supprimées ; les personnalisées si. La case Sensible active la saisie masquée et le masquage dans l’historique.
L’historique

La même liste que les Exécutions, avec des filtres en plus (date, nom du script, résultat) et un export PDF, pour retrouver « qui a lancé quoi, quand » ou fournir une trace lors d’un audit.
La documentation intégrée

Un guide d’utilisation et une FAQ directement dans l’application, organisés en sections dépliables : pas besoin d’Internet pour comprendre une fonction.
Les paramètres

- Préférences : thème, dossier d’export, dossier de travail des scripts, délai maximal, taille de police, notifications.
- Contrôle de sécurité : tes propres motifs (une expression régulière par ligne).
- Interpréteurs détectés : où ScriptPro a trouvé PowerShell, CMD, Bash et Python.
- Sauvegarde et restauration de la base.
Le thème sombre

Un clic sur Sombre dans la barre du haut. Le choix est mémorisé.
Dépannage
| Problème | Cause probable | Solution |
|---|---|---|
invalid target release: 21 | Maven utilise un JDK trop ancien | Pointe JAVA_HOME vers un JDK 21+ |
mvn n’est pas reconnu | Maven n’est pas dans le PATH | Installe Maven ou ajoute son dossier bin au PATH |
| « Bash est introuvable » | Ni Git Bash ni WSL installé | Installe Git for Windows, puis Actualiser la détection dans les Paramètres |
| « Python est introuvable » | Python absent du PATH | Réinstalle Python en cochant Add python.exe to PATH |
| Le script reste bloqué | Il attend une saisie ou une fenêtre | ScriptPro ferme l’entrée standard ; vérifie que le script n’ouvre pas de programme graphique, sinon le délai maximal l’arrête |
| Accents bizarres dans la sortie | Programme qui écrit dans une page de code exotique | Signale-le avec la commande concernée : la détection couvre UTF-8 et la page OEM du poste |
| « Sortie incomplète » | Le script a lancé un programme qui reste ouvert (start notepad) | Normal : ScriptPro n’attend pas indéfiniment ce programme |
| Repartir de zéro | — | Ferme l’application et supprime scriptpro.db : une base vide est recréée au lancement |
Améliorer ScriptPro
Tu veux t’entraîner en faisant évoluer ton gestionnaire de scripts ? Voici des idées, de la plus simple à la plus ambitieuse :
- 🟢 Raccourci « Dupliquer » (
Ctrl+D) dans l’éditeur. - 🟢 Nouveaux modèles dans
TemplateService(nettoyage de disque, état de BitLocker…). - 🟡 Statistiques par script : durée moyenne, taux d’échec, graphique d’évolution.
- 🟡 Planification : lancer un script tous les jours à 8 h (
ScheduledExecutorService, ou mieux, le Planificateur de tâches Windows). - 🟡 Exécution à distance via
Invoke-Command -ComputerNamepour PowerShell. - 🔴 Coffre-fort de secrets chiffré avec DPAPI ou le Gestionnaire d’identifiants Windows.
- 🔴 Installateur Windows (
.msi) avecjpackage, base déplacée dans%APPDATA%\ScriptPro. Pense à inclure le modulejdk.charsets, sinon le repli d’encodage OEM ne fonctionnera pas. - 🔴 Synchronisation de la bibliothèque entre plusieurs postes via un dépôt Git.
FAQ
Mes scripts sont-ils envoyés quelque part ?
Non. Tout est stocké dans scriptpro.db, sur ton poste. ScriptPro ne fait aucune connexion réseau.
Est-ce que ScriptPro fonctionne sous Linux ou macOS ?
JavaFX et SQLite oui, et Bash et Python aussi. Mais l’application a été pensée pour Windows (PowerShell, CMD, pages de code) et n’est testée que là.
Pourquoi Java et pas C# / .NET pour une application Windows ?
.NET aurait été un excellent choix aussi. Java est multiplateforme, très répandu dans l’enseignement, et JavaFX offre un stylage CSS confortable. Les concepts de ce tutoriel (couches, processus, encodage, threads) se transposent directement en C#.
Le contrôle de sécurité suffit-il à empêcher un script malveillant ?
Non, et il ne le prétend pas. C’est un garde-fou contre l’erreur humaine. Ne lance jamais un script que tu n’as pas relu.
Puis-je utiliser PowerShell 7 au lieu de Windows PowerShell 5.1 ?
ScriptPro privilégie powershell.exe (présent sur tous les Windows) pour garantir la compatibilité. pwsh est utilisé s’il est le seul disponible. Tu peux changer cet ordre dans InterpreterLocator.detect().
Comment sauvegarder mes scripts ?
Paramètres → Sauvegarder la base…. Tu obtiens un fichier .db à conserver où tu veux (NAS, cloud…).
Puis-je utiliser ScriptPro dans mon entreprise ?
Oui : le projet est sous licence MIT. Tu peux l’utiliser, le modifier et le redistribuer librement, en conservant la mention de copyright.
Conclusion
Envie d’un autre projet complet pour les admins Windows ? Découvre notre moniteur système Windows en PHP (System Dashboard), qui complète parfaitement ce gestionnaire de scripts.
Tu as maintenant un gestionnaire de scripts complet, et surtout tu sais pourquoi elle est construite ainsi. En résumé, tu as vu :
- ✅ comment organiser une application en couches et injecter les dépendances sans framework ;
- ✅ comment utiliser SQLite proprement : connexions courtes,
PreparedStatement, migrations idempotentes, sauvegarde à chaud ; - ✅ comment lancer des processus depuis Java sans interblocage, avec délai maximal et arrêt de toute l’arborescence ;
- ✅ comment venir à bout des problèmes d’encodage de la console Windows ;
- ✅ comment garder une interface JavaFX fluide avec
Task,Platform.runLateret le regroupement des mises à jour ; - ✅ comment construire un design moderne : CSS, couleurs porteuses de sens, thème sombre et animations ;
- ✅ comment tester tout ça avec JUnit 5.
Clone le projet, lance-le, casse-le, améliore-le. La meilleure façon d’apprendre reste de modifier un code qui fonctionne : commence par une idée 🟢 de la liste ci-dessus, et monte en difficulté.
