Ajout de l'installation de Windows Correction du chargement des images iso. Correction de la définition de la taille de WinRE.
386 lines
19 KiB
PowerShell
386 lines
19 KiB
PowerShell
# 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
|