Ajout de la configuration de paritionnement des disques

This commit is contained in:
benoit committed 2026-10-15 23:05:30 +02:00
1 parent 7c724d475e
commit e388cdca45
9 files changed
+450 -123

No files matched your search

+25 -6
View File
@@ -9,17 +9,36 @@ function Get-DeployNetworkIPv4 {
# APIPA et adresses IPv4 non routables couramment inutiles en WinPE.
$candidates = @()
try {
foreach ($cfg in (Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' -ErrorAction Stop)) {
$hasGateway = @($cfg.DefaultIPGateway | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' }).Count -gt 0
foreach ($address in @($cfg.IPAddress | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' })) {
if ($address -eq '127.0.0.1' -or $address -like '169.254.*' -or $address -eq '0.0.0.0') { continue }
$candidates += [pscustomobject]@{ Address = $address; HasGateway = $hasGateway; Index = [int]$cfg.Index }
foreach ($interface in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()) {
if ($interface.NetworkInterfaceType -eq [System.Net.NetworkInformation.NetworkInterfaceType]::Loopback) { continue }
$properties = $interface.GetIPProperties()
$ipv4Properties = $properties.GetIPv4Properties()
if ($null -eq $ipv4Properties) { continue }
$hasGateway = @(
$properties.GatewayAddresses | Where-Object {
$_.Address.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork -and
$_.Address.ToString() -ne '0.0.0.0'
}
).Count -gt 0
foreach ($unicast in $properties.UnicastAddresses) {
$address = $unicast.Address
if ($address.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork) { continue }
$addressText = $address.ToString()
if ($addressText -eq '127.0.0.1' -or $addressText -like '169.254.*' -or $addressText -eq '0.0.0.0') { continue }
$candidates += [pscustomobject]@{
Address = $addressText
HasGateway = $hasGateway
Index = [int]$ipv4Properties.Index
}
}
}
} catch {
# La détection réseau ne doit pas empêcher le démarrage de DeployCenter.
if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
Write-Log "Détection réseau WMI : $($_.Exception.Message)" 'WARNING'
Write-Log "Détection réseau : $($_.Exception.Message)" 'WARNING'
}
}
+104
View File
@@ -0,0 +1,104 @@
# DeployCenter - Chargement et validation des profils de partitionnement
# Les profils PSD1 sont des fichiers de données ; ils ne contiennent aucun code exécuté.
function Get-DeployCenterPartitionProfileNames {
[CmdletBinding()]
param([Parameter(Mandatory=$true)][string]$ProfilesRoot)
if (-not (Test-Path -LiteralPath $ProfilesRoot -PathType Container)) { return @() }
$names = @()
foreach ($file in (Get-ChildItem -LiteralPath $ProfilesRoot -Filter '*.psd1' -File -ErrorAction SilentlyContinue)) {
if ($file.BaseName -match '^[A-Za-z0-9_-]+$') { $names += $file.BaseName }
}
return @($names | Sort-Object -Unique)
}
function Import-DeployCenterPartitionProfile {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ProfilesRoot,
[Parameter(Mandatory=$true)][string]$ProfileName
)
if ($ProfileName -notmatch '^[A-Za-z0-9_-]+$') { throw "Nom de profil invalide : $ProfileName" }
$path = Join-Path $ProfilesRoot ($ProfileName + '.psd1')
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Profil introuvable : $ProfileName" }
$profile = Import-PowerShellDataFile -LiteralPath $path
foreach ($key in @('Id','Name','Description','Firmware','PartitionStyle','Partitions')) {
if (-not $profile.ContainsKey($key)) { throw "Le profil '$ProfileName' ne contient pas la clé '$key'." }
}
if ([string]$profile.Id -ne $ProfileName) { throw "L'identifiant du profil '$ProfileName' ne correspond pas au nom du fichier." }
if (@($profile.Partitions).Count -lt 2) { throw "Le profil '$ProfileName' doit définir au moins deux partitions." }
if (@($profile.Partitions | Where-Object { [string]$_.Role -eq 'Windows' }).Count -ne 1) {
throw "Le profil '$ProfileName' doit définir exactement une partition Windows."
}
$userInputPartitions = @($profile.Partitions | Where-Object { [string]$_.Size.Policy -eq 'UserInputGB' })
$remainderPartitions = @($profile.Partitions | Where-Object { [string]$_.Size.Policy -eq 'RemainderAfterInputs' })
$remainingPartitions = @($profile.Partitions | Where-Object { [string]$_.Size.Policy -eq 'Remaining' })
if ($userInputPartitions.Count -gt 0) {
if ($remainderPartitions.Count -ne 1 -or $remainingPartitions.Count -gt 0) {
throw "Le profil '$ProfileName' doit associer les saisies utilisateur à une partition de reste unique."
}
} elseif ($remainingPartitions.Count -ne 1 -or [string]$profile.Partitions[-1].Size.Policy -ne 'Remaining') {
throw "Le profil '$ProfileName' doit placer une unique partition utilisant l'espace restant en dernière position."
}
foreach ($partition in $profile.Partitions) {
foreach ($key in @('Name','Role','FileSystem','Size')) {
if (-not $partition.ContainsKey($key)) { throw "Une partition du profil '$ProfileName' ne contient pas la clé '$key'." }
}
if (-not $partition.Size.ContainsKey('Policy')) { throw "La taille d'une partition du profil '$ProfileName' ne définit pas Policy." }
switch ([string]$partition.Size.Policy) {
'FixedMB' {
if ([int]$partition.Size.Value -le 0) { throw "Une taille fixe du profil '$ProfileName' doit être supérieure à zéro." }
}
'ImageSizePlusFreeSpace' {
if ([int]$partition.Size.FreeSpaceMB -lt 250) { throw "La marge libre WinRE du profil '$ProfileName' doit être d'au moins 250 Mo." }
if ([int]$partition.Size.PreviewMB -le 0) { throw "Le profil '$ProfileName' doit définir une réserve PreviewMB pour estimer la capacité disponible." }
}
'UserInputGB' {
if ([int]$partition.Size.MinimumGB -le 0 -or [int]$partition.Size.DefaultPercent -le 0 -or [int]$partition.Size.DefaultPercent -ge 100) {
throw "La saisie de taille utilisateur du profil '$ProfileName' doit définir MinimumGB et un DefaultPercent entre 1 et 99."
}
if ([string]::IsNullOrWhiteSpace([string]$partition.Size.Label)) { throw "La saisie de taille utilisateur du profil '$ProfileName' doit définir Label." }
}
'RemainderAfterInputs' {
if ([double]$partition.Size.MinimumGB -lt 0 -or [string]::IsNullOrWhiteSpace([string]$partition.Size.Label)) {
throw "La partition de reste du profil '$ProfileName' doit définir un MinimumGB non négatif et un Label."
}
}
'Remaining' { }
default { throw "Politique de taille inconnue dans le profil '$ProfileName' : $($partition.Size.Policy)" }
}
}
return $profile
}
function Get-DeployCenterPartitionSizingCapacity {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)][long]$DiskSizeBytes
)
$reservedMB = 0.0
foreach ($partition in $Profile.Partitions) {
switch ([string]$partition.Size.Policy) {
'FixedMB' { $reservedMB += [double]$partition.Size.Value }
default {
if ($partition.Size.ContainsKey('PreviewMB')) { $reservedMB += [double]$partition.Size.PreviewMB }
}
}
}
$capacityBytes = $DiskSizeBytes - [long][math]::Ceiling($reservedMB * 1MB)
if ($capacityBytes -le 0) { throw "Le disque est trop petit pour les réserves du profil '$($Profile.Name)'." }
return [pscustomobject]@{
UsableBytes = [long]$capacityBytes
UsableGB = [math]::Round(($capacityBytes / 1GB), 1)
ReservedMB = [math]::Ceiling($reservedMB)
}
}
Export-ModuleMember -Function Get-DeployCenterPartitionProfileNames, Import-DeployCenterPartitionProfile, Get-DeployCenterPartitionSizingCapacity
+142 -1
View File
@@ -19,4 +19,145 @@
}
}
Export-ModuleMember -Function Set-DeployCenterButtonStyle
function New-DeployCenterComboBox {
param(
[Parameter(Mandatory=$true)][string]$Name,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[Parameter(Mandatory=$true)][string[]]$Items
)
$comboBox = New-Object System.Windows.Forms.ComboBox
$comboBox.Name = $Name
$comboBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
$comboBox.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$comboBox.BackColor = $Theme.PanelAlt
$comboBox.ForeColor = $Theme.Text
$comboBox.Font = $Font
$comboBox.IntegralHeight = $false
$comboBox.MaxDropDownItems = 12
foreach ($item in $Items) { [void]$comboBox.Items.Add($item) }
return $comboBox
}
function Set-DeployCenterComboBoxTheme {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.ComboBox]$ComboBox,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme
)
$ComboBox.BackColor = $Theme.PanelAlt
$ComboBox.ForeColor = $Theme.Text
}
function New-DeployCenterPartitionProfileSelector {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.GroupBox]$Parent,
[Parameter(Mandatory=$true)][string[]]$ProfileNames,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font
)
$label = New-Object System.Windows.Forms.Label
$label.Text = 'Profil de partitionnement'
$label.Size = New-Object System.Drawing.Size(240, 17)
$label.ForeColor = $Theme.Text
$label.BackColor = [System.Drawing.Color]::Transparent
$label.Font = $Font
$comboBox = New-DeployCenterComboBox -Name 'cmbPartitionProfile' -Theme $Theme -Font $Font -Items $ProfileNames
$Parent.Controls.Add($label)
$Parent.Controls.Add($comboBox)
Set-DeployCenterPartitionProfileSelectorLayout -Label $label -ComboBox $comboBox -Width ($Parent.ClientSize.Width - 24)
return [pscustomobject]@{ Label = $label; ComboBox = $comboBox }
}
function Set-DeployCenterPartitionProfileSelectorLayout {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.Label]$Label,
[Parameter(Mandatory=$true)][System.Windows.Forms.ComboBox]$ComboBox,
[Parameter(Mandatory=$true)][int]$Width
)
$Label.Location = New-Object System.Drawing.Point(12, 66)
$ComboBox.Location = New-Object System.Drawing.Point(12, 84)
$ComboBox.Size = New-Object System.Drawing.Size([Math]::Max(250, $Width), 26)
}
function Show-DeployCenterMessage {
param(
[Parameter(Mandatory=$true)][string]$Message,
[Parameter(Mandatory=$true)][string]$Title,
[ValidateSet('Information','Warning','Error')][string]$Icon = 'Information',
[System.Windows.Forms.Form]$Owner = $null,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font
)
$messageForm = New-Object System.Windows.Forms.Form
$messageForm.Text = $Title
$messageForm.StartPosition = if ($Owner) { 'CenterParent' } else { 'CenterScreen' }
$messageForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$messageForm.ShowInTaskbar = $false
$messageForm.MinimizeBox = $false
$messageForm.MaximizeBox = $false
$messageForm.BackColor = $Theme.Background
$messageForm.ForeColor = $Theme.Text
$messageForm.Font = $Font
$messageWidth = 390
$textSize = [System.Windows.Forms.TextRenderer]::MeasureText(
$Message,
$Font,
(New-Object System.Drawing.Size($messageWidth, 0)),
[System.Windows.Forms.TextFormatFlags]::WordBreak
)
$messageHeight = [Math]::Max(42, $textSize.Height + 8)
$messageForm.ClientSize = New-Object System.Drawing.Size(470, ($messageHeight + 94))
$iconLabel = New-Object System.Windows.Forms.Label
$iconLabel.Location = New-Object System.Drawing.Point(18, 24)
$iconLabel.Size = New-Object System.Drawing.Size(34, 34)
$iconLabel.Text = if ($Icon -eq 'Information') { 'i' } else { '!' }
$iconLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$iconLabel.Font = New-Object System.Drawing.Font('Segoe UI', [single]16, [System.Drawing.FontStyle]::Bold)
$iconLabel.ForeColor = if ($Icon -eq 'Error') { $Theme.Danger } else { $Theme.Accent }
$iconLabel.BackColor = [System.Drawing.Color]::Transparent
$messageForm.Controls.Add($iconLabel)
$messageLabel = New-Object System.Windows.Forms.Label
$messageLabel.Location = New-Object System.Drawing.Point(64, 20)
$messageLabel.Size = New-Object System.Drawing.Size($messageWidth, $messageHeight)
$messageLabel.Text = $Message
$messageLabel.ForeColor = $Theme.Text
$messageLabel.BackColor = [System.Drawing.Color]::Transparent
$messageLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$messageLabel.Font = $Font
$messageForm.Controls.Add($messageLabel)
$buttonPanel = New-Object System.Windows.Forms.Panel
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$buttonPanel.Height = 58
$buttonPanel.BackColor = $Theme.PanelAlt
$messageForm.Controls.Add($buttonPanel)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = 'OK'
$okButton.Location = New-Object System.Drawing.Point(362, 10)
$okButton.Size = New-Object System.Drawing.Size(92, 36)
$okButton.Font = $Font
$okButton.Cursor = [System.Windows.Forms.Cursors]::Hand
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
Set-DeployCenterButtonStyle -Button $okButton -Theme $Theme -Role 'PrimaryButton'
$buttonPanel.Controls.Add($okButton)
$messageForm.AcceptButton = $okButton
$messageForm.CancelButton = $okButton
if ($Owner) {
[void]$messageForm.ShowDialog($Owner)
} else {
[void]$messageForm.ShowDialog()
}
$messageForm.Dispose()
}
Export-ModuleMember -Function Set-DeployCenterButtonStyle, New-DeployCenterComboBox, Set-DeployCenterComboBoxTheme, New-DeployCenterPartitionProfileSelector, Set-DeployCenterPartitionProfileSelectorLayout, Show-DeployCenterMessage
+18 -6
View File
@@ -8,7 +8,6 @@ function Start-DeployCenterVnc {
$passwordTool = Join-Path $sourceVnc 'createpassword64.exe'
$sourceIni = Join-Path $sourceVnc 'ultravnc.ini'
$script:DeployIPv4 = Get-DeployNetworkIPv4
if ([string]::IsNullOrWhiteSpace($script:DeployIPv4)) {
Write-Log 'Aucune adresse IPv4 de déploiement détectée.' 'WARNING'
}
@@ -29,25 +28,36 @@ function Start-DeployCenterVnc {
return
}
$vncStage = 'préparation du dossier temporaire'
try {
if (Test-Path -LiteralPath $script:VncDirectory) {
Remove-Item -LiteralPath $script:VncDirectory -Recurse -Force -ErrorAction Stop
}
New-Item -Path $script:VncDirectory -ItemType Directory -Force | Out-Null
$vncStage = "copie des fichiers depuis $sourceVnc vers $script:VncDirectory"
Copy-Item -Path (Join-Path $sourceVnc '*') -Destination $script:VncDirectory -Recurse -Force
# WinPE ne permet pas toujours d'ajouter des règles de pare-feu.
# La désactivation globale n'est effectuée que lorsque VNC est demandé.
$vncStage = 'désactivation du pare-feu WinPE'
$wpeutil = Join-Path $env:WINDIR 'System32\wpeutil.exe'
if (-not (Test-Path -LiteralPath $wpeutil -PathType Leaf)) {
$wpeutil = 'wpeutil.exe'
}
& $wpeutil DisableFirewall
if ($LASTEXITCODE -ne 0) {
throw "wpeutil DisableFirewall a renvoyé le code $LASTEXITCODE."
$wpeutilOutput = @(& $wpeutil DisableFirewall 2>&1)
$wpeutilExitCode = $LASTEXITCODE
foreach ($outputLine in $wpeutilOutput) {
$outputText = [string]$outputLine
if (-not [string]::IsNullOrWhiteSpace($outputText)) {
Write-Log "wpeutil DisableFirewall : $outputText" 'INFO'
}
}
Write-Log 'Pare-feu WinPE désactivé à la demande de -Vnc.' 'WARNING'
if ($wpeutilExitCode -ne 0) {
throw "wpeutil DisableFirewall a renvoyé le code $wpeutilExitCode."
}
Write-Log "Pare-feu WinPE désactivé à la demande de -Vnc (code wpeutil : $wpeutilExitCode)." 'WARNING'
# Force le mode portable : l'INI actif est celui placé à côté de winvnc.exe.
$vncStage = 'activation du mode portable UltraVNC'
$portableFlag = Join-Path $script:VncDirectory 'ultravnc.portable'
Set-Content -LiteralPath $portableFlag -Value '' -Encoding ASCII
@@ -64,6 +74,7 @@ function Start-DeployCenterVnc {
}
# createpassword64 encode le mot de passe choisi dans le champ passwd d'ultravnc.ini.
$vncStage = 'génération du mot de passe UltraVNC'
$tool = Join-Path $script:VncDirectory 'createpassword64.exe'
$script:VncProcess = Start-Process -FilePath $tool -ArgumentList @($script:VncPassword) `
-WorkingDirectory $script:VncDirectory -Wait -PassThru -WindowStyle Hidden
@@ -78,6 +89,7 @@ function Start-DeployCenterVnc {
}
$server = Join-Path $script:VncDirectory 'winvnc.exe'
$vncStage = 'démarrage du serveur UltraVNC'
$script:VncProcess = Start-Process -FilePath $server -ArgumentList @('-run') `
-WorkingDirectory $script:VncDirectory -PassThru -WindowStyle Hidden
Start-Sleep -Milliseconds 800
@@ -87,7 +99,7 @@ function Start-DeployCenterVnc {
$script:VncStarted = $true
Write-Log "UltraVNC démarré. IP : $($script:DeployIPv4) ; mot de passe $passwordSource." 'SUCCESS'
} catch {
Write-Log "Échec du démarrage UltraVNC : $($_.Exception.Message)" 'ERROR'
Write-Log "Échec du démarrage UltraVNC à l'étape '$vncStage' : $($_.Exception.Message)" 'ERROR'
$script:VncStarted = $false
$script:VncPassword = $null
}