build: script d'installation de cuDNN, fonctions de téléchargement communes

install-cudnn.ps1 : version la plus récente lue dans l'index NVIDIA, archive cuda13 téléchargée avec reprise et vérifiée par SHA256, licence NVIDIA affichée et acceptée par l'utilisateur, installation sans droits admin dans %LOCALAPPDATA%\SEQUA\sdk\cudnn, variable utilisateur CUDNN_PATH. Refuse de remplacer un dossier de destination non vide qui n'est pas une installation cuDNN. Fonctions de téléchargement et d'empreinte factorisées dans scripts/lib/SequaDownload.ps1 (install-cuda.ps1 inchangé fonctionnellement). Écrit par un subagent (DeepSeek V4.1 Flash), relu et corrigé.
This commit is contained in:
Ange-Marie MAURIN - A2M Systèmes committed 2026-11-06 21:47:25 +01:00
1 parent 545b8fd369
commit 522ea04eb4
4 files changed
+437 -55

No files matched your search

+1
View File
@@ -37,6 +37,7 @@ Il n'y a pas d'intégration continue sur serveur : les vérifications se lancent
| Windows, avant commit | `.\scripts\check.ps1` | idem + Node.js pour Studio |
| Linux / macOS | `./scripts/build.sh [<preset>]` | CMake ≥ 3.25, Ninja, GCC ou Clang |
| Lancer Studio | `.\scripts\studio.ps1 [-Core ws://hôte:port]` ou `./scripts/studio.sh [ws://hôte:port]` | Node.js 24 |
| Windows, cuDNN pour l'inférence GPU | `.\scripts\install-cudnn.ps1 [-Version <x.y.z>] [-CudaMajor 12\|13] [-DryRun]` | CUDA Toolkit installé + GPU NVIDIA ; télécharge cuDNN (~1,3 Go), demande d'accepter la licence NVIDIA et définit `CUDNN_PATH` (utilisateur, sans droits admin) |
Chaque script PowerShell affiche son aide avec `-Help`.
+3 -55
View File
@@ -74,10 +74,9 @@ if ($Help) {
}
$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "lib\SequaDownload.ps1")
$nvidiaBase = "https://developer.download.nvidia.com/compute/cuda"
function Write-Step([string]$text) { Write-Host "==> $text" -ForegroundColor Cyan }
function Get-DriverCudaVersion {
$smi = Get-Command nvidia-smi.exe -ErrorAction SilentlyContinue
if (-not $smi) { throw "nvidia-smi introuvable : pilote NVIDIA absent." }
@@ -112,54 +111,6 @@ function Get-InstallerInfo([string]$version) {
}
}
# Empreinte via .NET : Get-FileHash manque dans certains Windows PowerShell 5.1
# (PSModulePath pointant vers les modules de PowerShell 7).
function Get-Md5([string]$path) {
$stream = [System.IO.File]::OpenRead($path)
$md5 = [System.Security.Cryptography.MD5]::Create()
try {
([System.BitConverter]::ToString($md5.ComputeHash($stream))).Replace("-", "")
}
finally {
$md5.Dispose()
$stream.Dispose()
}
}
function Get-RemoteSize([string]$url) {
$headers = & curl.exe -sIL --fail $url
if ($LASTEXITCODE -ne 0) { throw "Serveur injoignable : $url" }
$length = $headers | Select-String -Pattern '^Content-Length:\s*(\d+)' | Select-Object -Last 1
if (-not $length) { throw "Taille du fichier inconnue (pas de Content-Length)." }
[int64]$length.Matches[0].Groups[1].Value
}
# Telechargement avec reprise : curl -C - repart de la taille du fichier .part.
function Invoke-ResumableDownload([string]$url, [string]$destination, [int64]$expectedSize, [int]$maxAttempts) {
$part = "$destination.part"
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
$have = if (Test-Path $part) { (Get-Item $part).Length } else { 0 }
if ($have -eq $expectedSize) { break }
if ($have -gt $expectedSize) {
Write-Host " Fichier partiel plus grand que prevu : on recommence." -ForegroundColor Yellow
Remove-Item $part -Force
$have = 0
}
$pct = [math]::Round(100.0 * $have / $expectedSize, 1)
Write-Host (" Tentative {0}/{1} : {2:N0} / {3:N0} octets ({4} %)" -f $attempt, $maxAttempts, $have, $expectedSize, $pct)
& curl.exe -L --fail -C - --retry 5 --retry-delay 5 --retry-all-errors --speed-limit 1024 --speed-time 60 -o $part $url
if ($LASTEXITCODE -ne 0) {
Write-Host " curl interrompu (code $LASTEXITCODE), reprise dans 10 s..." -ForegroundColor Yellow
Start-Sleep -Seconds 10
}
}
$final = if (Test-Path $part) { (Get-Item $part).Length } else { 0 }
if ($final -ne $expectedSize) {
throw "Telechargement incomplet apres $maxAttempts tentatives ($final / $expectedSize octets). Relancer le script pour reprendre."
}
Move-Item $part $destination -Force
}
# --- Verifications ----------------------------------------------------------
if ($Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { throw "-Version attend une version complete, ex. 13.5.0 (recu : $Version)." }
$wanted = [version]$Version
@@ -208,16 +159,13 @@ if (Test-Path $installer) {
Write-Step "Installateur deja present dans le cache"
}
else {
$drive = Get-PSDrive -Name ([System.IO.Path]::GetPathRoot($CacheDir).Substring(0, 1))
if ($drive.Free -lt ($size * 2)) {
throw ("Espace disque insuffisant sur {0}: ({1:N1} Go libres, {2:N1} Go conseilles)." -f $drive.Name, ($drive.Free / 1GB), ($size * 2 / 1GB))
}
Assert-FreeSpace -Path $CacheDir -Needed ($size * 2)
Write-Step "Telechargement avec reprise"
Invoke-ResumableDownload $info.Url $installer $size $MaxAttempts
}
Write-Step "Verification MD5"
$hash = Get-Md5 $installer
$hash = Get-FileDigest -Path $installer -Algorithm MD5
if ($hash -ne $info.Md5) {
Remove-Item $installer -Force
throw "MD5 incorrect ($hash) : fichier supprime, relancer le script pour le retelecharger."
+363
View File
@@ -0,0 +1,363 @@
# SPDX-License-Identifier: MPL-2.0
# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM.
<#
.SYNOPSIS
Telecharge (avec reprise) et installe cuDNN NVIDIA pour Windows x86_64.
.DESCRIPTION
Utilise pour le prototype prototypes/gpu_interop (etape 3 : inference ONNX Runtime
sur GPU). cuDNN est un composant PROPRIETAIRE NVIDIA : il reste un plugin facultatif
de SEQUA (docs, par. 10.2). Le script n'accepte la licence qu'avec -AcceptLicense ou
apres confirmation interactive (O/N) ; il ne modifie jamais le PATH.
Etapes :
1. detecte la majeure CUDA via la variable machine CUDA_PATH (ou -CudaMajor) ;
2. lit l'index officiel NVIDIA et, si -Version est absent, prend la version la plus
recente (tri par version, pas par chaine : 9.28 > 9.9) ;
3. lit le manifeste redistrib_<version>.json et l'entree Windows x86_64 / cuda<maj> ;
4. telecharge l'archive (~1,3 Go) avec reprise : en cas de coupure, relancer le script
repart du dernier octet recu (curl -C -) ;
5. verifie le SHA256 (le fichier est supprime s'il est faux) ;
6. extrait dans un dossier temporaire a cote de la destination, affiche la licence
extraite et ses premieres lignes, puis demande confirmation (O/N) ;
7. installe en aplatissant le dossier racine de l'archive : <InstallDir>\bin,
\include, \lib ;
8. sauf -NoEnv, definit la variable d'environnement UTILISATEUR CUDNN_PATH (aucun
droit administrateur requis) et n'ajoute PAS au PATH.
A lancer dans une fenetre PowerShell externe (telechargement long).
.PARAMETER Version
Version cuDNN (ex. 9.28.0). Defaut : la plus recente de l'index NVIDIA.
.PARAMETER CudaMajor
Majeure CUDA du paquet a installer (12 ou 13). Defaut : deduite de CUDA_PATH machine.
.PARAMETER InstallDir
Dossier d'installation. Defaut : %LOCALAPPDATA%\SEQUA\sdk\cudnn\<version>_cuda<maj>.
.PARAMETER CacheDir
Dossier de telechargement. Defaut : %LOCALAPPDATA%\SEQUA\downloads\cudnn.
.PARAMETER ArchivePath
Installe depuis une archive locale deja telechargee, sans acces au manifeste. Aucune
empreinte connue : la verification SHA256 est clairement signalee comme ignoree.
.PARAMETER DryRun
Affiche ce qui serait fait (URL, SHA256, taille, destination, etat du cache) sans rien
telecharger, extraire ni installer.
.PARAMETER DownloadOnly
Telecharge et verifie l'archive sans l'extraire ni l'installer.
.PARAMETER AcceptLicense
Accepte la licence cuDNN sans demande interactive.
.PARAMETER NoEnv
N'ecrit pas la variable d'environnement utilisateur CUDNN_PATH (utile pour les tests).
.PARAMETER Force
Reinstalle meme si une DLL cudnn64_*.dll est deja presente dans la destination.
.PARAMETER Help
Affiche cette aide.
.EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DryRun
.EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DownloadOnly
.EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1
.EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -ArchivePath C:\cudnn.zip -AcceptLicense -NoEnv -InstallDir C:\tmp\cudnn
#>
[CmdletBinding()]
param(
[string]$Version = "",
[int]$CudaMajor = 0,
[string]$InstallDir = "",
[string]$CacheDir = (Join-Path $env:LOCALAPPDATA "SEQUA\downloads\cudnn"),
[string]$ArchivePath = "",
[switch]$DryRun,
[switch]$DownloadOnly,
[switch]$AcceptLicense,
[switch]$NoEnv,
[switch]$Force,
[Alias("h", "?")]
[switch]$Help
)
if ($Help) {
Get-Help $PSCommandPath -Detailed
exit 0
}
$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "lib\SequaDownload.ps1")
$nvidiaBase = "https://developer.download.nvidia.com/compute/cudnn/redist"
$maxAttempts = 50
# Majeure CUDA depuis CUDA_PATH (machine d'abord : c'est la variable installee par le
# CUDA Toolkit ; le processus courant peut ne pas encore l'avoir heritee).
function Get-CudaMajor {
foreach ($scope in @("Machine", "Process")) {
$path = [System.Environment]::GetEnvironmentVariable("CUDA_PATH", $scope)
if ($path -and $path -match 'v([0-9]+)\.[0-9]+') { return [int]$Matches[1] }
}
return 0
}
# Versions cuDNN publiees dans l'index, triees par version (9.28 > 9.9), sans doublon.
function Get-CudnnVersions {
$index = & curl.exe -sL --fail "$nvidiaBase/"
if ($LASTEXITCODE -ne 0) { throw "Index cuDNN injoignable : $nvidiaBase/" }
$index |
ForEach-Object { if ($_ -match 'redistrib_([0-9]+\.[0-9]+\.[0-9]+)\.json') { $Matches[1] } } |
Sort-Object { [version]$_ } -Unique
}
# Manifeste officiel de la version : donne chemin relatif, sha256, md5, taille.
function Get-CudnnManifest([string]$version) {
$url = "$nvidiaBase/redistrib_$version.json"
$json = & curl.exe -sL --fail $url
if ($LASTEXITCODE -ne 0) { throw "Manifeste cuDNN introuvable : $url" }
($json -join "`n") | ConvertFrom-Json
}
# Extraction ZIP sans Expand-Archive (absent de certains Windows PowerShell 5.1) :
# .NET en premier, tar.exe de Windows 10+ en repli.
function Expand-ZipArchive([string]$archive, [string]$destination) {
New-Item -ItemType Directory -Force -Path $destination | Out-Null
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
[System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $destination)
return
}
catch {
Write-Host " Extraction .NET indisponible : repli sur tar.exe." -ForegroundColor Yellow
Remove-Item -Recurse -Force $destination -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $destination | Out-Null
}
& tar.exe -xf $archive -C $destination
if ($LASTEXITCODE -ne 0) { throw "Extraction impossible : $archive" }
}
# L'archive cuDNN contient un dossier racine unique : on le detecte pour l'aplatir.
function Get-ExtractedRoot([string]$dir) {
$entries = @(Get-ChildItem -Force -LiteralPath $dir)
if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { return $entries[0].FullName }
return $dir
}
# DLL cuDNN a la racine du dossier bin, ou dans un sous-dossier de version CUDA.
function Find-CudnnDll([string]$root) {
if (-not (Test-Path -LiteralPath $root)) { return @() }
@(Get-ChildItem -Path $root -Recurse -File -Filter "cudnn64_*.dll" -ErrorAction SilentlyContinue)
}
# --- Detection de CUDA ------------------------------------------------------
Write-Step "CUDA installe"
if ($CudaMajor -gt 0) { $cudaMajor = $CudaMajor } else { $cudaMajor = Get-CudaMajor }
if ($cudaMajor -gt 0) {
Write-Host " Majeure CUDA : $cudaMajor (CUDA_PATH)"
}
else {
Write-Host " Majeure CUDA inconnue (CUDA_PATH absent)." -ForegroundColor Yellow
}
$fromArchive = -not [string]::IsNullOrWhiteSpace($ArchivePath)
if ($fromArchive) {
if (-not (Test-Path -LiteralPath $ArchivePath -PathType Leaf)) { throw "Archive introuvable : $ArchivePath" }
$archive = (Resolve-Path -LiteralPath $ArchivePath).Path
}
# --- Version ----------------------------------------------------------------
if ([string]::IsNullOrWhiteSpace($Version)) {
if ($fromArchive) {
if ((Split-Path -Leaf $archive) -match '([0-9]+\.[0-9]+\.[0-9]+)') { $Version = $Matches[1] } else { $Version = "local" }
}
else {
Write-Step "Index cuDNN"
$versions = @(Get-CudnnVersions)
if ($versions.Count -eq 0) { throw "Aucune version cuDNN dans l'index $nvidiaBase/." }
$Version = $versions[-1]
Write-Host " Derniere version : $Version"
}
}
if ($Version -ne "local" -and $Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') {
throw "-Version attend une version x.y.z, ex. 9.28.0 (recu : $Version)."
}
# --- Destination ------------------------------------------------------------
if ([string]::IsNullOrWhiteSpace($InstallDir)) {
if ($cudaMajor -le 0) { throw "Destination indeterminable : fournir -CudaMajor (CUDA_PATH absent) ou -InstallDir." }
$InstallDir = Join-Path $env:LOCALAPPDATA "SEQUA\sdk\cudnn\${Version}_cuda${cudaMajor}"
}
# --- Deja installe ----------------------------------------------------------
$existing = @(Find-CudnnDll $InstallDir)
if ($existing.Count -gt 0 -and -not $Force) {
Write-Step "cuDNN deja installe"
Write-Host " Dossier : $InstallDir" -ForegroundColor Green
$existing | ForEach-Object { Write-Host " $($_.FullName)" }
Write-Host "Rien a faire (utiliser -Force pour reinstaller)." -ForegroundColor Green
exit 0
}
# --- Archive, manifeste, empreinte ------------------------------------------
if ($fromArchive) {
$sha256 = $null
$size = (Get-Item -LiteralPath $archive).Length
$url = $archive
$download = $false
}
else {
Write-Step "Manifeste cuDNN $Version"
$manifest = Get-CudnnManifest $Version
$key = "cuda$cudaMajor"
$entry = $manifest.cudnn.'windows-x86_64'.$key
if (-not $entry) {
$available = ((Get-Member -InputObject $manifest.cudnn.'windows-x86_64' -MemberType NoteProperty).Name) -join ", "
throw "Pas de binaire cuDNN Windows x86_64 pour $key (disponibles : $available)."
}
$url = "$nvidiaBase/$($entry.relative_path)"
$sha256 = $entry.sha256.ToUpperInvariant()
$size = [int64]$entry.size
$archive = Join-Path $CacheDir (Split-Path -Leaf $entry.relative_path)
$download = $true
}
Write-Step "cuDNN $Version (cuda$cudaMajor)"
Write-Host " URL : $url"
if ($sha256) { Write-Host " SHA256 : $sha256" }
else { Write-Host " SHA256 : inconnu (archive locale : verification d'empreinte ignoree)" -ForegroundColor Yellow }
Write-Host (" Taille : {0:N0} octets ({1:N1} Mo)" -f $size, ($size / 1MB))
Write-Host " Archive : $archive"
Write-Host " Destination: $InstallDir"
if ($DryRun) {
if ($download) {
$partial = "$archive.part"
if (Test-Path $archive) { Write-Host " Archive deja telechargee dans le cache." }
elseif (Test-Path $partial) { Write-Host (" Telechargement partiel : {0:N0} octets." -f (Get-Item $partial).Length) }
}
Write-Host "DryRun : rien n'a ete telecharge, extrait ni installe." -ForegroundColor Yellow
exit 0
}
# --- Telechargement et verification -----------------------------------------
if ($download) {
New-Item -ItemType Directory -Force -Path $CacheDir | Out-Null
if (Test-Path $archive) {
Write-Step "Archive deja presente dans le cache"
}
else {
Assert-FreeSpace -Path $CacheDir -Needed ($size * 3)
Write-Step "Telechargement avec reprise"
Invoke-ResumableDownload $url $archive $size $maxAttempts
}
Write-Step "Verification SHA256"
$hash = Get-FileDigest -Path $archive -Algorithm SHA256
if ($hash -ne $sha256) {
Remove-Item $archive -Force
throw "SHA256 incorrect ($hash) : fichier supprime, relancer le script pour le retelecharger."
}
Write-Host " OK"
}
if ($DownloadOnly) {
Write-Host "Archive prete : $archive" -ForegroundColor Green
exit 0
}
# --- Extraction dans un dossier temporaire a cote de la destination ---------
Write-Step "Extraction"
$parent = Split-Path -Parent $InstallDir
if (-not $parent) { $parent = "." }
New-Item -ItemType Directory -Force -Path $parent | Out-Null
$temp = Join-Path $parent ((Split-Path -Leaf $InstallDir) + ".tmp-" + [guid]::NewGuid().ToString("N").Substring(0, 8))
if (Test-Path $temp) { Remove-Item -Recurse -Force $temp }
Expand-ZipArchive $archive $temp
$root = Get-ExtractedRoot $temp
Write-Host " Archive : $archive"
Write-Host " Temporaire : $temp"
Write-Host " Racine archive : $root"
# --- Licence ----------------------------------------------------------------
Write-Step "Licence cuDNN (proprietaire NVIDIA)"
$license = Get-ChildItem -Path $temp -Recurse -File -Filter "LICENSE.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $license) {
Remove-Item -Recurse -Force $temp
throw "LICENSE.txt introuvable dans l'archive extraite : installation refusee."
}
Write-Host " Licence : $($license.FullName)"
Get-Content -LiteralPath $license.FullName -TotalCount 8 | ForEach-Object { Write-Host " $_" }
Write-Host " cuDNN est proprietaire NVIDIA : son usage est regi par cette licence." -ForegroundColor Yellow
if (-not $AcceptLicense) {
$answer = Read-Host "Accepter la licence cuDNN et installer ? (O/N)"
if ($answer -notmatch '^(o|oui|y|yes)$') {
Remove-Item -Recurse -Force $temp
Write-Host "Licence refusee : aucune installation, dossier temporaire supprime." -ForegroundColor Yellow
exit 2
}
}
# --- Installation (aplatir le dossier racine de l'archive) -------------------
Write-Step "Installation"
# On ne remplace qu'un dossier vide ou une installation cuDNN precedente : jamais un
# dossier quelconque passe par erreur dans -InstallDir.
if (Test-Path -LiteralPath $InstallDir) {
$isEmpty = @(Get-ChildItem -Force -LiteralPath $InstallDir).Count -eq 0
$isCudnn = Test-Path -LiteralPath (Join-Path $InstallDir "include\cudnn.h")
if (-not $isEmpty -and -not $isCudnn) {
Remove-Item -Recurse -Force $temp
throw "$InstallDir existe, n'est pas vide et ne contient pas d'installation cuDNN : rien n'a ete supprime. Choisir un autre -InstallDir."
}
Remove-Item -Recurse -Force -LiteralPath $InstallDir
}
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Get-ChildItem -Force -LiteralPath $root | ForEach-Object { Move-Item -LiteralPath $_.FullName -Destination $InstallDir }
Remove-Item -Recurse -Force $temp
Write-Host " Installe dans : $InstallDir"
if (-not $NoEnv) {
[System.Environment]::SetEnvironmentVariable("CUDNN_PATH", $InstallDir, "User")
Write-Host " CUDNN_PATH (utilisateur) = $InstallDir"
}
# --- Verification -----------------------------------------------------------
Write-Step "Verification"
$dlls = @(Find-CudnnDll $InstallDir)
$header = Join-Path $InstallDir "include\cudnn.h"
if ($dlls.Count -gt 0) {
Write-Host " DLL cudnn64_*.dll :" -ForegroundColor Green
$dlls | ForEach-Object { Write-Host " $($_.FullName)" }
}
else {
Write-Host " Aucune cudnn64_*.dll trouvee sous $InstallDir" -ForegroundColor Red
}
if (Test-Path $header) { Write-Host " En-tete : $header" -ForegroundColor Green }
else { Write-Host " En-tete include\cudnn.h introuvable." -ForegroundColor Red }
if ($dlls.Count -eq 0 -or -not (Test-Path $header)) {
Write-Host ""
Write-Host "Installation incomplete : verifier l'archive extraite." -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "cuDNN $Version (cuda$cudaMajor) installe : $InstallDir" -ForegroundColor Green
if (-not $NoEnv) {
Write-Host "CUDNN_PATH (utilisateur) defini : ouvrir un NOUVEAU terminal pour l'utiliser."
}
else {
Write-Host "CUDNN_PATH non defini (-NoEnv)."
}
Write-Host "Ce script n'ajoute rien au PATH : pointer l'application sur bin\ (ou copier cudnn64_*.dll a cote de l'executable)."
+70
View File
@@ -0,0 +1,70 @@
# SPDX-License-Identifier: MPL-2.0
# Fonctions de telechargement communes (install-cuda.ps1, install-cudnn.ps1).
# ASCII uniquement : compatible Windows PowerShell 5.1 sans BOM.
# Ce fichier n'est pas un script autonome : il se dot-source depuis un autre script.
# Titre d'etape, meme style dans tous les scripts d'installation.
function Write-Step([string]$text) {
Write-Host "==> $text" -ForegroundColor Cyan
}
# Taille distante via une requete HEAD (curl.exe), en suivant les redirections.
function Get-RemoteSize([string]$url) {
$headers = & curl.exe -sIL --fail $url
if ($LASTEXITCODE -ne 0) { throw "Serveur injoignable : $url" }
$length = $headers | Select-String -Pattern '^Content-Length:\s*(\d+)' | Select-Object -Last 1
if (-not $length) { throw "Taille du fichier inconnue (pas de Content-Length)." }
[int64]$length.Matches[0].Groups[1].Value
}
# Empreinte generique via .NET : Get-FileHash manque dans certains Windows PowerShell 5.1
# (PSModulePath pointant vers les modules de PowerShell 7).
function Get-FileDigest([string]$path, [string]$Algorithm = "SHA256") {
switch ($Algorithm.ToUpperInvariant()) {
"SHA256" { $hasher = [System.Security.Cryptography.SHA256]::Create() }
"MD5" { $hasher = [System.Security.Cryptography.MD5]::Create() }
default { throw "Algorithme d'empreinte non gere : $Algorithm (SHA256 ou MD5)." }
}
$stream = [System.IO.File]::OpenRead($path)
try {
([System.BitConverter]::ToString($hasher.ComputeHash($stream))).Replace("-", "")
}
finally {
$hasher.Dispose()
$stream.Dispose()
}
}
# Verifie qu'il reste au moins $Needed octets libres sur le volume de $Path.
function Assert-FreeSpace([string]$Path, [int64]$Needed) {
$drive = Get-PSDrive -Name ([System.IO.Path]::GetPathRoot($Path).Substring(0, 1))
if ($drive.Free -lt $Needed) {
throw ("Espace disque insuffisant sur {0}: ({1:N1} Go libres, {2:N1} Go conseilles)." -f $drive.Name, ($drive.Free / 1GB), ($Needed / 1GB))
}
}
# Telechargement avec reprise : curl -C - repart de la taille du fichier .part.
function Invoke-ResumableDownload([string]$url, [string]$destination, [int64]$expectedSize, [int]$maxAttempts) {
$part = "$destination.part"
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
$have = if (Test-Path $part) { (Get-Item $part).Length } else { 0 }
if ($have -eq $expectedSize) { break }
if ($have -gt $expectedSize) {
Write-Host " Fichier partiel plus grand que prevu : on recommence." -ForegroundColor Yellow
Remove-Item $part -Force
$have = 0
}
$pct = [math]::Round(100.0 * $have / $expectedSize, 1)
Write-Host (" Tentative {0}/{1} : {2:N0} / {3:N0} octets ({4} %)" -f $attempt, $maxAttempts, $have, $expectedSize, $pct)
& curl.exe -L --fail -C - --retry 5 --retry-delay 5 --retry-all-errors --speed-limit 1024 --speed-time 60 -o $part $url
if ($LASTEXITCODE -ne 0) {
Write-Host " curl interrompu (code $LASTEXITCODE), reprise dans 10 s..." -ForegroundColor Yellow
Start-Sleep -Seconds 10
}
}
$final = if (Test-Path $part) { (Get-Item $part).Length } else { 0 }
if ($final -ne $expectedSize) {
throw "Telechargement incomplet apres $maxAttempts tentatives ($final / $expectedSize octets). Relancer le script pour reprendre."
}
Move-Item $part $destination -Force
}