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:
1 parent
1c67cb2630
commit
8d5014b4aa
13 files changed
+2524
-1445
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user