Ajout de la langue anglaise
Ajout d'un README.md avec le descriptif et les instructions L'ancien README.md s'appelle désormais CHANGELOG.md Activation du pavé numérique par défaut pour les installations sans assistance Nouveau design pour la fenêtre de déploiement. Ajout de paramètre pour pouvoir procéder à une installation totalement automatisée Ajout de paramètre pour copier au choix sources, drivers ou les deux sur C:\DeployVenter avant leur usage par DISM.
This commit is contained in:
1 parent
cb917c97ce
commit
2212e25d31
26 files changed
+2625
-830
No files matched your search
+422
-95
@@ -1,11 +1,11 @@
|
||||
# DeployCenter - Partitionnement et installation d'une edition Windows
|
||||
# Compatible Windows PowerShell 5.1 / WinPE.
|
||||
# DeployCenter - Windows partitioning and installation
|
||||
# Compatible with Windows PowerShell 5.1 / WinPE.
|
||||
|
||||
Import-Module (Join-Path $PSScriptRoot 'Unattend.psm1') -Force
|
||||
|
||||
function Write-DeployCenterDeploymentLog {
|
||||
param([AllowNull()][scriptblock]$LogAction, [string]$Message, [string]$Level = 'INFO')
|
||||
if ($LogAction) { & $LogAction $Message $Level }
|
||||
param([AllowNull()][scriptblock]$LogAction, [string]$Message, [string]$Level = 'INFO', [switch]$SystemOutput)
|
||||
if ($LogAction) { & $LogAction $Message $Level ([bool]$SystemOutput) }
|
||||
}
|
||||
|
||||
function Update-DeployCenterHostStorageCache {
|
||||
@@ -14,11 +14,11 @@ function Update-DeployCenterHostStorageCache {
|
||||
try {
|
||||
Update-HostStorageCache -ErrorAction Stop
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message 'Cache de stockage WinPE actualisé.' -Level 'INFO'
|
||||
-Message 'WinPE storage cache refreshed.' -Level 'INFO'
|
||||
return $true
|
||||
} catch {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Actualisation du cache de stockage WinPE indisponible : {0}" -f $_.Exception.Message) `
|
||||
-Message ("WinPE storage cache refresh is unavailable: {0}" -f $_.Exception.Message) `
|
||||
-Level 'WARNING'
|
||||
return $false
|
||||
}
|
||||
@@ -30,19 +30,19 @@ function Restart-DeployCenterWinPE {
|
||||
|
||||
$wpeutilPath = Join-Path $env:WINDIR 'System32\wpeutil.exe'
|
||||
if (-not (Test-Path -LiteralPath $wpeutilPath -PathType Leaf)) {
|
||||
throw 'wpeutil.exe est introuvable ; le redémarrage WinPE ne peut pas être demandé.'
|
||||
throw 'wpeutil.exe was not found; a WinPE restart cannot be requested.'
|
||||
}
|
||||
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message 'Redémarrage de WinPE demandé via wpeutil.' -Level 'INFO'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message 'WinPE restart requested through wpeutil.' -Level 'INFO'
|
||||
$output = @(& $wpeutilPath Reboot 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
foreach ($line in $output) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$line)) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO' -SystemOutput
|
||||
}
|
||||
}
|
||||
if ($null -ne $exitCode -and $exitCode -ne 0) {
|
||||
throw "wpeutil Reboot a renvoyé le code $exitCode."
|
||||
throw "wpeutil Reboot returned exit code $exitCode."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,13 +79,14 @@ function New-DeployCenterDismSourceAccess {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ImagePath,
|
||||
[Parameter(Mandatory=$true)][string]$TemporaryRoot,
|
||||
[AllowNull()][scriptblock]$LogAction = $null
|
||||
[AllowNull()][scriptblock]$LogAction = $null,
|
||||
[AllowNull()][scriptblock]$ProgressAction = $null
|
||||
)
|
||||
|
||||
$lease = [pscustomobject]@{ ImagePath = $ImagePath; DriveLetter = $null; VolumeRoot = $null }
|
||||
if (-not $ImagePath.StartsWith('\\?\Volume{', [StringComparison]::OrdinalIgnoreCase)) { return $lease }
|
||||
$volumeEnd = $ImagePath.IndexOf('}\', [StringComparison]::OrdinalIgnoreCase)
|
||||
if ($volumeEnd -lt 0) { throw "Chemin de volume GUID invalide pour l’image : $ImagePath" }
|
||||
if ($volumeEnd -lt 0) { throw "Invalid volume GUID path for image: $ImagePath" }
|
||||
$volumeRoot = $ImagePath.Substring(0, $volumeEnd + 2)
|
||||
$relativePath = $ImagePath.Substring($volumeEnd + 2)
|
||||
|
||||
@@ -103,7 +104,7 @@ function New-DeployCenterDismSourceAccess {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $letter) { throw 'Aucune lettre temporaire disponible pour accéder à la source Windows.' }
|
||||
if (-not $letter) { throw 'No temporary drive letter is available to access the Windows source.' }
|
||||
|
||||
$mountVolPath = Join-Path $env:WINDIR 'System32\mountvol.exe'
|
||||
if (-not (Test-Path -LiteralPath $mountVolPath -PathType Leaf)) { $mountVolPath = 'mountvol.exe' }
|
||||
@@ -111,20 +112,20 @@ function New-DeployCenterDismSourceAccess {
|
||||
$mountError = $null
|
||||
try {
|
||||
Invoke-DeployCenterNativeCommand -FilePath $mountVolPath -Arguments @(("{0}:\" -f $letter), $volumeRoot) `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
} catch {
|
||||
$mountError = $_.Exception
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $letterPath -PathType Leaf)) {
|
||||
try {
|
||||
Invoke-DeployCenterNativeCommand -FilePath $mountVolPath -Arguments @(("{0}:\" -f $letter), '/D') `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
} catch { }
|
||||
throw "La source Windows n’est pas accessible après l’affectation temporaire de ${letter}:."
|
||||
throw "The Windows source is not accessible after temporarily assigning ${letter}:."
|
||||
}
|
||||
if ($mountError) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("mountvol n’a pas fourni de code de sortie exploitable, mais la source est accessible via {0}:. L’affectation est confirmée." -f $letter) `
|
||||
-Message ("mountvol did not provide a usable exit code, but the source is accessible through {0}:. The assignment is confirmed." -f $letter) `
|
||||
-Level 'WARNING'
|
||||
}
|
||||
$lease.ImagePath = $letterPath
|
||||
@@ -137,7 +138,8 @@ function Remove-DeployCenterDismSourceAccess {
|
||||
param(
|
||||
[AllowNull()]$Lease,
|
||||
[Parameter(Mandatory=$true)][string]$TemporaryRoot,
|
||||
[AllowNull()][scriptblock]$LogAction = $null
|
||||
[AllowNull()][scriptblock]$LogAction = $null,
|
||||
[AllowNull()][scriptblock]$ProgressAction = $null
|
||||
)
|
||||
if ($null -eq $Lease -or -not $Lease.DriveLetter) { return }
|
||||
$mountVolPath = Join-Path $env:WINDIR 'System32\mountvol.exe'
|
||||
@@ -145,18 +147,18 @@ function Remove-DeployCenterDismSourceAccess {
|
||||
$releaseError = $null
|
||||
try {
|
||||
Invoke-DeployCenterNativeCommand -FilePath $mountVolPath -Arguments @(("{0}:\" -f $Lease.DriveLetter), '/D') `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
} catch {
|
||||
$releaseError = $_.Exception
|
||||
}
|
||||
[void](Update-DeployCenterHostStorageCache -LogAction $LogAction)
|
||||
if (Test-Path -LiteralPath ([string]$Lease.ImagePath) -PathType Leaf) {
|
||||
$details = if ($releaseError) { $releaseError.Message } else { 'Le fichier source reste accessible.' }
|
||||
$details = if ($releaseError) { $releaseError.Message } else { 'The source file is still accessible.' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message "Impossible de confirmer la libération de la lettre temporaire $($Lease.DriveLetter): $details" -Level 'WARNING'
|
||||
-Message "Could not confirm release of temporary drive letter $($Lease.DriveLetter): $details" -Level 'WARNING'
|
||||
} elseif ($releaseError) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("mountvol n’a pas fourni de code de sortie exploitable, mais la source n’est plus accessible via {0}:. La libération est confirmée." -f $Lease.DriveLetter) `
|
||||
-Message ("mountvol did not provide a usable exit code, but the source is no longer accessible through {0}:. The drive letter has been released." -f $Lease.DriveLetter) `
|
||||
-Level 'WARNING'
|
||||
}
|
||||
}
|
||||
@@ -183,6 +185,155 @@ function Get-DeployCenterNativeProgressPercent {
|
||||
return [int][math]::Max(0, [math]::Min(100, [math]::Floor($value)))
|
||||
}
|
||||
|
||||
function Test-DeployCenterRobocopyCopy {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SourceDirectory,
|
||||
[Parameter(Mandatory=$true)][string]$DestinationDirectory,
|
||||
[Parameter(Mandatory=$true)][string[]]$CopyArguments
|
||||
)
|
||||
|
||||
$sourceRoot = [IO.Path]::GetFullPath($SourceDirectory).TrimEnd('\')
|
||||
$fileFilters = @($CopyArguments | Where-Object { -not ([string]$_).StartsWith('/') })
|
||||
$sourceFiles = @(Get-ChildItem -LiteralPath $SourceDirectory -File -Recurse -ErrorAction Stop)
|
||||
if ($fileFilters.Count -gt 0) {
|
||||
$sourceFiles = @($sourceFiles | Where-Object {
|
||||
$fileName = $_.Name
|
||||
@($fileFilters | Where-Object { $fileName -like [string]$_ }).Count -gt 0
|
||||
})
|
||||
}
|
||||
if ($sourceFiles.Count -eq 0) { return $false }
|
||||
|
||||
foreach ($sourceFile in $sourceFiles) {
|
||||
$relativePath = $sourceFile.FullName.Substring($sourceRoot.Length).TrimStart('\')
|
||||
$destinationPath = Join-Path $DestinationDirectory $relativePath
|
||||
if (-not (Test-Path -LiteralPath $destinationPath -PathType Leaf)) { return $false }
|
||||
$destinationFile = Get-Item -LiteralPath $destinationPath -ErrorAction Stop
|
||||
if ([long]$sourceFile.Length -ne [long]$destinationFile.Length) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function ConvertTo-DeployCenterPowerShellLiteral {
|
||||
param([AllowNull()][string]$Value)
|
||||
if ($null -eq $Value) { $Value = '' }
|
||||
return "'" + $Value.Replace("'", "''") + "'"
|
||||
}
|
||||
|
||||
function Invoke-DeployCenterRobocopy {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SourceDirectory,
|
||||
[Parameter(Mandatory=$true)][string]$DestinationDirectory,
|
||||
[Parameter(Mandatory=$true)][string[]]$CopyArguments,
|
||||
[Parameter(Mandatory=$true)][string]$TemporaryRoot,
|
||||
[AllowNull()][scriptblock]$LogAction = $null,
|
||||
[AllowNull()][scriptblock]$ProgressAction = $null
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $TemporaryRoot -PathType Container)) {
|
||||
New-Item -Path $TemporaryRoot -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
$robocopyPath = Join-Path $env:WINDIR 'System32\robocopy.exe'
|
||||
if (-not (Test-Path -LiteralPath $robocopyPath -PathType Leaf)) {
|
||||
$robocopyCommand = Get-Command 'robocopy.exe' -ErrorAction SilentlyContinue
|
||||
if ($null -eq $robocopyCommand) { throw 'Robocopy.exe was not found in WinPE.' }
|
||||
$robocopyPath = [string]$robocopyCommand.Path
|
||||
}
|
||||
|
||||
$id = [guid]::NewGuid().ToString('N')
|
||||
$stdoutPath = Join-Path $TemporaryRoot ("robocopy_{0}.out" -f $id)
|
||||
$stderrPath = Join-Path $TemporaryRoot ("robocopy_{0}.err" -f $id)
|
||||
$runnerPath = Join-Path $TemporaryRoot ("robocopy_{0}.ps1" -f $id)
|
||||
$exitCodePath = Join-Path $TemporaryRoot ("robocopy_{0}.exitcode" -f $id)
|
||||
$arguments = @($SourceDirectory, $DestinationDirectory) + @($CopyArguments)
|
||||
$argumentLine = ($arguments | ForEach-Object {
|
||||
ConvertTo-DeployCenterNativeArgument -Value ([string]$_)
|
||||
}) -join ' '
|
||||
$powerShellPath = Join-Path $PSHOME 'powershell.exe'
|
||||
if (-not (Test-Path -LiteralPath $powerShellPath -PathType Leaf)) {
|
||||
$powerShellCommand = Get-Command 'powershell.exe' -ErrorAction SilentlyContinue
|
||||
if ($null -eq $powerShellCommand) { throw 'Windows PowerShell was not found to capture the Robocopy exit code.' }
|
||||
$powerShellPath = [string]$powerShellCommand.Path
|
||||
}
|
||||
|
||||
try {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Native command: {0} {1}" -f $robocopyPath, $argumentLine)
|
||||
$runnerCommand = '& ' + (ConvertTo-DeployCenterPowerShellLiteral -Value $robocopyPath)
|
||||
foreach ($argument in $arguments) {
|
||||
$runnerCommand += ' ' + (ConvertTo-DeployCenterPowerShellLiteral -Value ([string]$argument))
|
||||
}
|
||||
$runnerLines = @(
|
||||
$runnerCommand,
|
||||
'$robocopyExitCode = $LASTEXITCODE',
|
||||
('[System.IO.File]::WriteAllText(' + (ConvertTo-DeployCenterPowerShellLiteral -Value $exitCodePath) + ', [string]$robocopyExitCode, [System.Text.Encoding]::ASCII)')
|
||||
)
|
||||
[System.IO.File]::WriteAllLines($runnerPath, [string[]]$runnerLines, (New-Object System.Text.UTF8Encoding($true)))
|
||||
$runnerArguments = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $runnerPath)
|
||||
$runnerArgumentLine = ($runnerArguments | ForEach-Object {
|
||||
ConvertTo-DeployCenterNativeArgument -Value ([string]$_)
|
||||
}) -join ' '
|
||||
$process = Start-Process -FilePath $powerShellPath -ArgumentList $runnerArgumentLine `
|
||||
-PassThru -WindowStyle Hidden -RedirectStandardOutput $stdoutPath `
|
||||
-RedirectStandardError $stderrPath -ErrorAction Stop
|
||||
while (-not $process.WaitForExit(250)) {
|
||||
if ($ProgressAction -and (Test-Path -LiteralPath $stdoutPath -PathType Leaf)) {
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
$copyPercent = Get-DeployCenterNativeProgressPercent -OutputPath $stdoutPath
|
||||
if ($null -ne $copyPercent) { & $ProgressAction $null $copyPercent }
|
||||
}
|
||||
}
|
||||
$process.WaitForExit()
|
||||
$exitCode = $null
|
||||
if (Test-Path -LiteralPath $exitCodePath -PathType Leaf) {
|
||||
$exitCodeText = [System.IO.File]::ReadAllText($exitCodePath).Trim()
|
||||
$parsedExitCode = 0
|
||||
if ([int]::TryParse($exitCodeText, [ref]$parsedExitCode)) { $exitCode = $parsedExitCode }
|
||||
}
|
||||
|
||||
$outputEncoding = [System.Console]::OutputEncoding
|
||||
if ($null -eq $outputEncoding) { $outputEncoding = [System.Text.Encoding]::Default }
|
||||
foreach ($outputPath in @($stdoutPath, $stderrPath)) {
|
||||
if (Test-Path -LiteralPath $outputPath -PathType Leaf) {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($outputPath, $outputEncoding)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($line)) {
|
||||
if ($line -match '^\s*\d{1,3}(?:[.,]\d+)?\s*%\s*$') { continue }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO' -SystemOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ProgressAction -and (Test-Path -LiteralPath $stdoutPath -PathType Leaf)) {
|
||||
$copyPercent = Get-DeployCenterNativeProgressPercent -OutputPath $stdoutPath
|
||||
if ($null -ne $copyPercent) { & $ProgressAction $null $copyPercent }
|
||||
}
|
||||
# Robocopy uses codes 0 to 7 for non-failure results; 8 and above indicate an error.
|
||||
if ($null -eq $exitCode) {
|
||||
if (-not (Test-DeployCenterRobocopyCopy -SourceDirectory $SourceDirectory `
|
||||
-DestinationDirectory $DestinationDirectory -CopyArguments $CopyArguments)) {
|
||||
throw 'Robocopy did not report an exit code and file verification failed.'
|
||||
}
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message 'Robocopy did not report an exit code; file verification succeeded.' `
|
||||
-Level 'WARNING'
|
||||
return $null
|
||||
}
|
||||
if ([int]$exitCode -ge 8) {
|
||||
throw ("Robocopy exited with code {0}." -f $exitCode)
|
||||
}
|
||||
return [int]$exitCode
|
||||
} finally {
|
||||
foreach ($outputPath in @($stdoutPath, $stderrPath)) {
|
||||
if (Test-Path -LiteralPath $outputPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $outputPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
foreach ($temporaryPath in @($runnerPath, $exitCodePath)) {
|
||||
if (Test-Path -LiteralPath $temporaryPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-DeployCenterNativeCommand {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$FilePath,
|
||||
@@ -199,6 +350,8 @@ function Invoke-DeployCenterNativeCommand {
|
||||
$stdoutPath = Join-Path $TemporaryRoot ("native_{0}.out" -f $id)
|
||||
$stderrPath = Join-Path $TemporaryRoot ("native_{0}.err" -f $id)
|
||||
$dismLogPath = Join-Path $TemporaryRoot ("dism_{0}.log" -f $id)
|
||||
$runnerPath = Join-Path $TemporaryRoot ("native_{0}.ps1" -f $id)
|
||||
$exitCodePath = Join-Path $TemporaryRoot ("native_{0}.exitcode" -f $id)
|
||||
$isDism = [IO.Path]::GetFileName($FilePath) -ieq 'dism.exe'
|
||||
$isImageApply = $isDism -and @($Arguments | Where-Object { [string]$_ -ieq '/Apply-Image' }).Count -gt 0
|
||||
if ($isDism) {
|
||||
@@ -209,7 +362,7 @@ function Invoke-DeployCenterNativeCommand {
|
||||
}) -join ' '
|
||||
|
||||
try {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Commande native : {0} {1}" -f $FilePath, $argumentLine)
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Native command: {0} {1}" -f $FilePath, $argumentLine)
|
||||
$nativeOutputLines = New-Object System.Collections.Generic.List[string]
|
||||
if ($isImageApply) {
|
||||
$process = Start-Process -FilePath $FilePath -ArgumentList $argumentLine `
|
||||
@@ -234,7 +387,7 @@ function Invoke-DeployCenterNativeCommand {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($outputPath, $nativeOutputEncoding)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($line)) {
|
||||
[void]$nativeOutputLines.Add([string]$line)
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO' -SystemOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,17 +398,55 @@ function Invoke-DeployCenterNativeCommand {
|
||||
}
|
||||
if ($null -eq $exitCode -and @($nativeOutputLines | Where-Object { $_ -match '(?i)completed successfully|a r.ussi' }).Count -gt 0) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message 'DISM confirme que Windows a été appliqué, mais le code de sortie est absent dans WinPE.' `
|
||||
-Message 'DISM confirms Windows was applied, but the exit code is unavailable in WinPE.' `
|
||||
-Level 'WARNING'
|
||||
return
|
||||
}
|
||||
} else {
|
||||
$commandOutput = @(& $FilePath @Arguments 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
foreach ($line in $commandOutput) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$line)) {
|
||||
[void]$nativeOutputLines.Add([string]$line)
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO'
|
||||
$powerShellPath = Join-Path $PSHOME 'powershell.exe'
|
||||
if (-not (Test-Path -LiteralPath $powerShellPath -PathType Leaf)) {
|
||||
$powerShellCommand = Get-Command 'powershell.exe' -ErrorAction SilentlyContinue
|
||||
if ($null -eq $powerShellCommand) { throw 'Windows PowerShell was not found to capture the native command exit code.' }
|
||||
$powerShellPath = [string]$powerShellCommand.Path
|
||||
}
|
||||
$runnerCommand = '& ' + (ConvertTo-DeployCenterPowerShellLiteral -Value $FilePath)
|
||||
foreach ($argument in $Arguments) {
|
||||
$runnerCommand += ' ' + (ConvertTo-DeployCenterPowerShellLiteral -Value ([string]$argument))
|
||||
}
|
||||
$runnerLines = @(
|
||||
$runnerCommand,
|
||||
'$nativeCommandExitCode = $LASTEXITCODE',
|
||||
('[System.IO.File]::WriteAllText(' + (ConvertTo-DeployCenterPowerShellLiteral -Value $exitCodePath) + ', [string]$nativeCommandExitCode, [System.Text.Encoding]::ASCII)')
|
||||
)
|
||||
[System.IO.File]::WriteAllLines($runnerPath, [string[]]$runnerLines, (New-Object System.Text.UTF8Encoding($true)))
|
||||
$runnerArguments = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $runnerPath)
|
||||
$runnerArgumentLine = ($runnerArguments | ForEach-Object {
|
||||
ConvertTo-DeployCenterNativeArgument -Value ([string]$_)
|
||||
}) -join ' '
|
||||
$process = Start-Process -FilePath $powerShellPath -ArgumentList $runnerArgumentLine `
|
||||
-PassThru -WindowStyle Hidden -RedirectStandardOutput $stdoutPath `
|
||||
-RedirectStandardError $stderrPath -ErrorAction Stop
|
||||
while (-not $process.WaitForExit(250)) {
|
||||
if ($ProgressAction) { [System.Windows.Forms.Application]::DoEvents() }
|
||||
}
|
||||
$process.WaitForExit()
|
||||
$exitCode = $null
|
||||
if (Test-Path -LiteralPath $exitCodePath -PathType Leaf) {
|
||||
$exitCodeText = [System.IO.File]::ReadAllText($exitCodePath).Trim()
|
||||
$parsedExitCode = 0
|
||||
if ([int]::TryParse($exitCodeText, [ref]$parsedExitCode)) { $exitCode = $parsedExitCode }
|
||||
}
|
||||
if ($null -eq $exitCode) { throw ("Could not capture the exit code from {0}." -f [IO.Path]::GetFileName($FilePath)) }
|
||||
$nativeOutputEncoding = [System.Console]::OutputEncoding
|
||||
if ($null -eq $nativeOutputEncoding) { $nativeOutputEncoding = [System.Text.Encoding]::Default }
|
||||
foreach ($outputPath in @($stdoutPath, $stderrPath)) {
|
||||
if (Test-Path -LiteralPath $outputPath -PathType Leaf) {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($outputPath, $nativeOutputEncoding)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$line)) {
|
||||
[void]$nativeOutputLines.Add([string]$line)
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO' -SystemOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,11 +455,11 @@ function Invoke-DeployCenterNativeCommand {
|
||||
if ($isDism -and (Test-Path -LiteralPath $dismLogPath -PathType Leaf)) {
|
||||
$diagnostic = @(Get-Content -LiteralPath $dismLogPath -Tail 30 -ErrorAction SilentlyContinue | ForEach-Object { [string]$_ })
|
||||
foreach ($line in $diagnostic) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("DISM : {0}" -f $line) -Level 'ERROR'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'ERROR' -SystemOutput
|
||||
}
|
||||
}
|
||||
$diagnosticMessage = $diagnostic -join ' | '
|
||||
throw ("La commande {0} s'est terminée avec le code {1}. {2}" -f $FilePath, $exitCode, $diagnosticMessage)
|
||||
throw ("Command {0} exited with code {1}. {2}" -f $FilePath, $exitCode, $diagnosticMessage)
|
||||
}
|
||||
} finally {
|
||||
foreach ($outputPath in @($stdoutPath, $stderrPath)) {
|
||||
@@ -279,6 +470,11 @@ function Invoke-DeployCenterNativeCommand {
|
||||
if (Test-Path -LiteralPath $dismLogPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $dismLogPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
foreach ($temporaryPath in @($runnerPath, $exitCodePath)) {
|
||||
if (Test-Path -LiteralPath $temporaryPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,11 +482,12 @@ function Get-DeployCenterOfflineWindowsBootGuid {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$BcdStorePath,
|
||||
[Parameter(Mandatory=$true)][string]$WindowsDriveLetter,
|
||||
[Parameter(Mandatory=$true)][string]$IdentifierLabel,
|
||||
[AllowNull()][scriptblock]$LogAction = $null
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $BcdStorePath -PathType Leaf)) {
|
||||
throw "Magasin BCD introuvable après la création des fichiers de démarrage : $BcdStorePath"
|
||||
throw "BCD store was not found after boot files were created: $BcdStorePath"
|
||||
}
|
||||
$bcdeditPath = Join-Path $env:WINDIR 'System32\bcdedit.exe'
|
||||
if (-not (Test-Path -LiteralPath $bcdeditPath -PathType Leaf)) { $bcdeditPath = 'bcdedit.exe' }
|
||||
@@ -298,19 +495,20 @@ function Get-DeployCenterOfflineWindowsBootGuid {
|
||||
$exitCode = $LASTEXITCODE
|
||||
foreach ($line in $output) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$line)) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ([string]$line) -Level 'INFO' -SystemOutput
|
||||
}
|
||||
}
|
||||
if ($null -eq $exitCode -or [int]$exitCode -ne 0) {
|
||||
throw "bcdedit n’a pas pu lire le magasin BCD $BcdStorePath (code $exitCode)."
|
||||
throw "bcdedit could not read the BCD store $BcdStorePath (exit code $exitCode)."
|
||||
}
|
||||
|
||||
$targetLetter = $WindowsDriveLetter.TrimEnd(':').ToUpperInvariant()
|
||||
$entries = New-Object System.Collections.Generic.List[object]
|
||||
$currentEntry = $null
|
||||
$identifierPattern = '^\s*' + [regex]::Escape($IdentifierLabel) + '\s+(?<guid>\{[0-9A-Fa-f-]{36}\})\s*$'
|
||||
foreach ($lineObject in $output) {
|
||||
$line = [string]$lineObject
|
||||
if ($line -match '^\s*(?:identifier|identificateur)\s+(?<guid>\{[0-9A-Fa-f-]{36}\})\s*$') {
|
||||
if ($line -match $identifierPattern) {
|
||||
if ($null -ne $currentEntry) { $entries.Add($currentEntry) }
|
||||
$currentEntry = [pscustomobject]@{
|
||||
Guid = [string]$Matches['guid']
|
||||
@@ -332,7 +530,7 @@ function Get-DeployCenterOfflineWindowsBootGuid {
|
||||
$_.Device -eq $targetLetter -and $_.OsDevice -eq $targetLetter
|
||||
})
|
||||
if ($matchingEntries.Count -ne 1) {
|
||||
throw "Impossible d’identifier une entrée BCD unique pour Windows sur ${targetLetter}: (entrées correspondantes : $($matchingEntries.Count))."
|
||||
throw "Could not identify a unique BCD entry for Windows on ${targetLetter}: (matching entries: $($matchingEntries.Count))."
|
||||
}
|
||||
return [string]$matchingEntries[0].Guid
|
||||
}
|
||||
@@ -370,9 +568,9 @@ function Get-DeployCenterTemporaryDriveLetters {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $selected) { throw "Aucune lettre de lecteur libre pour la partition '$($partition.Name)'." }
|
||||
if (-not $selected) { throw "No free drive letter is available for partition '$($partition.Name)'." }
|
||||
}
|
||||
if ($selected -in $lettersByRole.Values) { throw "La lettre ${selected}: est affectée à plusieurs partitions du profil." }
|
||||
if ($selected -in $lettersByRole.Values) { throw "The drive letter ${selected}: is assigned to multiple partitions in the profile." }
|
||||
$lettersByRole[[string]$partition.Role] = $selected
|
||||
$occupied[$selected] = $true
|
||||
}
|
||||
@@ -387,7 +585,7 @@ function New-DeployCenterPartitionPlan {
|
||||
)
|
||||
|
||||
if ([string]$Profile.PartitionStyle -ne 'GPT' -or [string]$Profile.Firmware -ne 'UEFI') {
|
||||
throw "Le profil '$($Profile.Name)' n’est pas pris en charge par le moteur actuel (UEFI/GPT requis)."
|
||||
throw "The profile '$($Profile.Name)' is not supported by the current engine (UEFI/GPT required)."
|
||||
}
|
||||
$totalMB = [long][math]::Floor(([double]$DiskInfo.SizeBytes / 1MB))
|
||||
$availableMB = $totalMB - 2
|
||||
@@ -401,26 +599,26 @@ function New-DeployCenterPartitionPlan {
|
||||
'ConfiguredMB' { $sizeMB = [long]$partition.Size.Value }
|
||||
'UserInputGB' {
|
||||
if ($null -eq $SizingState -or [string]$SizingState.ProfileId -ne [string]$Profile.Id) {
|
||||
throw "Le profil '$($Profile.Name)' nécessite une taille utilisateur, qui n’a pas été validée."
|
||||
throw "Profile '$($Profile.Name)' requires a user-defined size, which has not been validated."
|
||||
}
|
||||
$role = [string]$partition.Role
|
||||
if (-not $SizingState.Inputs.Contains($role)) { throw "La taille de la partition '$role' est absente." }
|
||||
if (-not $SizingState.Inputs.Contains($role)) { throw "The size for partition '$role' is missing." }
|
||||
$sizeMB = [long][math]::Round(([double]$SizingState.Inputs[$role] * 1024), 0)
|
||||
if ($sizeMB -lt ([long]$partition.Size.MinimumGB * 1024)) { throw "La partition '$role' est sous sa taille minimale." }
|
||||
if ($sizeMB -lt ([long]$partition.Size.MinimumGB * 1024)) { throw "Partition '$role' is below its minimum size." }
|
||||
}
|
||||
{ $_ -in @('Remaining','RemainderAfterInputs') } {
|
||||
$sizeMB = $availableMB - $usedMB
|
||||
if ($sizeMB -lt 1) { throw "Le disque ne dispose plus d’espace pour la partition '$($partition.Name)'." }
|
||||
if ($sizeMB -lt 1) { throw "The disk no longer has enough space for partition '$($partition.Name)'." }
|
||||
if ($policy -eq 'RemainderAfterInputs' -and $sizeMB -lt ([long]$partition.Size.MinimumGB * 1024)) {
|
||||
throw "L’espace restant pour '$($partition.Name)' est inférieur au minimum du profil."
|
||||
throw "The remaining space for '$($partition.Name)' is below the profile minimum."
|
||||
}
|
||||
}
|
||||
default { throw "Politique de taille non prise en charge lors du partitionnement : $policy" }
|
||||
default { throw "Unsupported partition size policy: $policy" }
|
||||
}
|
||||
if ($null -ne $sizeMB) { $usedMB += [long]$sizeMB }
|
||||
$plan.Add([pscustomobject]@{ Definition = $partition; SizeMB = $sizeMB })
|
||||
}
|
||||
if ($usedMB -gt $availableMB) { throw "La taille demandée dépasse la capacité du disque sélectionné." }
|
||||
if ($usedMB -gt $availableMB) { throw "The requested size exceeds the selected disk capacity." }
|
||||
return $plan.ToArray()
|
||||
}
|
||||
|
||||
@@ -432,38 +630,49 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
[AllowNull()]$SizingState,
|
||||
[Parameter(Mandatory=$true)]$Edition,
|
||||
[AllowNull()][string]$HostName = $null,
|
||||
[Parameter(Mandatory=$true)][string]$IdentifierLabel,
|
||||
[AllowNull()][System.Collections.IDictionary]$UnattendProfile = $null,
|
||||
[AllowNull()][System.Collections.IDictionary]$UnattendConfiguration = $null,
|
||||
[Parameter(Mandatory=$true)][string]$Firmware,
|
||||
[Parameter(Mandatory=$true)][string]$TemporaryRoot,
|
||||
[AllowNull()][string]$DriverPath = $null,
|
||||
[int]$DriverInfCount = 0,
|
||||
[ValidateSet('Sources', 'Drivers')]
|
||||
[string[]]$CopyLocally = @(),
|
||||
[AllowNull()][scriptblock]$LogAction = $null,
|
||||
[AllowNull()][scriptblock]$ProgressAction = $null
|
||||
)
|
||||
|
||||
if ($null -ne $UnattendProfile) {
|
||||
if ($null -eq $UnattendConfiguration) { throw 'La configuration DeployCenter.ini est absente pour le profil Unattend sélectionné.' }
|
||||
if ($null -eq $UnattendConfiguration) { throw 'DeployCenter.ini configuration is missing for the selected Unattend profile.' }
|
||||
Test-DeployCenterUnattendConfiguration -Profile $UnattendProfile -Configuration $UnattendConfiguration -HostName ([string]$HostName)
|
||||
}
|
||||
$winreRecommendationWarnings = @()
|
||||
if ([string]$Firmware -ne [string]$Profile.Firmware) {
|
||||
throw ("Le profil {0} exige le firmware {1}, détecté : {2}." -f $Profile.Name, $Profile.Firmware, $Firmware)
|
||||
throw ("The profile {0} requires firmware {1}, detected: {2}." -f $Profile.Name, $Profile.Firmware, $Firmware)
|
||||
}
|
||||
foreach ($requiredRole in @('System','Reserved','Recovery','Windows')) {
|
||||
$roleCount = @($Profile.Partitions | Where-Object { [string]$_.Role -eq $requiredRole }).Count
|
||||
if ($roleCount -ne 1) { throw "Le profil doit définir exactement une partition de rôle '$requiredRole'. Le disque n’a pas été modifié." }
|
||||
if ($roleCount -ne 1) { throw "The profile must define exactly one partition with the '$requiredRole' role. The disk was not modified." }
|
||||
}
|
||||
$recoveryDefinitions = @($Profile.Partitions | Where-Object { [string]$_.Role -eq 'Recovery' })
|
||||
if ($recoveryDefinitions.Count -ne 1 -or [string]$recoveryDefinitions[0].Size.Policy -ne 'ConfiguredMB') {
|
||||
throw "Le profil doit définir une taille configurée pour la partition Recovery. Le disque n’a pas été modifié."
|
||||
throw "The profile must define a configured size for the Recovery partition. The disk was not modified."
|
||||
}
|
||||
$winrePartitionSizeMB = [long]$recoveryDefinitions[0].Size.Value
|
||||
if ($winrePartitionSizeMB -le 0) {
|
||||
throw "La taille configurée de la partition WinRE doit être strictement positive. Le disque n’a pas été modifié."
|
||||
throw "The configured WinRE partition size must be greater than zero. The disk was not modified."
|
||||
}
|
||||
if (-not $Edition.ImagePath -or -not (Test-Path -LiteralPath ([string]$Edition.ImagePath) -PathType Leaf)) {
|
||||
throw "Le chemin de l’image Windows sélectionnée est introuvable."
|
||||
throw "The selected Windows image path was not found."
|
||||
}
|
||||
if (-not $Edition.Index -or [int]$Edition.Index -lt 1) { throw "The Windows edition index is invalid." }
|
||||
if (-not [string]::IsNullOrWhiteSpace($DriverPath)) {
|
||||
if (-not (Test-Path -LiteralPath $DriverPath -PathType Container)) { throw "The selected driver folder was not found: $DriverPath" }
|
||||
if ($DriverInfCount -le 0 -or @(Get-ChildItem -LiteralPath $DriverPath -Filter '*.inf' -File -Recurse -ErrorAction SilentlyContinue).Count -eq 0) {
|
||||
throw "The selected driver folder no longer contains an INF file: $DriverPath"
|
||||
}
|
||||
}
|
||||
if (-not $Edition.Index -or [int]$Edition.Index -lt 1) { throw "L’index de l’édition Windows est invalide." }
|
||||
|
||||
$wallpaperSourcePath = $null
|
||||
if ($null -ne $UnattendProfile -and $null -ne $UnattendConfiguration) {
|
||||
@@ -476,16 +685,16 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
$wallpaperSourcePath = [System.IO.Path]::GetFullPath((Join-Path $deployRoot $configuredWallpaperPath))
|
||||
}
|
||||
if ([System.IO.Path]::GetExtension($wallpaperSourcePath) -notin @('.png', '.jpg', '.bmp')) {
|
||||
throw 'WallpaperPath doit désigner un fichier .png, .jpg ou .bmp.'
|
||||
throw 'WallpaperPath must point to a .png, .jpg, or .bmp file.'
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $wallpaperSourcePath -PathType Leaf)) {
|
||||
throw "Image de fond introuvable : $wallpaperSourcePath"
|
||||
throw "Wallpaper image not found: $wallpaperSourcePath"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$disk = Get-Disk -Number ([int]$DiskInfo.Index) -ErrorAction Stop
|
||||
if ($disk.IsBoot -or $disk.IsSystem) { throw 'Le disque sélectionné héberge le système WinPE et ne peut pas être effacé.' }
|
||||
if ($disk.IsBoot -or $disk.IsSystem) { throw 'The selected disk hosts WinPE and cannot be erased.' }
|
||||
if ($disk.IsOffline) { [void](Set-Disk -Number $disk.Number -IsOffline $false -ErrorAction Stop) }
|
||||
if ($disk.IsReadOnly) { [void](Set-Disk -Number $disk.Number -IsReadOnly $false -ErrorAction Stop) }
|
||||
|
||||
@@ -493,7 +702,8 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
New-Item -Path $TemporaryRoot -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
$sourceLease = New-DeployCenterDismSourceAccess -ImagePath ([string]$Edition.ImagePath) `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
$windowsDeployCenterPath = $null
|
||||
try {
|
||||
$deploymentImagePath = [string]$sourceLease.ImagePath
|
||||
$partitionSizingDisk = [pscustomobject]@{ Index = [int]$disk.Number; SizeBytes = [long]$disk.Size }
|
||||
@@ -524,7 +734,7 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
}
|
||||
if ($role -eq 'Reserved') { continue }
|
||||
$label = [string]$definition.Name
|
||||
if ($label -notmatch '^[A-Za-z0-9_-]{1,32}$') { throw "Nom de partition invalide dans le profil : $label" }
|
||||
if ($label -notmatch '^[A-Za-z0-9_-]{1,32}$') { throw "Invalid partition name in profile: $label" }
|
||||
if ([string]$definition.FileSystem) {
|
||||
$scriptLines.Add("format fs=$($definition.FileSystem) quick label=$label")
|
||||
}
|
||||
@@ -544,8 +754,8 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
$unattendPath = Join-Path $TemporaryRoot ("unattend_{0}.xml" -f ([guid]::NewGuid().ToString('N')))
|
||||
try {
|
||||
[System.IO.File]::WriteAllLines($cleanPath, $cleanLines, [System.Text.Encoding]::ASCII)
|
||||
if ($ProgressAction) { & $ProgressAction 'Effacement et partitionnement du disque…' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Partitionnement du disque {0} avec le profil '{1}' ; partition WinRE configurée : {2} Mo." -f $disk.Number, $Profile.Name, $winrePartitionSizeMB)
|
||||
if ($ProgressAction) { & $ProgressAction 'Erasing and partitioning the disk…' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Partitioning disk {0} with profile '{1}' ; configured WinRE partition: {2} MB." -f $disk.Number, $Profile.Name, $winrePartitionSizeMB)
|
||||
Invoke-DeployCenterNativeCommand -FilePath 'diskpart.exe' -Arguments @('/s', $cleanPath) `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
[void](Update-DeployCenterHostStorageCache -LogAction $LogAction)
|
||||
@@ -556,7 +766,7 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
[void](Update-DeployCenterHostStorageCache -LogAction $LogAction)
|
||||
$createdPartitions = @(Get-Partition -DiskNumber ([int]$disk.Number) -ErrorAction Stop)
|
||||
if ($createdPartitions.Count -ne $partitionPlan.Count) {
|
||||
throw "DiskPart a créé $($createdPartitions.Count) partition(s), alors que le profil en prévoit $($partitionPlan.Count). L’installation est interrompue."
|
||||
throw "DiskPart created $($createdPartitions.Count) partition(s), but the profile specifies $($partitionPlan.Count). Installation has been stopped."
|
||||
}
|
||||
$orderedCreatedPartitions = @($createdPartitions | Sort-Object -Property PartitionNumber)
|
||||
$recoveryPlanIndex = -1
|
||||
@@ -566,12 +776,12 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($recoveryPlanIndex -lt 0) { throw 'La partition Recovery est absente du plan exécuté.' }
|
||||
if ($recoveryPlanIndex -lt 0) { throw 'The Recovery partition is missing from the executed plan.' }
|
||||
$recoveryPartition = $orderedCreatedPartitions[$recoveryPlanIndex]
|
||||
$recoveryLetter = [string]$driveLetters['Recovery']
|
||||
$recoveryDriveRoot = '{0}:\' -f $recoveryLetter
|
||||
if (-not (Test-Path -LiteralPath $recoveryDriveRoot -PathType Container)) {
|
||||
throw "La lettre temporaire ${recoveryLetter}: de WinRE n’est pas accessible après le partitionnement."
|
||||
throw "The temporary drive letter ${recoveryLetter}: de WinRE is not accessible after partitioning."
|
||||
}
|
||||
$partitionsByLetter = @{}
|
||||
for ($index = 0; $index -lt $partitionPlan.Count; $index++) {
|
||||
@@ -580,21 +790,22 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
$letter = [string]$driveLetters[$role]
|
||||
$driveRoot = '{0}:\' -f $letter
|
||||
if (-not (Test-Path -LiteralPath $driveRoot -PathType Container)) {
|
||||
throw "La lettre temporaire ${letter}: n’est pas accessible après le partitionnement. L’installation est interrompue."
|
||||
throw "The temporary drive letter ${letter}: is not accessible after partitioning. Installation has been stopped."
|
||||
}
|
||||
# WinPE peut ne pas exposer les partitions EFI/Recovery dans les
|
||||
# requêtes Get-Partition par lettre. Leur ordre sur le disque suit
|
||||
# directement celui des définitions exécutées par DiskPart.
|
||||
# WinPE may not expose EFI/Recovery partitions in
|
||||
# Get-Partition queries by drive letter. Their disk order follows
|
||||
# the order of the definitions executed by DiskPart.
|
||||
$partitionsByLetter[$letter] = $orderedCreatedPartitions[$index]
|
||||
}
|
||||
|
||||
$windowsLetter = [string]$driveLetters['Windows']
|
||||
$windowsPath = "${windowsLetter}:\"
|
||||
$windowsRoot = Join-Path $windowsPath 'Windows'
|
||||
$windowsDeployCenterPath = Join-Path $windowsPath 'DeployCenter'
|
||||
$recoveryPath = "${recoveryLetter}:\Recovery\WindowsRE"
|
||||
$efiLetter = [string]$driveLetters['System']
|
||||
if (-not $windowsLetter -or -not $recoveryLetter -or -not $efiLetter) {
|
||||
throw "Le profil doit définir les rôles System, Windows et Recovery pour permettre l’installation UEFI avec WinRE."
|
||||
throw "The profile must define the System, Windows, and Recovery roles for UEFI installation with WinRE."
|
||||
}
|
||||
|
||||
$wallpaperTargetPath = $null
|
||||
@@ -604,31 +815,126 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
$wallpaperDirectory = Join-Path $windowsRoot 'Web\Wallpaper\DeployCenter'
|
||||
$wallpaperDestination = Join-Path $wallpaperDirectory $wallpaperName
|
||||
$wallpaperTargetPath = '%WINDIR%\Web\Wallpaper\DeployCenter\' + $wallpaperName
|
||||
if ($wallpaperDestination.Length -gt 259) { throw "Le chemin configuré du fond d’écran dépasse 259 caractères dans Windows." }
|
||||
if ($wallpaperDestination.Length -gt 259) { throw "The configured wallpaper path exceeds 259 characters in Windows." }
|
||||
}
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction "Application de Windows sur ${windowsLetter}:…" }
|
||||
$imageFileName = [System.IO.Path]::GetFileName($deploymentImagePath)
|
||||
if ([string]::IsNullOrWhiteSpace($imageFileName)) { throw "Could not determine the selected Windows image filename." }
|
||||
$imageStagingPath = $null
|
||||
$imageStagingDirectory = $null
|
||||
if ('Sources' -in $CopyLocally) {
|
||||
$imageStagingPath = Join-Path (Join-Path (Join-Path $windowsPath 'DeployCenter') 'Sources') $imageFileName
|
||||
$imageStagingDirectory = Split-Path -Path $imageStagingPath -Parent
|
||||
} else {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Using the Windows image directly from {0}." -f $deploymentImagePath) -Level 'INFO'
|
||||
}
|
||||
$imageFile = Get-Item -LiteralPath $deploymentImagePath -ErrorAction Stop
|
||||
$windowsDriveInfo = New-Object System.IO.DriveInfo -ArgumentList ("{0}:\" -f $windowsLetter)
|
||||
$driverPackSizeBytes = [long]0
|
||||
if (-not [string]::IsNullOrWhiteSpace($DriverPath) -and 'Drivers' -in $CopyLocally) {
|
||||
$driverFiles = @(Get-ChildItem -LiteralPath $DriverPath -Force -File -Recurse -ErrorAction Stop)
|
||||
$driverPackSizeBytes = [long](($driverFiles | Measure-Object -Property Length -Sum).Sum)
|
||||
}
|
||||
$requiredStagingBytes = [long]0
|
||||
if ('Sources' -in $CopyLocally) { $requiredStagingBytes += [long]$imageFile.Length }
|
||||
if ('Drivers' -in $CopyLocally) { $requiredStagingBytes += $driverPackSizeBytes }
|
||||
if ($requiredStagingBytes -gt 0 -and (-not $windowsDriveInfo.IsReady -or [long]$windowsDriveInfo.AvailableFreeSpace -lt $requiredStagingBytes)) {
|
||||
$availableImageSpaceMB = if ($windowsDriveInfo.IsReady) { [math]::Floor(([double]$windowsDriveInfo.AvailableFreeSpace / 1MB)) } else { 0 }
|
||||
$requiredStagingSpaceMB = [math]::Ceiling(([double]$requiredStagingBytes / 1MB))
|
||||
throw ("Insufficient space on ${windowsLetter}: to stage the Windows image and driver pack: {0} MB required, {1} MB available." -f $requiredStagingSpaceMB, $availableImageSpaceMB)
|
||||
}
|
||||
|
||||
if ('Sources' -in $CopyLocally) {
|
||||
$imageCopyTimer = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
New-Item -Path $imageStagingDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
if ($ProgressAction) { & $ProgressAction "Copying the Windows image locally…" }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Copying image {0} from {1} to {2} locally." -f $imageFileName, $deploymentImagePath, $imageStagingDirectory) -Level 'INFO'
|
||||
[void](Invoke-DeployCenterRobocopy -SourceDirectory (Split-Path -Path $deploymentImagePath -Parent) `
|
||||
-DestinationDirectory $imageStagingDirectory -CopyArguments @($imageFileName, '/J', '/R:2', '/W:2', '/ETA') `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction)
|
||||
$stagedImage = Get-Item -LiteralPath $imageStagingPath -ErrorAction Stop
|
||||
if ([long]$stagedImage.Length -ne [long]$imageFile.Length) {
|
||||
throw ("The local Windows image copy is incomplete: {0} bytes copied of {1}." -f $stagedImage.Length, $imageFile.Length)
|
||||
}
|
||||
$imageCopyTimer.Stop()
|
||||
$imageSizeMB = [math]::Round(([double]$stagedImage.Length / 1MB), 0)
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Local Windows image copy completed in {0:N1} s ({1} MB)." -f $imageCopyTimer.Elapsed.TotalSeconds, $imageSizeMB) -Level 'SUCCESS'
|
||||
$deploymentImagePath = $imageStagingPath
|
||||
} finally {
|
||||
if ($imageCopyTimer.IsRunning) { $imageCopyTimer.Stop() }
|
||||
}
|
||||
}
|
||||
|
||||
$driverDeploymentPath = $DriverPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($DriverPath) -and 'Drivers' -in $CopyLocally) {
|
||||
$driverStagingPath = Join-Path $windowsDeployCenterPath 'Drivers'
|
||||
$driverCopyTimer = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
if ($ProgressAction) { & $ProgressAction 'Copying drivers locally…' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Copying {0} INF file(s) from {1} to {2} before applying Windows." -f $DriverInfCount, $DriverPath, $driverStagingPath) -Level 'INFO'
|
||||
New-Item -Path $driverStagingPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
[void](Invoke-DeployCenterRobocopy -SourceDirectory $DriverPath -DestinationDirectory $driverStagingPath `
|
||||
-CopyArguments @('/E', '/J', '/R:2', '/W:2', '/ETA') -TemporaryRoot $TemporaryRoot `
|
||||
-LogAction $LogAction -ProgressAction $ProgressAction)
|
||||
$stagedInfCount = @(Get-ChildItem -LiteralPath $driverStagingPath -Filter '*.inf' -File -Recurse -ErrorAction Stop).Count
|
||||
if ($stagedInfCount -ne $DriverInfCount) {
|
||||
throw ("The local driver copy is incomplete: {0} INF file(s) copied of {1}." -f $stagedInfCount, $DriverInfCount)
|
||||
}
|
||||
$driverCopyTimer.Stop()
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Local driver copy completed in {0:N1} s ({1} INF file(s))." -f $driverCopyTimer.Elapsed.TotalSeconds, $stagedInfCount) -Level 'SUCCESS'
|
||||
$driverDeploymentPath = $driverStagingPath
|
||||
} finally {
|
||||
if ($driverCopyTimer.IsRunning) { $driverCopyTimer.Stop() }
|
||||
}
|
||||
} elseif (-not [string]::IsNullOrWhiteSpace($DriverPath)) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Using the driver pack directly from {0}." -f $DriverPath) -Level 'INFO'
|
||||
}
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction "Applying Windows to ${windowsLetter}:…" }
|
||||
Invoke-DeployCenterNativeCommand -FilePath $dismPath -Arguments @(
|
||||
'/Apply-Image', "/ImageFile:$deploymentImagePath", "/Index:$([int]$Edition.Index)", "/ApplyDir:$windowsPath", '/CheckIntegrity'
|
||||
) -TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DriverDeploymentPath)) {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message 'No matching driver pack; injection skipped.' -Level 'INFO'
|
||||
} else {
|
||||
$driverInstallTimer = $null
|
||||
try {
|
||||
if ($ProgressAction) { & $ProgressAction 'Installing hardware drivers…' }
|
||||
$driverInstallTimer = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
Invoke-DeployCenterNativeCommand -FilePath $dismPath -Arguments @(
|
||||
"/Image:$windowsPath", '/Add-Driver', "/Driver:$driverDeploymentPath", '/Recurse'
|
||||
) -TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
$driverInstallTimer.Stop()
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("DISM driver injection completed in {0:N1} s." -f $driverInstallTimer.Elapsed.TotalSeconds) -Level 'SUCCESS'
|
||||
} finally {
|
||||
if ($driverInstallTimer -and $driverInstallTimer.IsRunning) { $driverInstallTimer.Stop() }
|
||||
}
|
||||
}
|
||||
|
||||
if ($wallpaperSourcePath) {
|
||||
New-Item -Path $wallpaperDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Copy-Item -LiteralPath $wallpaperSourcePath -Destination $wallpaperDestination -Force -ErrorAction Stop
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Image de fond copiée dans Windows : {0}." -f $wallpaperDestination) -Level 'INFO'
|
||||
-Message ("Wallpaper image copied to Windows: {0}." -f $wallpaperDestination) -Level 'INFO'
|
||||
}
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction 'Vérification de la capacité de la partition WinRE…' }
|
||||
if ($ProgressAction) { & $ProgressAction 'Checking WinRE partition capacity…' }
|
||||
$sourceWinre = Join-Path $windowsRoot 'System32\Recovery\winre.wim'
|
||||
if (-not (Test-Path -LiteralPath $sourceWinre -PathType Leaf)) {
|
||||
throw "winre.wim est absent de l’image Windows appliquée ; impossible de configurer WinRE."
|
||||
throw "winre.wim is missing from the applied Windows image; WinRE cannot be configured."
|
||||
}
|
||||
$winreFile = Get-Item -LiteralPath $sourceWinre -ErrorAction Stop
|
||||
$recoveryPartition = $partitionsByLetter[$recoveryLetter]
|
||||
$recoveryDriveInfo = New-Object System.IO.DriveInfo -ArgumentList "${recoveryLetter}:\"
|
||||
if (-not $recoveryDriveInfo.IsReady) {
|
||||
throw "Le volume de récupération ${recoveryLetter}: n’est pas prêt pour vérifier l’espace WinRE."
|
||||
throw "The recovery volume ${recoveryLetter}: is not ready to check WinRE space."
|
||||
}
|
||||
$minimumPartitionBytes = [long](990 * 1MB)
|
||||
$minimumFreeAfterCopyBytes = [long](250 * 1MB)
|
||||
@@ -638,29 +944,29 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
$availableFreeMB = [math]::Floor(([double]$recoveryFreeBytes / 1MB))
|
||||
$availableAfterCopyMB = [math]::Floor(([double]$expectedFreeBytes / 1MB))
|
||||
if ($recoveryFreeBytes -lt [long]$winreFile.Length) {
|
||||
$message = "Espace insuffisant sur la partition WinRE pour copier winre.wim : fichier de $winreSizeForLog Mo, espace disponible $availableFreeMB Mo. Le déploiement ne peut pas configurer WinRE."
|
||||
$message = "Insufficient space on the WinRE partition to copy winre.wim: file size: $winreSizeForLog MB, available space $availableFreeMB MB. Deployment cannot configure WinRE."
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message $message -Level 'ERROR'
|
||||
throw $message
|
||||
}
|
||||
$recommendationWarnings = New-Object System.Collections.Generic.List[string]
|
||||
if ([long]$recoveryPartition.Size -lt $minimumPartitionBytes) {
|
||||
$actualPartitionMB = [math]::Floor(([double]$recoveryPartition.Size / 1MB))
|
||||
[void]$recommendationWarnings.Add("taille de partition : $actualPartitionMB Mo, minimum recommandé : 990 Mo")
|
||||
[void]$recommendationWarnings.Add("partition size: $actualPartitionMB MB, recommended minimum: 990 MB")
|
||||
}
|
||||
if ($expectedFreeBytes -lt $minimumFreeAfterCopyBytes) {
|
||||
[void]$recommendationWarnings.Add("espace libre après copie : $availableAfterCopyMB Mo, minimum recommandé : 250 Mo")
|
||||
[void]$recommendationWarnings.Add("free space after copy: $availableAfterCopyMB MB, recommended minimum: 250 MB")
|
||||
}
|
||||
if ($recommendationWarnings.Count -gt 0) {
|
||||
$warningDetails = $recommendationWarnings -join '; '
|
||||
$winreRecommendationWarnings = @($recommendationWarnings.ToArray())
|
||||
$message = "Avertissement WinRE : les recommandations Microsoft ne sont pas toutes respectées ($warningDetails). La copie de winre.wim reste possible ; le déploiement continue."
|
||||
$message = "WinRE warning: Microsoft recommendations are not all met ($warningDetails). winre.wim can still be copied; deployment will continue."
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message $message -Level 'WARNING'
|
||||
} else {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Vérification WinRE réussie : partition {0} Mo, winre.wim {1} Mo, espace restant estimé après copie {2} Mo." -f $winrePartitionSizeMB, $winreSizeForLog, $availableAfterCopyMB) -Level 'SUCCESS'
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("WinRE validation succeeded: partition {0} MB, winre.wim {1} MB, estimated free space after copy {2} MB." -f $winrePartitionSizeMB, $winreSizeForLog, $availableAfterCopyMB) -Level 'SUCCESS'
|
||||
}
|
||||
|
||||
if ($null -ne $UnattendProfile) {
|
||||
if ($ProgressAction) { & $ProgressAction 'Préparation du fichier de réponses Windows…' }
|
||||
if ($ProgressAction) { & $ProgressAction 'Preparing the Windows answer file…' }
|
||||
$editionArchitecture = if ($Edition.Edition -and $Edition.Edition.Architecture) { [string]$Edition.Edition.Architecture } else { [string]$Edition.Architecture }
|
||||
$unattendXml = New-DeployCenterUnattendXml -Profile $UnattendProfile `
|
||||
-Configuration $UnattendConfiguration -HostName ([string]$HostName) `
|
||||
@@ -670,35 +976,45 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
New-Item -Path $pantherUnattendDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Copy-Item -LiteralPath $unattendPath -Destination (Join-Path $pantherUnattendDirectory 'Unattend.xml') -Force -ErrorAction Stop
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Fichier de réponses Windows préparé avec le profil '{0}' (nom d’hôte : {1})." -f $UnattendProfile.Name, $HostName) -Level 'INFO'
|
||||
-Message ("Windows answer file prepared with profile '{0}' (computer name: {1})." -f $UnattendProfile.Name, $HostName) -Level 'INFO'
|
||||
}
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction 'Configuration du démarrage UEFI…' }
|
||||
if ($ProgressAction) { & $ProgressAction 'Configuring UEFI boot…' }
|
||||
Invoke-DeployCenterNativeCommand -FilePath $bcdbootPath -Arguments @(
|
||||
("{0}:\Windows" -f $windowsLetter), '/s', ("{0}:" -f $efiLetter), '/f', 'UEFI'
|
||||
) -TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction 'Configuration de la partition de récupération WinRE…' }
|
||||
if ($ProgressAction) { & $ProgressAction 'Configuring the WinRE recovery partition…' }
|
||||
New-Item -Path $recoveryPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Copy-Item -LiteralPath $sourceWinre -Destination (Join-Path $recoveryPath 'winre.wim') -Force -ErrorAction Stop
|
||||
if ($ProgressAction) { & $ProgressAction 'Copying winre.wim…' }
|
||||
$recoveryImagePath = Join-Path $recoveryPath 'winre.wim'
|
||||
[void](Invoke-DeployCenterRobocopy -SourceDirectory (Split-Path -Path $sourceWinre -Parent) `
|
||||
-DestinationDirectory $recoveryPath -CopyArguments @('winre.wim', '/J', '/R:2', '/W:2', '/ETA') `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction)
|
||||
$copiedWinre = Get-Item -LiteralPath $recoveryImagePath -ErrorAction Stop
|
||||
if ([long]$copiedWinre.Length -ne [long]$winreFile.Length) {
|
||||
throw ("The WinRE image copy is incomplete: {0} bytes copied of {1}." -f $copiedWinre.Length, $winreFile.Length)
|
||||
}
|
||||
$reagentcPath = Join-Path $windowsRoot 'System32\reagentc.exe'
|
||||
if (-not (Test-Path -LiteralPath $reagentcPath -PathType Leaf)) {
|
||||
$reagentcPath = Join-Path $env:WINDIR 'System32\reagentc.exe'
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $reagentcPath -PathType Leaf)) {
|
||||
throw 'REAgentC.exe est introuvable dans Windows appliqué et dans WinPE.'
|
||||
throw 'REAgentC.exe was not found in the applied Windows image or WinPE.'
|
||||
}
|
||||
$bcdStorePath = "${efiLetter}:\EFI\Microsoft\Boot\BCD"
|
||||
$windowsBootGuid = Get-DeployCenterOfflineWindowsBootGuid -BcdStorePath $bcdStorePath `
|
||||
-WindowsDriveLetter $windowsLetter -LogAction $LogAction
|
||||
-WindowsDriveLetter $windowsLetter -IdentifierLabel $IdentifierLabel -LogAction $LogAction
|
||||
if ($ProgressAction) { & $ProgressAction 'Registering the WinRE image…' }
|
||||
Invoke-DeployCenterNativeCommand -FilePath $reagentcPath -Arguments @(
|
||||
'/SetReImage', '/Path', $recoveryPath, '/Target', $windowsRoot
|
||||
) -TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
if ($ProgressAction) { & $ProgressAction 'Enabling WinRE…' }
|
||||
Invoke-DeployCenterNativeCommand -FilePath $reagentcPath -Arguments @(
|
||||
'/Enable', '/OSGUID', $windowsBootGuid
|
||||
) -TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
|
||||
if ($ProgressAction) { & $ProgressAction 'Finalisation des partitions…' }
|
||||
if ($ProgressAction) { & $ProgressAction 'Finalizing partitions…' }
|
||||
$removeLines = New-Object System.Collections.Generic.List[string]
|
||||
$lettersToRemove = @()
|
||||
foreach ($roleToRemove in @('System', 'Recovery')) {
|
||||
@@ -719,28 +1035,39 @@ function Invoke-DeployCenterWindowsDeployment {
|
||||
Invoke-DeployCenterNativeCommand -FilePath 'diskpart.exe' -Arguments @('/s', $removePath) `
|
||||
-TemporaryRoot $TemporaryRoot -LogAction $LogAction -ProgressAction $ProgressAction
|
||||
[void](Update-DeployCenterHostStorageCache -LogAction $LogAction)
|
||||
# Get-Partition peut conserver la lettre Recovery dans ses propriétés
|
||||
# après que DiskPart l'a effectivement retirée sous WinPE.
|
||||
# Get-Partition may retain the Recovery drive letter in its properties
|
||||
# after DiskPart has actually removed it in WinPE.
|
||||
$remainingPaths = @($lettersToRemove | Where-Object {
|
||||
[System.IO.Directory]::Exists(('{0}:\' -f $_))
|
||||
})
|
||||
if ($remainingPaths.Count -gt 0) {
|
||||
$remaining = (@($remainingPaths | ForEach-Object { ([string]$_).ToUpperInvariant() } | Select-Object -Unique)) -join ', '
|
||||
throw "DiskPart n’a pas libéré toutes les lettres temporaires EFI/WinRE : $remaining."
|
||||
throw "DiskPart did not release all temporary EFI/WinRE drive letters: $remaining."
|
||||
}
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $removePath -PathType Leaf) { Remove-Item -LiteralPath $removePath -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
$completionMode = if ($null -ne $UnattendProfile) { "profil Unattend '$($UnattendProfile.Name)'" } else { 'Avec OOBE' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Déploiement terminé : Windows {0}, disque {1}, mode {2}." -f $Edition.Name, $disk.Number, $completionMode) -Level 'SUCCESS'
|
||||
$completionMode = if ($null -ne $UnattendProfile) { "Unattend profile '$($UnattendProfile.Name)'" } else { 'Use OOBE' }
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message ("Deployment complete: Windows {0}, disk {1}, mode {2}." -f $Edition.Name, $disk.Number, $completionMode) -Level 'SUCCESS'
|
||||
if ($ProgressAction) { & $ProgressAction 'Cleaning up temporary files…' }
|
||||
} finally {
|
||||
foreach ($temporaryFile in @($cleanPath, $diskpartPath, $unattendPath)) {
|
||||
if (Test-Path -LiteralPath $temporaryFile -PathType Leaf) { Remove-Item -LiteralPath $temporaryFile -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Remove-DeployCenterDismSourceAccess -Lease $sourceLease -TemporaryRoot $TemporaryRoot -LogAction $LogAction
|
||||
if ($windowsDeployCenterPath -and (Test-Path -LiteralPath $windowsDeployCenterPath -PathType Container)) {
|
||||
try {
|
||||
Remove-Item -LiteralPath $windowsDeployCenterPath -Recurse -Force -ErrorAction Stop
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction -Message 'Temporary C:\DeployCenter directory removed.' -Level 'INFO'
|
||||
} catch {
|
||||
Write-DeployCenterDeploymentLog -LogAction $LogAction `
|
||||
-Message ("Unable to remove the temporary C:\DeployCenter directory: {0}" -f $_.Exception.Message) -Level 'WARNING'
|
||||
}
|
||||
}
|
||||
Remove-DeployCenterDismSourceAccess -Lease $sourceLease -TemporaryRoot $TemporaryRoot `
|
||||
-LogAction $LogAction -ProgressAction $ProgressAction
|
||||
}
|
||||
return [pscustomobject]@{
|
||||
WinRERecommendationWarnings = @($winreRecommendationWarnings)
|
||||
|
||||
Reference in new issue
Block a user