310 lines
10 KiB
PowerShell
310 lines
10 KiB
PowerShell
param(
|
|
[switch]$SkipZip,
|
|
[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 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"
|
|
$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"
|
|
$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 $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
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
Write-Step "Build terminé"
|
|
Write-Host "EXE final : $ExePath" -ForegroundColor Green
|
|
if (-not $SkipZip) {
|
|
Write-Host "Archive prête : $ZipPath" -ForegroundColor Green
|
|
}
|
|
Write-Host "Hash SHA256 : $HashPath" -ForegroundColor Green
|
|
} finally {
|
|
Pop-Location
|
|
}
|