Ajout de la partie application des changements sur le disque

Ajout de l'installation de Windows
Correction du chargement des images iso.
Correction de la définition de la taille de WinRE.
This commit is contained in:
benoit committed 2026-10-17 03:56:53 +02:00
1 parent 1c67cb2630
commit 8d5014b4aa
13 files changed
+2524 -1445

No files matched your search

+385
View File
@@ -0,0 +1,385 @@
# DeployCenter - Recherche et lecture des sources Windows
# Compatible Windows PowerShell 5.1
function Find-WindowsSources {
param(
[Parameter(Mandatory=$true)][string]$RootPath,
[AllowNull()][scriptblock]$LogAction = $null
)
$results = New-Object System.Collections.Generic.List[object]
$roots = @($RootPath -split ';' | ForEach-Object {
$candidate = $_.Trim().Trim('"')
if ($candidate -notmatch '^[A-Za-z]:\\$') { $candidate = $candidate.TrimEnd('\') }
$candidate
} | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique)
if ($roots.Count -eq 0) {
throw 'Indique au moins un chemin local et/ou un partage SMB, separes par un point-virgule.'
}
foreach ($root in $roots) {
if (-not (Test-Path -LiteralPath $root -PathType Container)) {
if ($LogAction) { & $LogAction "Source inaccessible, ignoree : $root" 'WARNING' }
continue
}
if ($LogAction) { & $LogAction "Recherche des sources Windows dans : $root" 'INFO' }
# Parcours en lecture seule. Les erreurs d'acces aux sous-dossiers sont ignorees.
# Afficher les ISO et les fichiers WIM/ESD autonomes.
# Exception : masquer boot.wim uniquement lorsqu'il fait partie d'un
# support Windows extrait dont le même dossier Sources contient install.wim/esd.
$allFiles = @(Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue)
$files = New-Object System.Collections.Generic.List[object]
foreach ($file in $allFiles) {
if ($file.Extension -ieq '.iso') {
[void]$files.Add($file)
continue
}
if ($file.Extension -ine '.wim' -and $file.Extension -ine '.esd') {
continue
}
$isBootWimInExtractedMedia = $false
if ($file.Name -ieq 'boot.wim' -and $file.Directory.Name -ieq 'Sources') {
$installWim = Join-Path $file.Directory.FullName 'install.wim'
$installEsd = Join-Path $file.Directory.FullName 'install.esd'
if ((Test-Path -LiteralPath $installWim -PathType Leaf) -or
(Test-Path -LiteralPath $installEsd -PathType Leaf)) {
$isBootWimInExtractedMedia = $true
}
}
if (-not $isBootWimInExtractedMedia) {
[void]$files.Add($file)
}
}
foreach ($file in $files) {
$kind = switch ($file.Extension.ToLowerInvariant()) {
'.iso' { 'Image ISO' }
'.wim' { 'Image WIM Windows' }
'.esd' { 'Image ESD Windows' }
}
$detail = if ($file.Name -ieq 'install.wim' -or $file.Name -ieq 'install.esd') {
"Image d’installation dans un support extrait"
} elseif ($file.Extension -ieq '.iso') {
"Support d’installation ISO"
} else {
"Image WIM/ESD autonome"
}
$results.Add([pscustomobject]@{
Type = $kind; Nom = $file.Name; Chemin = $file.FullName
Taille = ('{0:N2} Go' -f ($file.Length / 1GB)); Detail = $detail
})
}
}
if (-not ($roots | Where-Object { Test-Path -LiteralPath $_ -PathType Container })) {
throw "Aucun des chemins n'est accessible : $($roots -join '; ')`nVerifie que le lecteur local et le partage SMB sont accessibles."
}
if ($LogAction) { & $LogAction ("Detection terminee : {0} element(s) trouve(s)." -f $results.Count) 'SUCCESS' }
return @($results | Sort-Object Type, Nom, Chemin -Unique)
}
function Get-WindowsImageEditions {
param(
[Parameter(Mandatory=$true)][string]$ImagePath,
[AllowNull()][scriptblock]$LogAction = $null
)
if (-not (Test-Path -LiteralPath $ImagePath -PathType Leaf)) {
throw "Fichier image introuvable : $ImagePath"
}
$normalized = @()
$getWindowsImage = Get-Command Get-WindowsImage -ErrorAction SilentlyContinue
if ($getWindowsImage) {
try {
$images = @(Get-WindowsImage -ImagePath $ImagePath -ErrorAction Stop)
foreach ($image in $images) {
$indexValue = $null
foreach ($propertyName in @('ImageIndex', 'Index')) {
if ($image.PSObject.Properties[$propertyName] -and $image.$propertyName) {
$indexValue = [int]$image.$propertyName
break
}
}
if ($null -eq $indexValue) { continue }
$nameValue = $null
foreach ($propertyName in @('ImageName', 'Name', 'ImageDescription')) {
if ($image.PSObject.Properties[$propertyName] -and $image.$propertyName) {
$nameValue = [string]$image.$propertyName
break
}
}
$archValue = $null
foreach ($propertyName in @('Architecture', 'ImageArchitecture')) {
if ($image.PSObject.Properties[$propertyName] -and $image.$propertyName) {
$archValue = $image.$propertyName
break
}
}
$versionValue = $null
foreach ($propertyName in @('Version', 'ImageVersion')) {
if ($image.PSObject.Properties[$propertyName] -and $image.$propertyName) {
$versionValue = [string]$image.$propertyName
break
}
}
$normalized += [pscustomobject]@{
Index = $indexValue
Name = $nameValue
Description = if ($image.PSObject.Properties['ImageDescription']) { $image.ImageDescription } else { $null }
Architecture = $archValue
Version = $versionValue
Edition = if ($image.PSObject.Properties['EditionId']) { $image.EditionId } else { $null }
}
}
} catch {
if ($LogAction) { & $LogAction "Get-WindowsImage indisponible pour $ImagePath : $($_.Exception.Message)" 'WARNING' }
}
}
$dism = Join-Path $env:WINDIR 'System32\dism.exe'
if (-not (Test-Path -LiteralPath $dism)) { $dism = 'dism.exe' }
# Si le module PowerShell n'a pas renvoyé les index/noms, les récupérer avec DISM.
if ($normalized.Count -eq 0) {
$output = & $dism /English /Get-WimInfo "/WimFile:$ImagePath" 2>&1
if ($LASTEXITCODE -ne 0) {
throw "DISM n'a pas pu lire l'image : $ImagePath`n$($output -join "`n")"
}
$current = $null
foreach ($line in $output) {
$lineText = [string]$line
if ($lineText -match '^\s*Index\s*:\s*(\d+)\s*$') {
if ($null -ne $current) { $normalized += [pscustomobject]$current }
$current = @{ Index = [int]$Matches[1]; Name = ''; Description = ''; Architecture = $null; Version = $null; Edition = $null }
} elseif ($null -ne $current -and $lineText -match '^\s*Name\s*:\s*(.*?)\s*$') {
$current.Name = $Matches[1]
} elseif ($null -ne $current -and $lineText -match '^\s*Description\s*:\s*(.*?)\s*$') {
$current.Description = $Matches[1]
}
}
if ($null -ne $current) { $normalized += [pscustomobject]$current }
}
if ($normalized.Count -eq 0) {
throw "DISM n’a retourné aucune édition exploitable pour : $ImagePath"
}
# Interroger DISM pour chaque index même si Get-WindowsImage a fourni les noms :
# selon la version du module, les propriétés Architecture/Version peuvent être absentes.
foreach ($edition in $normalized) {
if ($edition.Architecture -and $edition.Version) { continue }
$detailOutput = & $dism /English /Get-WimInfo "/WimFile:$ImagePath" "/Index:$($edition.Index)" 2>&1
if ($LASTEXITCODE -ne 0) {
if ($LogAction) { & $LogAction "DISM n'a pas pu lire les metadonnees de l'index $($edition.Index) : $($detailOutput -join ' ')" 'WARNING' }
continue
}
$insideImageDetails = $false
$imageVersion = $null
$servicePackBuild = $null
foreach ($detailLine in $detailOutput) {
$detailText = [string]$detailLine
if ($detailText -match '^\s*Details for image\s*:') {
$insideImageDetails = $true
continue
}
# Ignorer la version de DISM affichée au début de sa sortie.
if (-not $insideImageDetails) { continue }
if (-not $edition.Architecture -and $detailText -match '^\s*Architecture\s*:\s*(.*?)\s*$') {
$edition.Architecture = $Matches[1]
} elseif ($detailText -match '^\s*Version\s*:\s*(.*?)\s*$') {
$imageVersion = $Matches[1]
} elseif ($detailText -match '^\s*ServicePack Build\s*:\s*(.*?)\s*$') {
$servicePackBuild = $Matches[1]
} elseif (-not $edition.Name -and $detailText -match '^\s*Name\s*:\s*(.*?)\s*$') {
$edition.Name = $Matches[1]
} elseif (-not $edition.Edition -and $detailText -match '^\s*Edition\s*:\s*(.*?)\s*$') {
$edition.Edition = $Matches[1]
}
}
if ($imageVersion) {
$edition.Version = if ($servicePackBuild -and $servicePackBuild -ne '0') {
"$imageVersion.$servicePackBuild"
} else {
$imageVersion
}
}
}
# Version globale de l'image : ne l'afficher que si les métadonnées des index
# donnent une seule version cohérente. Ne pas répéter cette valeur sur chaque ligne.
$versionValues = @($normalized | ForEach-Object {
if (-not [string]::IsNullOrWhiteSpace([string]$_.Version)) { [string]$_.Version }
} | Select-Object -Unique)
$globalVersion = $null
if ($versionValues.Count -eq 1) {
$globalVersion = $versionValues[0]
}
foreach ($edition in $normalized) {
if ($edition.Architecture -is [int] -or [string]$edition.Architecture -match '^\d+$') {
switch ([string]$edition.Architecture) {
'0' { $edition.Architecture = 'x86' }
'5' { $edition.Architecture = 'ARM' }
'9' { $edition.Architecture = 'x64' }
'12' { $edition.Architecture = 'ARM64' }
}
}
}
return [pscustomobject]@{
Editions = @($normalized)
GlobalVersion = $globalVersion
}
}
function Resolve-InstallImagePath {
param(
[Parameter(Mandatory=$true)][string]$SelectedPath,
[AllowNull()][object]$ExistingImage = $null
)
if ([IO.Path]::GetExtension($SelectedPath) -in @('.wim', '.esd')) {
return [pscustomobject]@{ SourcePath = $SelectedPath; ImagePath = $SelectedPath; MountedImage = $null }
}
if (Test-Path -LiteralPath $SelectedPath -PathType Container) {
foreach ($relative in @('sources\install.wim', 'sources\install.esd', 'Sources\install.wim', 'Sources\install.esd')) {
$candidate = Join-Path $SelectedPath $relative
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
return [pscustomobject]@{ SourcePath = $SelectedPath; ImagePath = $candidate; MountedImage = $null }
}
}
throw "Aucun fichier Sources\install.wim ou Sources\install.esd dans : $SelectedPath"
}
if ([IO.Path]::GetExtension($SelectedPath) -ieq '.iso') {
if ($ExistingImage -and $ExistingImage.SourcePath -and
[string]::Equals([IO.Path]::GetFullPath([string]$ExistingImage.SourcePath), [IO.Path]::GetFullPath($SelectedPath), [StringComparison]::OrdinalIgnoreCase) -and
$ExistingImage.MountedImage -and $ExistingImage.ImagePath) {
return $ExistingImage
}
if (-not (Get-Command Mount-DiskImage -ErrorAction SilentlyContinue)) {
throw "Le montage ISO n'est pas disponible dans cet environnement (commande Mount-DiskImage absente). Pour analyser cette source, utilise le dossier extrait ou un fichier install.wim/esd."
}
# Ne pas laisser Windows attribuer une lettre arbitraire (souvent D:) à l'ISO.
$diskImage = Mount-DiskImage -ImagePath $SelectedPath -Access ReadOnly -NoDriveLetter -PassThru -ErrorAction Stop
$mountInfo = [pscustomobject]@{
SourcePath = $SelectedPath
ImagePath = $null
MountedImage = $diskImage
MountedPartition = $null
DriveLetter = $null
}
try {
# Partir des volumes explicitement associés à cette image évite de
# confondre le lecteur CDROM virtuel et un disque physique dans WinPE.
$volumes = @(Get-Volume -DiskImage $diskImage -ErrorAction Stop)
if ($volumes.Count -eq 0) { throw 'Aucun volume ISO expose par Windows.' }
$usedLetters = @(
Get-Volume -ErrorAction SilentlyContinue | Where-Object { $_.DriveLetter } | ForEach-Object { ([string]$_.DriveLetter).ToUpperInvariant() }
Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue | ForEach-Object { ([string]$_.Name).ToUpperInvariant() }
)
$availableLetter = $null
foreach ($candidateLetter in @('Z','Y','X','W','V','U','T','S','R','Q','P','O','N','M','L','K','J','I','H','G','F','E')) {
if ($candidateLetter -notin @('C','D') -and $candidateLetter -notin $usedLetters) {
$availableLetter = $candidateLetter
break
}
}
if (-not $availableLetter) { throw 'Aucune lettre de lecteur temporaire disponible pour le montage ISO.' }
$checkedPaths = @()
foreach ($volume in $volumes) {
$volumeRoot = [string]$volume.Path
if (-not $volumeRoot.StartsWith('\\?\Volume{', [StringComparison]::OrdinalIgnoreCase) -and $volume.DriveLetter) {
$mountVolCommand = Join-Path $env:WINDIR 'System32\mountvol.exe'
if (-not (Test-Path -LiteralPath $mountVolCommand -PathType Leaf)) { $mountVolCommand = 'mountvol.exe' }
$volumeMountPath = '{0}:\' -f [string]$volume.DriveLetter
$volumeNameOutput = & $mountVolCommand $volumeMountPath '/L' 2>&1
$volumeGuidPath = @($volumeNameOutput | ForEach-Object { [string]$_ } | Where-Object { $_.StartsWith('\\?\Volume{', [StringComparison]::OrdinalIgnoreCase) }) | Select-Object -First 1
if ($volumeGuidPath) { $volumeRoot = [string]$volumeGuidPath }
}
if ($volumeRoot -and $volumeRoot.StartsWith('\\?\Volume{', [StringComparison]::OrdinalIgnoreCase)) {
if (-not $volumeRoot.EndsWith('\')) { $volumeRoot += '\' }
foreach ($relative in @('sources\install.wim', 'sources\install.esd')) {
$candidate = $volumeRoot + $relative
$checkedPaths += $candidate
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
if ([string]$volume.DriveLetter -eq 'D') {
$mountVolCommand = Join-Path $env:WINDIR 'System32\mountvol.exe'
if (-not (Test-Path -LiteralPath $mountVolCommand -PathType Leaf)) { $mountVolCommand = 'mountvol.exe' }
$removeOutput = & $mountVolCommand 'D:\' '/D' 2>&1
if ($LASTEXITCODE -ne 0) { throw "Impossible de libérer D: pour le volume ISO : $($removeOutput -join ' ')" }
}
$mountInfo.ImagePath = $candidate
return $mountInfo
}
}
}
$partition = Get-Partition -Volume $volume -ErrorAction Stop
if ($null -eq $partition) { continue }
$existingDrivePaths = @($partition.AccessPaths | Where-Object { [string]$_ -match '^[A-Za-z]:\\$' })
$reusableDrivePath = $existingDrivePaths | Where-Object { [string]$_ -notmatch '^[CDcd]:\\$' } | Select-Object -First 1
if ($reusableDrivePath) {
$driveAccessPath = [string]$reusableDrivePath
$driveLetter = $driveAccessPath.Substring(0, 1).ToUpperInvariant()
} else {
foreach ($existingDrivePath in $existingDrivePaths) {
Remove-PartitionAccessPath -InputObject $partition -AccessPath ([string]$existingDrivePath) -ErrorAction Stop | Out-Null
}
$driveLetter = $availableLetter
$driveAccessPath = '{0}:\' -f $driveLetter
Add-PartitionAccessPath -InputObject $partition -AccessPath $driveAccessPath -ErrorAction Stop
}
$mountInfo.MountedPartition = $partition
$mountInfo.DriveLetter = $driveLetter
foreach ($relative in @('sources\install.wim', 'sources\install.esd')) {
$candidate = "${driveLetter}:\$relative"
$checkedPaths += $candidate
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
$mountInfo.ImagePath = $candidate
return $mountInfo
}
}
if ($driveLetter -eq $availableLetter) {
Remove-PartitionAccessPath -InputObject $partition -AccessPath $driveAccessPath -ErrorAction SilentlyContinue | Out-Null
}
$mountInfo.MountedPartition = $null
$mountInfo.DriveLetter = $null
}
throw "Aucun fichier Sources\install.wim ou Sources\install.esd trouvé dans l'ISO. Chemins vérifiés : $($checkedPaths -join ', ')."
} catch {
try { [void](Dismount-DeployCenterInstallImage -Image $mountInfo -ErrorAction SilentlyContinue) } catch { }
throw
}
}
throw "Type de source non pris en charge pour l'analyse : $SelectedPath"
}
function Dismount-DeployCenterInstallImage {
param([AllowNull()][object]$Image)
if ($null -eq $Image -or $null -eq $Image.MountedImage) { return }
try {
if ($Image.MountedPartition -and $Image.DriveLetter) {
$driveAccessPath = '{0}:\' -f [string]$Image.DriveLetter
Remove-PartitionAccessPath -InputObject $Image.MountedPartition -AccessPath $driveAccessPath -ErrorAction Stop | Out-Null
}
} finally {
Dismount-DiskImage -InputObject $Image.MountedImage -ErrorAction Stop | Out-Null
}
}
Export-ModuleMember -Function Find-WindowsSources, Get-WindowsImageEditions, Resolve-InstallImagePath, Dismount-DeployCenterInstallImage