From 94233c3538d34739f9ec016bc18de33dad2896a6 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Thu, 4 Jun 2026 16:31:06 +0200 Subject: [PATCH] build(windows): scripts build one-click + installer + doc - build_windows_oneclick.bat / build_windows_installer_oneclick.bat : wrappers - scripts/build_windows_oneclick.ps1 / build_windows_installer_only.ps1 / install_inno_setup_build_dep.ps1 - build_signing.example.ps1 : exemple protocole signing (sans secret) - docs/build-windows-oneclick.md : documentation du build Co-Authored-By: Claude Opus 4.8 (1M context) --- build_signing.example.ps1 | 17 ++ build_windows_installer_oneclick.bat | 28 ++ build_windows_oneclick.bat | 27 ++ docs/build-windows-oneclick.md | 119 ++++++++ scripts/build_windows_installer_only.ps1 | 61 ++++ scripts/build_windows_oneclick.ps1 | 369 +++++++++++++++++++++++ scripts/install_inno_setup_build_dep.ps1 | 57 ++++ 7 files changed, 678 insertions(+) create mode 100644 build_signing.example.ps1 create mode 100644 build_windows_installer_oneclick.bat create mode 100644 build_windows_oneclick.bat create mode 100644 docs/build-windows-oneclick.md create mode 100644 scripts/build_windows_installer_only.ps1 create mode 100644 scripts/build_windows_oneclick.ps1 create mode 100644 scripts/install_inno_setup_build_dep.ps1 diff --git a/build_signing.example.ps1 b/build_signing.example.ps1 new file mode 100644 index 0000000..15b9845 --- /dev/null +++ b/build_signing.example.ps1 @@ -0,0 +1,17 @@ +# Copier ce fichier en build_signing.local.ps1 sur la machine Windows de build. +# Ne pas versionner build_signing.local.ps1 : il peut contenir des secrets. + +# Active la signature Authenticode pendant build_windows_oneclick.bat. +$BuildSigningEnabled = $true + +# Option recommandée si le certificat est installé dans le magasin Windows. +# Récupérer l'empreinte avec : +# Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert +$BuildSigningCertThumbprint = "REMPLACER_PAR_L_EMPREINTE_DU_CERTIFICAT" + +# Alternative si vous disposez d'un fichier PFX. +# $BuildSigningPfxPath = "C:\chemin\certificat-code-signing.pfx" +# $BuildSigningPfxPassword = "MOT_DE_PASSE_PFX" + +# Serveur d'horodatage RFC 3161. +$BuildSigningTimestampServer = "http://timestamp.digicert.com" diff --git a/build_windows_installer_oneclick.bat b/build_windows_installer_oneclick.bat new file mode 100644 index 0000000..ddd701f --- /dev/null +++ b/build_windows_installer_oneclick.bat @@ -0,0 +1,28 @@ +@echo off +setlocal + +set "SCRIPT_DIR=%~dp0" +set "PS_SCRIPT=%SCRIPT_DIR%scripts\build_windows_oneclick.ps1" + +if not exist "%PS_SCRIPT%" ( + echo Script PowerShell introuvable : %PS_SCRIPT% + pause + exit /b 1 +) + +echo Lancement du build Windows avec installateur... +powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%PS_SCRIPT%" +set "EXITCODE=%ERRORLEVEL%" + +if not "%EXITCODE%"=="0" ( + echo. + echo Le build installateur a echoue. Code retour : %EXITCODE% + pause + exit /b %EXITCODE% +) + +echo. +echo Build installateur termine avec succes. +echo Sortie attendue : release\Anonymisation-Setup.exe +pause +exit /b 0 diff --git a/build_windows_oneclick.bat b/build_windows_oneclick.bat new file mode 100644 index 0000000..e92ffa8 --- /dev/null +++ b/build_windows_oneclick.bat @@ -0,0 +1,27 @@ +@echo off +setlocal + +set "SCRIPT_DIR=%~dp0" +set "PS_SCRIPT=%SCRIPT_DIR%scripts\build_windows_oneclick.ps1" + +if not exist "%PS_SCRIPT%" ( + echo Script PowerShell introuvable : %PS_SCRIPT% + pause + exit /b 1 +) + +echo Lancement du build Windows one-click... +powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%PS_SCRIPT%" +set "EXITCODE=%ERRORLEVEL%" + +if not "%EXITCODE%"=="0" ( + echo. + echo Le build a echoue. Code retour : %EXITCODE% + pause + exit /b %EXITCODE% +) + +echo. +echo Build termine avec succes. +pause +exit /b 0 diff --git a/docs/build-windows-oneclick.md b/docs/build-windows-oneclick.md new file mode 100644 index 0000000..c6103f6 --- /dev/null +++ b/docs/build-windows-oneclick.md @@ -0,0 +1,119 @@ +# Build Windows One-Click + +Le packaging Windows standard du projet repose sur : + +- `build_windows_oneclick.bat` +- `build_windows_installer_oneclick.bat` +- `scripts/build_windows_oneclick.ps1` +- `anonymisation_onefile.spec` +- `installer/Anonymisation.iss` + +## Usage + +Sur la machine Windows de build : + +1. ouvrir le dossier du projet +2. double-cliquer sur `build_windows_oneclick.bat` + +Le script : + +- crée un venv de build local `.venv_build_win` +- installe les dépendances nécessaires au packaging +- génère `build_info.py` +- lance `PyInstaller` avec `anonymisation_onefile.spec` +- vérifie la présence de l'exécutable final +- prépare un dossier de livraison et une archive ZIP +- crée `release\Anonymisation-Setup.exe` si Inno Setup 6 est installé + +## Sorties attendues + +- exécutable : `dist\Anonymisation.exe` +- dossier de livraison : `release\Anonymisation-Windows\` +- archive : `release\Anonymisation-Windows.zip` +- installateur : `release\Anonymisation-Setup.exe` +- hash : `release\Anonymisation.exe.sha256.txt` + +## Installateur Windows + +L'installateur est généré avec Inno Setup 6. Il fournit : + +- choix du dossier d'installation +- installation utilisateur par défaut sans droits administrateur +- raccourci menu Démarrer +- option d'icône sur le bureau +- désinstallation Windows standard + +Si Inno Setup n'est pas présent sur la machine de build, le script conserve le +build EXE/ZIP et affiche un avertissement. Installer Inno Setup 6 depuis le site +officiel puis relancer le build. + +Installation automatisée de la dépendance de build Inno Setup : + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\install_inno_setup_build_dep.ps1 +``` + +Recompiler uniquement l'installateur à partir de `release\Anonymisation-Windows\Anonymisation.exe` : + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build_windows_installer_only.ps1 +``` + +Pour ne générer que l'exécutable et le ZIP : + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build_windows_oneclick.ps1 -SkipInstaller +``` + +## Important + +- les utilisateurs finaux n'ont pas besoin d'installer Python +- le build doit être lancé depuis Windows +- le modèle ONNX embarqué requis doit exister localement dans : + `models\camembert-bio-deid\onnx\model.onnx` + +## Blocage Windows / SmartScreen + +Un exécutable PyInstaller non signé peut déclencher Microsoft Defender SmartScreen, surtout s'il est téléchargé depuis Internet ou envoyé par e-mail. La signature réduit fortement le risque et évite l'éditeur inconnu, mais elle ne garantit pas toujours l'absence totale d'avertissement SmartScreen pour une toute nouvelle version : Windows tient aussi compte de la réputation du fichier et de son hash. + +Pour une diffusion à des utilisateurs novices, la voie recommandée est : + +- signer `Anonymisation.exe` avec un certificat Authenticode +- horodater la signature +- diffuser par partage réseau interne, Intune, GPO ou portail établissement +- conserver le hash `release\Anonymisation.exe.sha256.txt` +- éviter de demander aux utilisateurs de cliquer sur `Exécuter quand même` + +Le script prend en charge la signature si un certificat est disponible. + +### Signature automatique avec configuration locale + +Sur la machine Windows de build : + +1. copier `build_signing.example.ps1` en `build_signing.local.ps1` +2. renseigner l'empreinte du certificat ou le chemin du PFX +3. double-cliquer comme d'habitude sur `build_windows_oneclick.bat` + +`build_signing.local.ps1` est ignoré par Git pour éviter de versionner des secrets. + +### Signature manuelle via PowerShell + +Avec un certificat installé dans le magasin Windows : + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build_windows_oneclick.ps1 -Sign -CertThumbprint "EMPREINTE_CERTIFICAT" +``` + +Avec un fichier PFX : + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build_windows_oneclick.ps1 -Sign -PfxPath "C:\chemin\certificat.pfx" -PfxPassword "mot-de-passe" +``` + +Si aucun certificat n'est disponible, le build reste possible, mais Windows peut afficher un avertissement de réputation au premier lancement. + +Références Microsoft : + +- SmartScreen reputation : https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/smartscreen-reputation +- SignTool : https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool +- Authenticode timestamping : https://learn.microsoft.com/en-us/windows/win32/seccrypto/time-stamping-authenticode-signatures diff --git a/scripts/build_windows_installer_only.ps1 b/scripts/build_windows_installer_only.ps1 new file mode 100644 index 0000000..8706b81 --- /dev/null +++ b/scripts/build_windows_installer_only.ps1 @@ -0,0 +1,61 @@ +param( + [string]$AppVersion = (Get-Date -Format "yyyy.MM.dd.HHmm") +) + +$ErrorActionPreference = "Stop" + +function Resolve-InnoCompiler { + $command = Get-Command ISCC.exe -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + $candidates = @() + if (${env:ProgramFiles(x86)}) { + $candidates += (Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6\ISCC.exe") + } + if ($env:ProgramFiles) { + $candidates += (Join-Path $env:ProgramFiles "Inno Setup 6\ISCC.exe") + } + if ($env:LOCALAPPDATA) { + $candidates += (Join-Path $env:LOCALAPPDATA "Programs\Inno Setup 6\ISCC.exe") + $candidates += (Join-Path $env:LOCALAPPDATA "Inno Setup 6\ISCC.exe") + } + + foreach ($candidate in $candidates) { + if ($candidate -and (Test-Path $candidate)) { + return $candidate + } + } + throw "ISCC.exe introuvable. Installer Inno Setup 6 puis relancer." +} + +function Require-Path { + param( + [string]$PathValue, + [string]$Label + ) + if (-not (Test-Path $PathValue)) { + throw "$Label introuvable: $PathValue" + } +} + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path +$InstallerScriptPath = Join-Path $ProjectRoot "installer\Anonymisation.iss" +$PackageExePath = Join-Path $ProjectRoot "release\Anonymisation-Windows\Anonymisation.exe" +$InstallerPath = Join-Path $ProjectRoot "release\Anonymisation-Setup.exe" + +Require-Path -PathValue $InstallerScriptPath -Label "Script Inno Setup" +Require-Path -PathValue $PackageExePath -Label "Executable package" + +$innoCompiler = Resolve-InnoCompiler +Write-Host "Inno Setup Compiler : $innoCompiler" +& $innoCompiler "/DAppVersion=$AppVersion" $InstallerScriptPath +if ($LASTEXITCODE -ne 0) { + throw "Inno Setup a echoue avec le code $LASTEXITCODE." +} + +Require-Path -PathValue $InstallerPath -Label "Installateur Windows" +$installerSizeMb = [math]::Round((Get-Item $InstallerPath).Length / 1MB, 1) +Write-Host "Installateur pret : $InstallerPath ($installerSizeMb MB)" diff --git a/scripts/build_windows_oneclick.ps1 b/scripts/build_windows_oneclick.ps1 new file mode 100644 index 0000000..e626cd1 --- /dev/null +++ b/scripts/build_windows_oneclick.ps1 @@ -0,0 +1,369 @@ +param( + [switch]$SkipZip, + [switch]$SkipInstaller, + [switch]$SkipRequirements, + [switch]$Sign, + [string]$CertThumbprint, + [string]$PfxPath, + [string]$PfxPassword, + [string]$TimestampServer = "http://timestamp.digicert.com" +) + +$ErrorActionPreference = "Stop" +$script:SignatureSummary = "Non signé" + +function Write-Step { + param([string]$Message) + Write-Host "" + Write-Host "=== $Message ===" -ForegroundColor Cyan +} + +function Require-Path { + param( + [string]$PathValue, + [string]$Label + ) + if (-not (Test-Path $PathValue)) { + throw "$Label introuvable: $PathValue" + } +} + +function Invoke-BootstrapPython { + param([string[]]$Arguments) + if ($script:PythonBootstrap[0] -eq "py") { + & py $script:PythonBootstrap[1] @Arguments + } else { + & $script:PythonBootstrap[0] @Arguments + } +} + +function Resolve-BootstrapPython { + if (Get-Command py -ErrorAction SilentlyContinue) { + try { + & py -3.11 --version | Out-Host + if ($LASTEXITCODE -eq 0) { + return @("py", "-3.11") + } + } catch {} + try { + & py -3 --version | Out-Host + if ($LASTEXITCODE -eq 0) { + return @("py", "-3") + } + } catch {} + } + if (Get-Command python -ErrorAction SilentlyContinue) { + & python --version | Out-Host + if ($LASTEXITCODE -eq 0) { + return @("python") + } + } + throw "Python introuvable sur la machine de build Windows." +} + +function Resolve-SignTool { + $command = Get-Command signtool.exe -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + $programFilesX86 = ${env:ProgramFiles(x86)} + if ($programFilesX86) { + $kitsRoot = Join-Path $programFilesX86 "Windows Kits\10\bin" + if (Test-Path $kitsRoot) { + $candidates = @( + Get-ChildItem -Path $kitsRoot -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match "\\x64\\signtool\.exe$" } | + Sort-Object FullName -Descending + ) + if ($candidates.Count -gt 0) { + return $candidates[0].FullName + } + } + } + + throw "signtool.exe introuvable. Installer Windows SDK ou ajouter signtool.exe au PATH." +} + +function Resolve-InnoCompiler { + $command = Get-Command ISCC.exe -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + $candidates = @() + if (${env:ProgramFiles(x86)}) { + $candidates += (Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6\ISCC.exe") + } + if ($env:ProgramFiles) { + $candidates += (Join-Path $env:ProgramFiles "Inno Setup 6\ISCC.exe") + } + if ($env:LOCALAPPDATA) { + $candidates += (Join-Path $env:LOCALAPPDATA "Programs\Inno Setup 6\ISCC.exe") + $candidates += (Join-Path $env:LOCALAPPDATA "Inno Setup 6\ISCC.exe") + } + foreach ($candidate in $candidates) { + if ($candidate -and (Test-Path $candidate)) { + return $candidate + } + } + + return $null +} + +function Invoke-CodeSigning { + param([string]$FilePath) + + if (-not $Sign) { + Write-Host "Signature Authenticode ignorée. Utiliser -Sign pour signer l'exécutable." + return + } + + Require-Path -PathValue $FilePath -Label "Fichier à signer" + if ($PfxPath) { + Require-Path -PathValue $PfxPath -Label "Certificat PFX" + } + + $signTool = Resolve-SignTool + Write-Host "SignTool : $signTool" + + if ($CertThumbprint -eq "REMPLACER_PAR_L_EMPREINTE_DU_CERTIFICAT") { + throw "Empreinte de certificat non renseignée dans build_signing.local.ps1." + } + + $args = @("sign", "/fd", "SHA256", "/tr", $TimestampServer, "/td", "SHA256", "/d", "Anonymisation") + if ($PfxPath) { + $args += @("/f", $PfxPath) + if ($PfxPassword) { + $args += @("/p", $PfxPassword) + } + } elseif ($CertThumbprint) { + $args += @("/sha1", ($CertThumbprint -replace "\s", "")) + } else { + $args += @("/a") + } + $args += $FilePath + + & $signTool @args + if ($LASTEXITCODE -ne 0) { + throw "La signature Authenticode a échoué." + } + + & $signTool verify /pa /v $FilePath + if ($LASTEXITCODE -ne 0) { + throw "La vérification Authenticode a échoué." + } + + $signature = Get-AuthenticodeSignature $FilePath + $subject = "" + if ($signature.SignerCertificate) { + $subject = $signature.SignerCertificate.Subject + } + $script:SignatureSummary = "$($signature.Status) - $subject" + Write-Host "Signature : $script:SignatureSummary" + + if ($signature.Status -ne "Valid") { + throw "Signature Authenticode non valide : $($signature.Status)" + } +} + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path +$SigningConfigPath = Join-Path $ProjectRoot "build_signing.local.ps1" +$SpecPath = Join-Path $ProjectRoot "anonymisation_onefile.spec" +$InstallerScriptPath = Join-Path $ProjectRoot "installer\Anonymisation.iss" +$BuildInfoPath = Join-Path $ProjectRoot "build_info.py" +$ModelPath = Join-Path $ProjectRoot "models\camembert-bio-deid\onnx\model.onnx" +$VenvDir = Join-Path $ProjectRoot ".venv_build_win" +$VenvPython = Join-Path $VenvDir "Scripts\python.exe" +$DistDir = Join-Path $ProjectRoot "dist" +$BuildDir = Join-Path $ProjectRoot "build" +$ReleaseDir = Join-Path $ProjectRoot "release" +$ExePath = Join-Path $DistDir "Anonymisation.exe" +$PackageDir = Join-Path $ReleaseDir "Anonymisation-Windows" +$ZipPath = Join-Path $ReleaseDir "Anonymisation-Windows.zip" +$HashPath = Join-Path $ReleaseDir "Anonymisation.exe.sha256.txt" +$InstallerPath = Join-Path $ReleaseDir "Anonymisation-Setup.exe" +$ReadmePath = Join-Path $PackageDir "README.txt" +$RequiredSourceFiles = @( + "launcher.py", + "Pseudonymisation_Gui_V5.py", + "anonymizer_core_refactored_onnx.py", + "admin_rules.py", + "config_defaults.py", + "profile_defaults.py", + "gui_batch_paths.py", + "manual_masking.py", + "pdf_mask_designer.py", + "format_converter.py", + "camembert_ner_manager.py" +) + +Write-Step "Préparation du build Windows" +Write-Host "Projet : $ProjectRoot" + +Require-Path -PathValue $SpecPath -Label "Spec PyInstaller" +Require-Path -PathValue $InstallerScriptPath -Label "Script installateur Inno Setup" +Require-Path -PathValue $ModelPath -Label "Modèle ONNX embarqué" +foreach ($RelativeSourceFile in $RequiredSourceFiles) { + Require-Path -PathValue (Join-Path $ProjectRoot $RelativeSourceFile) -Label "Module source requis" +} + +if (Test-Path $SigningConfigPath) { + Write-Step "Configuration locale de signature" + . $SigningConfigPath + if ($BuildSigningEnabled) { $Sign = $true } + if ($BuildSigningCertThumbprint -and -not $CertThumbprint) { $CertThumbprint = $BuildSigningCertThumbprint } + if ($BuildSigningPfxPath -and -not $PfxPath) { $PfxPath = $BuildSigningPfxPath } + if ($BuildSigningPfxPassword -and -not $PfxPassword) { $PfxPassword = $BuildSigningPfxPassword } + if ($BuildSigningTimestampServer -and $TimestampServer -eq "http://timestamp.digicert.com") { + $TimestampServer = $BuildSigningTimestampServer + } + if ($Sign) { + Write-Host "Signature activée depuis build_signing.local.ps1" + } +} + +Write-Step "Détection de Python" +$script:PythonBootstrap = Resolve-BootstrapPython +Write-Host "Bootstrap Python : $($script:PythonBootstrap -join ' ')" + +Write-Step "Environnement virtuel de build" +if (-not (Test-Path $VenvPython)) { + Write-Host "Création du venv : $VenvDir" + Invoke-BootstrapPython -Arguments @("-m", "venv", $VenvDir) +} +Require-Path -PathValue $VenvPython -Label "Python du venv" + +Push-Location $ProjectRoot +try { + Write-Step "Installation des dépendances de build" + & $VenvPython -m pip install --upgrade pip setuptools wheel + if (-not $SkipRequirements) { + & $VenvPython -m pip install -r requirements.txt + } + & $VenvPython -m pip install pyinstaller + + Write-Step "Génération de build_info.py" + $commit = "local" + $branch = "local" + if (Get-Command git -ErrorAction SilentlyContinue) { + try { + $gitCommit = (git rev-parse --short HEAD 2>$null | Out-String).Trim() + if ($gitCommit) { $commit = $gitCommit } + $gitBranch = (git rev-parse --abbrev-ref HEAD 2>$null | Out-String).Trim() + if ($gitBranch) { $branch = $gitBranch } + } catch {} + } + $buildDate = Get-Date -Format "yyyy-MM-dd HH:mm" + $buildInfo = @" +"""Métadonnées de build - généré automatiquement par build_windows_oneclick.ps1.""" +BUILD_DATE = "$buildDate" +BUILD_COMMIT = "$commit" +BUILD_BRANCH = "$branch" +"@ + Set-Content -Path $BuildInfoPath -Value $buildInfo -Encoding UTF8 + Write-Host "Build info : $buildDate / $branch / $commit" + + Write-Step "Nettoyage des anciens artefacts" + foreach ($PathValue in @($BuildDir, $DistDir, $PackageDir)) { + if (Test-Path $PathValue) { + Remove-Item -Recurse -Force $PathValue -ErrorAction SilentlyContinue + } + } + if (Test-Path $ZipPath) { + Remove-Item -Force $ZipPath -ErrorAction SilentlyContinue + } + if (Test-Path $HashPath) { + Remove-Item -Force $HashPath -ErrorAction SilentlyContinue + } + if (Test-Path $InstallerPath) { + Remove-Item -Force $InstallerPath -ErrorAction SilentlyContinue + } + + Write-Step "Compilation PyInstaller" + & $VenvPython -m PyInstaller --clean --noconfirm $SpecPath + if ($LASTEXITCODE -ne 0) { + throw "PyInstaller a échoué avec le code $LASTEXITCODE." + } + + Write-Step "Vérification de l'exécutable" + Require-Path -PathValue $ExePath -Label "Exécutable Windows" + $exeSizeMb = [math]::Round((Get-Item $ExePath).Length / 1MB, 1) + Write-Host "EXE créé : $ExePath ($exeSizeMb MB)" + + Write-Step "Signature Authenticode" + Invoke-CodeSigning -FilePath $ExePath + + Write-Step "Préparation du dossier de livraison" + New-Item -ItemType Directory -Force -Path $PackageDir | Out-Null + Copy-Item $ExePath (Join-Path $PackageDir "Anonymisation.exe") + + $readme = @" +Anonymisation - paquet Windows +================================ + +Fichier principal : +- Anonymisation.exe + +Conseils de diffusion : +- Aucune installation de Python n'est nécessaire pour l'utilisateur final. +- Conservez le fichier dans un dossier en écriture (par exemple Bureau ou Documents). +- Privilégiez une diffusion par partage réseau interne, Intune, GPO ou portail établissement. +- Évitez l'envoi direct par e-mail ou téléchargement public non signé. +- Le journal applicatif s'écrit à côté de l'exécutable : anonymisation.log + +Build : +- Date : $buildDate +- Branche : $branch +- Commit : $commit +- Signature : $script:SignatureSummary +"@ + Set-Content -Path $ReadmePath -Value $readme -Encoding UTF8 + + $hash = (Get-FileHash -Algorithm SHA256 $ExePath).Hash + Set-Content -Path $HashPath -Value "SHA256 Anonymisation.exe $hash" -Encoding UTF8 + Write-Host "SHA256 : $hash" + + if (-not $SkipZip) { + Write-Step "Création de l'archive de livraison" + Compress-Archive -Path (Join-Path $PackageDir "*") -DestinationPath $ZipPath -CompressionLevel Optimal + Write-Host "Archive créée : $ZipPath" + } + + if (-not $SkipInstaller) { + Write-Step "Création de l'installateur Windows" + $innoCompiler = Resolve-InnoCompiler + if ($innoCompiler) { + Write-Host "Inno Setup Compiler : $innoCompiler" + $installerVersion = (Get-Date -Format "yyyy.MM.dd.HHmm") + & $innoCompiler "/DAppVersion=$installerVersion" $InstallerScriptPath + if ($LASTEXITCODE -ne 0) { + throw "Inno Setup a échoué avec le code $LASTEXITCODE." + } + Require-Path -PathValue $InstallerPath -Label "Installateur Windows" + $installerSizeMb = [math]::Round((Get-Item $InstallerPath).Length / 1MB, 1) + Write-Host "Installateur créé : $InstallerPath ($installerSizeMb MB)" + + if ($Sign) { + Write-Step "Signature Authenticode de l'installateur" + Invoke-CodeSigning -FilePath $InstallerPath + } + } else { + Write-Warning "Inno Setup 6 introuvable. Installateur ignoré. Installer Inno Setup puis relancer le build." + Write-Warning "Téléchargement officiel : https://jrsoftware.org/isdl.php" + } + } + + Write-Step "Build terminé" + Write-Host "EXE final : $ExePath" -ForegroundColor Green + if (-not $SkipZip) { + Write-Host "Archive prête : $ZipPath" -ForegroundColor Green + } + if ((-not $SkipInstaller) -and (Test-Path $InstallerPath)) { + Write-Host "Installateur prêt : $InstallerPath" -ForegroundColor Green + } + Write-Host "Hash SHA256 : $HashPath" -ForegroundColor Green +} finally { + Pop-Location +} diff --git a/scripts/install_inno_setup_build_dep.ps1 b/scripts/install_inno_setup_build_dep.ps1 new file mode 100644 index 0000000..9c63512 --- /dev/null +++ b/scripts/install_inno_setup_build_dep.ps1 @@ -0,0 +1,57 @@ +param( + [string]$DownloadUrl = "https://jrsoftware.org/download.php/is.exe" +) + +$ErrorActionPreference = "Stop" + +function Write-Step { + param([string]$Message) + Write-Host "" + Write-Host "=== $Message ===" -ForegroundColor Cyan +} + +function Find-InnoCompiler { + $candidates = @() + if (${env:ProgramFiles(x86)}) { + $candidates += (Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6\ISCC.exe") + } + if ($env:ProgramFiles) { + $candidates += (Join-Path $env:ProgramFiles "Inno Setup 6\ISCC.exe") + } + if ($env:LOCALAPPDATA) { + $candidates += (Join-Path $env:LOCALAPPDATA "Programs\Inno Setup 6\ISCC.exe") + $candidates += (Join-Path $env:LOCALAPPDATA "Inno Setup 6\ISCC.exe") + } + foreach ($candidate in $candidates) { + if ($candidate -and (Test-Path $candidate)) { + return $candidate + } + } + return $null +} + +$existing = Find-InnoCompiler +if ($existing) { + Write-Host "Inno Setup deja disponible : $existing" + exit 0 +} + +Write-Step "Telechargement Inno Setup" +$installerPath = Join-Path $env:TEMP "innosetup-build-dep.exe" +Invoke-WebRequest -Uri $DownloadUrl -OutFile $installerPath +Write-Host "Installeur telecharge : $installerPath" + +Write-Step "Installation Inno Setup utilisateur" +$args = @("/SP-", "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/CURRENTUSER") +$process = Start-Process -FilePath $installerPath -ArgumentList $args -Wait -PassThru +Write-Host "Code retour : $($process.ExitCode)" +if ($process.ExitCode -ne 0) { + throw "Installation Inno Setup echouee avec le code $($process.ExitCode)." +} + +$compiler = Find-InnoCompiler +if (-not $compiler) { + throw "ISCC.exe introuvable apres installation Inno Setup." +} + +Write-Host "Inno Setup pret : $compiler"