Files
DeployCenter/DeployCenter.ps1
T
benoit e067721763 Ajout du support de unattend
Ajout du script cmd à utiliser pour lancer DeployCenter
2026-10-17 20:41:41 +02:00

889 lines
40 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DeployCenter - Interface WinPE
# Compatible Windows PowerShell 5.1
param(
# Affiche DeployCenter dans une fenetre redimensionnable pour le debug.
[switch]$Windowed,
[switch]$Vnc
)
$script:Windowed = [bool]$Windowed
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
Import-Module (Join-Path $PSScriptRoot "Modules\ThemeManager.psm1") -Force
$uiModulePath = Join-Path $PSScriptRoot 'Modules\UI.psm1'
if (-not (Test-Path -LiteralPath $uiModulePath -PathType Leaf)) { throw "Module UI introuvable : $uiModulePath" }
# Les workflows UI utilisent l'état de la session principale ; ce fichier est donc chargé dans ce scope.
. ([scriptblock]::Create([System.IO.File]::ReadAllText($uiModulePath)))
Import-Module (Join-Path $PSScriptRoot "Modules\Configuration.psm1") -Force
Import-Module (Join-Path $PSScriptRoot "Modules\HardwareInventory.psm1") -Force
# GroupBox dessine par DeployCenter pour que le fond et le cadre suivent les themes.
if (-not ([System.Management.Automation.PSTypeName]'DeployCenter.ThemedGroupBox').Type) {
Add-Type -TypeDefinition @'
using System;
using System.Drawing;
using System.Windows.Forms;
namespace DeployCenter {
public class ThemedGroupBox : GroupBox {
public Color PanelColor { get; set; }
public Color BorderColor { get; set; }
public Color TextColor { get; set; }
public ThemedGroupBox() {
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
PanelColor = SystemColors.Control;
BorderColor = SystemColors.ControlDark;
TextColor = SystemColors.ControlText;
}
protected override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
g.Clear(PanelColor);
Size textSize = TextRenderer.MeasureText(Text, Font);
int textLeft = 10;
int lineY = Math.Max(8, (int)Math.Ceiling(textSize.Height / 2.0));
using (Pen pen = new Pen(BorderColor)) {
g.DrawLine(pen, 0, lineY, textLeft - 2, lineY);
int afterText = textLeft + textSize.Width + 4;
if (afterText < Width - 2) g.DrawLine(pen, afterText, lineY, Width - 2, lineY);
g.DrawLine(pen, 0, lineY, 0, Height - 2);
g.DrawLine(pen, 0, Height - 2, Width - 2, Height - 2);
g.DrawLine(pen, Width - 2, lineY, Width - 2, Height - 2);
}
TextRenderer.DrawText(g, Text, Font, new Point(textLeft, 0), TextColor,
PanelColor, TextFormatFlags.NoPadding | TextFormatFlags.SingleLine);
}
}
}
'@ -ReferencedAssemblies System.Windows.Forms,System.Drawing
}
# ============================================================
# CHEMINS
# ============================================================
$script:DeployRoot = $PSScriptRoot
# Racine du lecteur qui heberge DeployCenter (ex. Y:\), sans lecteur code en dur.
$script:DeployVolumeRoot = $null
try {
$deployQualifier = Split-Path -Path $script:DeployRoot -Qualifier -ErrorAction Stop
if (-not [string]::IsNullOrWhiteSpace($deployQualifier)) {
$script:DeployVolumeRoot = $deployQualifier.TrimEnd('\') + '\'
}
} catch {
$script:DeployVolumeRoot = $null
}
$script:AssetsRoot = Join-Path $script:DeployRoot "Assets"
$script:LogoPath = Join-Path $script:AssetsRoot "logo.png"
$script:LogoDarkPath = Join-Path $script:AssetsRoot "logo-dark.png"
$script:LogRoot = Join-Path $script:DeployRoot "Logs"
# Détection réseau centralisée dans Modules\\Network.psm1.
$networkModulePath = Join-Path $PSScriptRoot "Modules\\Network.psm1"
if (-not (Test-Path -LiteralPath $networkModulePath -PathType Leaf)) {
throw "Module réseau introuvable : $networkModulePath"
}
. ([scriptblock]::Create([System.IO.File]::ReadAllText($networkModulePath)))
$script:DeployIPv4 = Get-DeployNetworkIPv4
# La gestion des journaux est centralisee dans Modules\\Logging.psm1.
$loggingModulePath = Join-Path $PSScriptRoot "Modules\\Logging.psm1"
if (-not (Test-Path -LiteralPath $loggingModulePath -PathType Leaf)) {
throw "Module de journalisation introuvable : $loggingModulePath"
}
. ([scriptblock]::Create([System.IO.File]::ReadAllText($loggingModulePath)))
Initialize-DeployCenterLogging -LogRoot $script:LogRoot -IPv4 $script:DeployIPv4
# Garder le scope du script principal pour que Write-Log reste résolu dans les callbacks WinForms.
$script:SourceLogAction = {
param($Message, $Level)
if ([string]::IsNullOrWhiteSpace([string]$Level)) { $Level = 'INFO' }
Write-Log -Message $Message -Level $Level
}
# Analyse des sources et des images Windows.
$windowsSourcesModulePath = Join-Path $script:DeployRoot 'Modules\WindowsSources.psm1'
if (-not (Test-Path -LiteralPath $windowsSourcesModulePath -PathType Leaf)) {
throw "Module de sources Windows introuvable : $windowsSourcesModulePath"
}
Import-Module $windowsSourcesModulePath -Force
# Les profils de partitionnement sont des fichiers de données PSD1, validés par leur module.
$partitioningModulePath = Join-Path $script:DeployRoot 'Modules\Partitioning.psm1'
if (-not (Test-Path -LiteralPath $partitioningModulePath -PathType Leaf)) {
throw "Module de partitionnement introuvable : $partitioningModulePath"
}
Import-Module $partitioningModulePath -Force
$deploymentModulePath = Join-Path $script:DeployRoot 'Modules\Deployment.psm1'
if (-not (Test-Path -LiteralPath $deploymentModulePath -PathType Leaf)) {
throw "Module de déploiement introuvable : $deploymentModulePath"
}
Import-Module $deploymentModulePath -Force
$unattendModulePath = Join-Path $script:DeployRoot 'Modules\Unattend.psm1'
if (-not (Test-Path -LiteralPath $unattendModulePath -PathType Leaf)) {
throw "Module Unattend introuvable : $unattendModulePath"
}
Import-Module $unattendModulePath -Force
$script:PartitionProfilesRoot = Join-Path $script:DeployRoot 'Partitioning\Profiles'
$script:PartitionProfiles = @()
foreach ($profileName in (Get-DeployCenterPartitionProfileNames -ProfilesRoot $script:PartitionProfilesRoot)) {
try {
$script:PartitionProfiles += Import-DeployCenterPartitionProfile -ProfilesRoot $script:PartitionProfilesRoot -ProfileName $profileName
} catch {
& $script:SourceLogAction "Profil de partitionnement '$profileName' ignoré : $($_.Exception.Message)" 'WARNING'
}
}
if ($script:PartitionProfiles.Count -eq 0) {
throw "Aucun profil de partitionnement valide trouvé dans $script:PartitionProfilesRoot"
}
$script:SelectedPartitionProfileIndex = 0
for ($profileIndex = 0; $profileIndex -lt $script:PartitionProfiles.Count; $profileIndex++) {
if ($script:PartitionProfiles[$profileIndex].Default -eq $true) {
$script:SelectedPartitionProfileIndex = $profileIndex
break
}
}
$script:SelectedPartitionProfile = $script:PartitionProfiles[$script:SelectedPartitionProfileIndex]
$script:SelectedPartitionSizing = $null
$script:SelectedDiskIndex = $null
$script:SelectedWindowsEdition = $null
$script:RestartAfterDeployment = $false
$script:RefreshingDiskList = $false
$script:ChangingPartitionProfileSelection = $false
# Inventaire de session et UltraVNC portable : tout reste dans le dossier temporaire.
$script:InventoryDir = Join-Path $env:TEMP 'DeployCenter'
$script:InventoryFile = Join-Path $script:InventoryDir 'DeployCenter_Inventory.json'
$script:VncPassword = $null
$script:VncDirectory = Join-Path $script:InventoryDir ("uvnc_{0}" -f $PID)
$script:VncProcess = $null
$script:VncStarted = $false
$script:HardwareInventory = $null
# ============================================================
# THEMES EXTERNES (Themes\*.psd1)
# Les thèmes sont des fichiers de données ; aucun code PowerShell n'est exécuté.
# ============================================================
$script:ThemesRoot = Join-Path $script:DeployRoot "Themes"
if (-not (Test-Path -LiteralPath $script:ThemesRoot -PathType Container)) {
New-Item -Path $script:ThemesRoot -ItemType Directory -Force | Out-Null
}
# ============================================================
# PREFERENCES (DeployCenter.ini en lecture seule : aucune ecriture automatique)
# ============================================================
$script:ConfigPath = Join-Path $script:DeployRoot "DeployCenter.ini"
# Themes par defaut : crees uniquement si absents, afin de faciliter les personnalisations.
$defaultThemeFiles = @{
Dark = @"
@{
Background = "#171A21"
Panel = "#222731"
PanelAlt = "#1C2029"
Text = "#F1F5F9"
Secondary = "#9CA9BC"
Accent = "#3B82F6"
Success = "#4ADE80"
Border = "#414A59"
Danger = "#EF4444"
Button = @{
Background = "#303744"; Foreground = "#F1F5F9"; Border = "#414A59"
HoverBackground = "#3B4555"; PressedBackground = "#485365"
DisabledBackground = "#292E38"; DisabledForeground = "#7F8999"
}
PrimaryButton = @{
Background = "#3B82F6"; Foreground = "#FFFFFF"; Border = "#3B82F6"
HoverBackground = "#2563EB"; PressedBackground = "#1D4ED8"
DisabledBackground = "#344A6A"; DisabledForeground = "#9AAAC1"
}
DangerButton = @{
Background = "#EF4444"; Foreground = "#FFFFFF"; Border = "#EF4444"
HoverBackground = "#DC2626"; PressedBackground = "#B91C1C"
DisabledBackground = "#59383B"; DisabledForeground = "#BBA0A2"
}
}
"@
Light = @"
@{
Background = "#F5F7FA"
Panel = "#FFFFFF"
PanelAlt = "#F8FAFC"
Text = "#1E293B"
Secondary = "#475569"
Accent = "#2563EB"
Success = "#16A34A"
Border = "#CBD5E1"
Danger = "#DC2626"
Button = @{
Background = "#E8EDF3"; Foreground = "#1E293B"; Border = "#CBD5E1"
HoverBackground = "#DCE4EE"; PressedBackground = "#CBD5E1"
DisabledBackground = "#F1F3F6"; DisabledForeground = "#94A3B8"
}
PrimaryButton = @{
Background = "#2563EB"; Foreground = "#FFFFFF"; Border = "#2563EB"
HoverBackground = "#1D4ED8"; PressedBackground = "#1E40AF"
DisabledBackground = "#C7D8F8"; DisabledForeground = "#F8FAFC"
}
DangerButton = @{
Background = "#DC2626"; Foreground = "#FFFFFF"; Border = "#DC2626"
HoverBackground = "#B91C1C"; PressedBackground = "#991B1B"
DisabledBackground = "#F2C5C5"; DisabledForeground = "#FFF7F7"
}
}
"@
}
foreach ($themeFileName in $defaultThemeFiles.Keys) {
$themeFilePath = Join-Path $script:ThemesRoot ($themeFileName + '.psd1')
if (-not (Test-Path -LiteralPath $themeFilePath -PathType Leaf)) {
Set-Content -LiteralPath $themeFilePath -Value $defaultThemeFiles[$themeFileName] -Encoding UTF8
}
}
$script:Config = Read-DeployCenterConfig -ConfigPath $script:ConfigPath
$script:UnattendProfilesRoot = Join-Path $script:DeployRoot 'Unattend\Profiles'
$script:UnattendProfiles = @()
foreach ($unattendProfileName in (Get-DeployCenterUnattendProfileNames -ProfilesRoot $script:UnattendProfilesRoot)) {
try {
$script:UnattendProfiles += Import-DeployCenterUnattendProfile `
-ProfilesRoot $script:UnattendProfilesRoot -ProfileName $unattendProfileName
} catch {
& $script:SourceLogAction "Profil Unattend '$unattendProfileName' ignoré : $($_.Exception.Message)" 'WARNING'
}
}
$duplicateUnattendNames = @($script:UnattendProfiles | Group-Object -Property Name | Where-Object Count -gt 1)
foreach ($duplicateName in $duplicateUnattendNames) {
& $script:SourceLogAction "Profils Unattend ignorés car leur nom affiché est en double : $($duplicateName.Name)." 'WARNING'
$script:UnattendProfiles = @($script:UnattendProfiles | Where-Object { [string]$_.Name -ne [string]$duplicateName.Name })
}
$script:UnattendOobeChoice = 'Avec OOBE'
foreach ($partitionProfile in $script:PartitionProfiles) {
foreach ($partition in $partitionProfile.Partitions) {
if ([string]$partition.Size.Policy -ne 'ConfiguredMB') { continue }
$configKey = [string]$partition.Size.ConfigKey
if (-not $script:Config.ContainsKey($configKey)) {
throw "Le paramètre DeployCenter.ini '$configKey' requis par le profil '$($partitionProfile.Name)' est absent de la configuration."
}
$configuredSizeMB = [int]$script:Config[$configKey]
if ($configuredSizeMB -le 0) {
throw "Le paramètre DeployCenter.ini '$configKey' doit être strictement positif."
}
$partition.Size.Value = $configuredSizeMB
$partition.Size.PreviewMB = $configuredSizeMB
}
}
$availableThemes = @(Get-DeployCenterThemeNames -ThemesRoot $script:ThemesRoot)
if ($availableThemes.Count -eq 0) { throw "Aucun thème valide trouvé dans $script:ThemesRoot" }
if ($availableThemes -notcontains $script:Config.Theme) { $script:Config.Theme = "Light" }
$script:CurrentTheme = $script:Config.Theme
try {
$script:Theme = Import-DeployCenterTheme -ThemesRoot $script:ThemesRoot -ThemeName $script:CurrentTheme
} catch {
$script:CurrentTheme = "Light"
$script:Config.Theme = "Light"
$script:Theme = Import-DeployCenterTheme -ThemesRoot $script:ThemesRoot -ThemeName "Light"
}
# ============================================================
# POLICES
# ============================================================
$script:FontRegular = [System.Drawing.Font]::new("Segoe UI", [single]10, [System.Drawing.FontStyle]::Regular)
$script:FontSmall = [System.Drawing.Font]::new("Segoe UI", [single]9, [System.Drawing.FontStyle]::Regular)
$script:FontTitle = [System.Drawing.Font]::new("Segoe UI", [single]23, [System.Drawing.FontStyle]::Bold)
$script:FontSubtitle = [System.Drawing.Font]::new("Segoe UI", [single]10, [System.Drawing.FontStyle]::Regular)
$script:FontGroup = [System.Drawing.Font]::new("Segoe UI", [single]11, [System.Drawing.FontStyle]::Bold)
# ============================================================
# RESEAU, ULTRAVNC ET INVENTAIRE DE SESSION
# ============================================================
# L'adresse IPv4 a été détectée par Modules\\Network.psm1 avant l'initialisation du journal.
# VNC est désactivé par défaut ; il démarre uniquement avec le paramètre -vnc.
$script:EnableVnc = [bool]$Vnc
if ($script:EnableVnc) {
try {
# Le code VNC est exécuté dans le scope principal uniquement avec -vnc.
$vncModulePath = Join-Path $PSScriptRoot "Modules\VNC.psm1"
if (-not (Test-Path -LiteralPath $vncModulePath -PathType Leaf)) {
throw "Module VNC introuvable : $vncModulePath"
}
$vncCode = [System.IO.File]::ReadAllText($vncModulePath)
. ([scriptblock]::Create($vncCode))
Start-DeployCenterVnc
} catch {
& $script:SourceLogAction "Impossible de démarrer VNC : $($_.Exception.Message)" 'ERROR'
}
} else {
& $script:SourceLogAction "UltraVNC non démarré : paramètre -vnc absent."
}
# ============================================================
# CREATION DES CONTROLES
# ============================================================
# ============================================================
# FENETRE PRINCIPALE
# ============================================================
$script:form = New-Object System.Windows.Forms.Form
$script:form.Text = "DeployCenter"
$script:form.BackColor = $script:Theme.Background
if ($script:Windowed) {
# Mode debug : fenetre classique, deplacable et reductible.
$script:form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
$script:form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Sizable
$script:form.WindowState = [System.Windows.Forms.FormWindowState]::Normal
$script:form.TopMost = $false
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
$debugWidth = [Math]::Min(1200, [Math]::Max(800, $screen.Width - 80))
$debugHeight = [Math]::Min(850, [Math]::Max(600, $screen.Height - 80))
$script:form.Size = New-Object System.Drawing.Size($debugWidth, $debugHeight)
} else {
# Mode normal : plein ecran, sans bordure et au premier plan.
$script:form.StartPosition = "Manual"
$script:form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None
$script:form.WindowState = [System.Windows.Forms.FormWindowState]::Maximized
$script:form.TopMost = $true
$script:form.Bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
}
$script:form.ForeColor = $script:Theme.Text
$script:form.Font = $script:FontRegular
$script:form.KeyPreview = $true
$script:form.MinimumSize = New-Object System.Drawing.Size(800, 600)
$screenWidth = $script:form.ClientSize.Width
$screenHeight = $script:form.ClientSize.Height
$margin = 30
$usableWidth = $screenWidth - (2 * $margin)
# ============================================================
# EN-TETE ET LOGO FACULTATIF
# ============================================================
$script:pictureLogo = $null
$titleX = 30
# Deux fichiers seulement : logo.png (clair et solution de repli) et logo-dark.png.
# En mode sombre, logo-dark.png est prioritaire ; s'il manque, logo.png est utilise.
# En mode clair, logo.png est utilise.
$initialLogoPath = Get-DeployCenterLogoPathForTheme -ThemeName $script:CurrentTheme `
-LogoPath $script:LogoPath -LogoDarkPath $script:LogoDarkPath
if ($null -ne $initialLogoPath) {
try {
$script:pictureLogo = New-Object System.Windows.Forms.PictureBox
$script:pictureLogo.Location = New-Object System.Drawing.Point(30, 20)
$script:pictureLogo.Size = New-Object System.Drawing.Size(64, 64)
$script:pictureLogo.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom
$script:pictureLogo.BackColor = [System.Drawing.Color]::Transparent
$script:pictureLogo.Image = [System.Drawing.Image]::FromFile($initialLogoPath)
$script:form.Controls.Add($script:pictureLogo)
$titleX = 110
}
catch {
$script:pictureLogo = $null
$titleX = 30
}
}
$script:lblTitle = New-Label -Text "DeployCenter" `
-X $titleX -Y 17 -Width 600 -Height 42 -Theme $script:Theme -Font $script:FontTitle `
-Color $script:Theme.Text
$script:lblSubtitle = New-Label `
-Text "Environnement de déploiement Windows" `
-X ($titleX + 4) -Y 61 -Width 650 -Height 24 -Theme $script:Theme -Font $script:FontSubtitle `
-Color $script:Theme.Secondary
$script:form.Controls.Add($script:lblTitle)
$script:form.Controls.Add($script:lblSubtitle)
# Menu de selection du theme ; les noms correspondent aux fichiers Themes\<Nom>.psd1
$script:cmbTheme = New-Object System.Windows.Forms.ComboBox
$script:cmbTheme.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
# OwnerDraw permet de colorer aussi la liste deroulante, que WinForms laisse sinon au theme systeme.
$script:cmbTheme.DrawMode = [System.Windows.Forms.DrawMode]::OwnerDrawFixed
$script:cmbTheme.ItemHeight = 24
$script:cmbTheme.Font = $script:FontRegular
$script:cmbTheme.Width = 190
$script:cmbTheme.Height = 32
$script:cmbTheme.Location = New-Object System.Drawing.Point(0, 29)
$script:cmbTheme.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
foreach ($themeName in (Get-DeployCenterThemeNames -ThemesRoot $script:ThemesRoot)) { [void]$script:cmbTheme.Items.Add($themeName) }
$script:cmbTheme.SelectedItem = $script:CurrentTheme
$script:cmbTheme.BackColor = $script:Theme.Panel
$script:cmbTheme.ForeColor = $script:Theme.Text
$script:cmbTheme.Add_DrawItem({
param($sender, $e)
if ($e.Index -lt 0) {
$itemText = [string]$sender.Text
} else {
$itemText = [string]$sender.Items[$e.Index]
}
$isSelected = (($e.State -band [System.Windows.Forms.DrawItemState]::Selected) -ne 0)
if ($isSelected) {
$backgroundColor = $script:Theme.Accent
$foregroundColor = [System.Drawing.Color]::White
} else {
$backgroundColor = $script:Theme.Panel
$foregroundColor = $script:Theme.Text
}
$backgroundBrush = New-Object System.Drawing.SolidBrush($backgroundColor)
try { $e.Graphics.FillRectangle($backgroundBrush, $e.Bounds) }
finally { $backgroundBrush.Dispose() }
if (-not [string]::IsNullOrEmpty($itemText)) {
$textBounds = $e.Bounds
$textBounds.X += 4
[System.Windows.Forms.TextRenderer]::DrawText(
$e.Graphics, $itemText, $sender.Font, $textBounds, $foregroundColor,
([System.Windows.Forms.TextFormatFlags]::Left -bor [System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor [System.Windows.Forms.TextFormatFlags]::EndEllipsis)
)
}
$e.DrawFocusRectangle()
})
$script:form.Controls.Add($script:cmbTheme)
$script:cmbTheme.BringToFront()
# ============================================================
# INFORMATIONS DU PC
# ============================================================
$script:groupPC = New-GroupBox -Text "Informations du PC" `
-X $margin -Y 100 -Width $usableWidth -Height 90 -Theme $script:Theme -Font $script:FontGroup
$script:lblManufacturer = New-Label -Text "Fabricant : -" `
-X 18 -Y 25 -Width 330 -Height 24 -Theme $script:Theme -Font $script:FontRegular
$script:lblModel = New-Label -Text "Modèle : -" `
-X 360 -Y 25 -Width 400 -Height 24 -Theme $script:Theme -Font $script:FontRegular
$script:lblSerial = New-Label -Text "Numéro de série : -" `
-X 18 -Y 52 -Width 330 -Height 24 -Theme $script:Theme -Font $script:FontRegular
$script:lblFirmware = New-Label -Text "Firmware : -" `
-X 360 -Y 52 -Width 220 -Height 24 -Theme $script:Theme -Font $script:FontRegular
$script:lblArchitecture = New-Label -Text "Architecture : -" `
-X 600 -Y 52 -Width 220 -Height 24 -Theme $script:Theme -Font $script:FontRegular
$script:groupPC.Controls.AddRange(@(
$script:lblManufacturer,
$script:lblModel,
$script:lblSerial,
$script:lblFirmware,
$script:lblArchitecture
))
$script:form.Controls.Add($script:groupPC)
# ============================================================
# PREPARATION DU DEPLOIEMENT
# ============================================================
$script:groupDisks = New-GroupBox -Text "Préparation du déploiement" `
-X $margin -Y 205 -Width $usableWidth -Height 270 -Theme $script:Theme -Font $script:FontGroup
$deploymentControls = New-DeployCenterDeploymentControls `
-Parent $script:groupDisks `
-ProfileNames @($script:PartitionProfiles | ForEach-Object { [string]$_.Name }) `
-Theme $script:Theme `
-Font $script:FontSmall
$script:cmbDisk = $deploymentControls.DiskComboBox
$script:btnChooseWindows = $deploymentControls.ChooseSystemButton
$script:lblSelectedWindows = $deploymentControls.SelectedSystemLabel
$script:lblPartitionProfile = $deploymentControls.PartitionProfileLabel
$script:cmbPartitionProfile = $deploymentControls.PartitionProfileComboBox
$script:cmbUnattendProfile = $deploymentControls.UnattendProfileComboBox
$script:DeploymentControls = $deploymentControls
$script:lblHostName = $deploymentControls.HostNameLabel
$script:txtHostName = $deploymentControls.HostNameTextBox
$script:cmbPartitionProfile.SelectedIndex = $script:SelectedPartitionProfileIndex
$script:form.Controls.Add($script:groupDisks)
# ============================================================
# JOURNAL DANS UNE FENETRE DEDIEE
# ============================================================
$logWindow = New-DeployCenterLogWindow -Theme $script:Theme -Font $script:FontRegular
$script:logForm = $logWindow.Form
$script:txtLog = $logWindow.TextBox
$buttonHeight = 38
# En plein ecran, rapprocher les boutons du bas sans empiéter sur le pied de page.
# En mode fenetre, conserver la position actuelle.
$buttonY = if ($script:Windowed) {
$screenHeight - 54 - $buttonHeight
} else {
$screenHeight - 42 - $buttonHeight
}
# BOUTONS JOURNAL, ACTUALISER ET FERMER
# ============================================================
$buttonWidth = 140
$buttonGap = 12
$closeX = $screenWidth - $margin - $buttonWidth
$deployX = $closeX - $buttonGap - $buttonWidth
$refreshX = $deployX - $buttonGap - $buttonWidth
$script:btnLog = New-Button -Text "Journal" `
-X $margin -Y $buttonY -Width $buttonWidth -Height $buttonHeight `
-Theme $script:Theme -Font $script:FontRegular `
-Role "Button"
$script:btnRefresh = New-Button -Text "Actualiser" `
-X $refreshX -Y $buttonY -Width $buttonWidth -Height $buttonHeight `
-Theme $script:Theme -Font $script:FontRegular `
-Role "PrimaryButton"
$script:btnDeploy = New-Button -Text "Installer" `
-X $deployX -Y $buttonY -Width $buttonWidth -Height $buttonHeight `
-Theme $script:Theme -Font $script:FontRegular `
-Role "PrimaryButton"
$script:btnClose = New-Button -Text "Fermer" `
-X $closeX -Y $buttonY -Width $buttonWidth -Height $buttonHeight `
-Theme $script:Theme -Font $script:FontRegular `
-Role "DangerButton"
$script:form.Controls.Add($script:btnLog)
$script:form.Controls.Add($script:btnRefresh)
$script:form.Controls.Add($script:btnDeploy)
$script:form.Controls.Add($script:btnClose)
Add-SessionFooter -TargetForm $script:form -Theme $script:Theme -Font $script:FontSmall `
-IPv4 $script:DeployIPv4 -VncPassword $script:VncPassword -ShowPassword $true
# ============================================================
# APPLICATION DU THEME A TOUS LES CONTROLES
# ============================================================
# ============================================================
# DETECTION DES SOURCES WINDOWS SUR PARTAGE SMB
# ============================================================
# ============================================================
# INVENTAIRE MATERIEL
# ============================================================
# ============================================================
# MISE EN PAGE ADAPTATIVE
# Les grandes zones suivent la fenetre ; les marges et hauteurs de controles restent fixes.
# ============================================================
# ============================================================
# EVENEMENTS
# ============================================================
$script:btnChooseWindows.Add_Click({
Show-WindowsSourcesDialog
if ($script:SelectedWindowsEdition) {
$edition = $script:SelectedWindowsEdition
$sourceDescription = if ($edition.ImagePath) { [string]$edition.ImagePath } else { [string]$edition.SourcePath }
$script:lblSelectedWindows.Text = "{0} (index {1}) — {2}" -f $edition.Name, $edition.Index, $sourceDescription
$script:lblSelectedWindows.ForeColor = $script:Theme.Success
$script:lblSelectedWindows.Name = 'lblSelectedWindows'
$script:cmbPartitionProfile.Enabled = $true
Set-DeployCenterUnattendProfileChoices -ComboBox $script:cmbUnattendProfile `
-Profiles $script:UnattendProfiles -EditionName ([string]$edition.Name) `
-OobeChoice $script:UnattendOobeChoice
}
})
$script:cmbUnattendProfile.Add_SelectedIndexChanged({
$showHostName = $script:cmbUnattendProfile.SelectedItem -and
[string]$script:cmbUnattendProfile.SelectedItem -ne $script:UnattendOobeChoice
Set-DeployCenterUnattendHostNameVisibility -Controls $script:DeploymentControls -ShowHostName ([bool]$showHostName)
})
$script:btnLog.Add_Click({
if ($script:logForm.IsDisposed) { return }
if (-not $script:logForm.Visible) { $script:logForm.Show($script:form) }
$script:logForm.BringToFront()
})
$script:btnRefresh.Add_Click({
Update-HardwareView
})
$script:btnDeploy.Add_Click({
$diskIndex = $null
if ($null -ne $script:cmbDisk.SelectedItem) { $diskIndex = [int]$script:cmbDisk.SelectedItem.Tag }
$diskInfo = if ($null -ne $diskIndex) { Get-SelectedDeployDiskInfo -HardwareInfo $script:HardwareInventory -DiskIndex $diskIndex } else { $null }
$unattendChoice = [string]$script:cmbUnattendProfile.SelectedItem
$unattendProfile = $null
$hostName = $null
if ($unattendChoice -and $unattendChoice -ne $script:UnattendOobeChoice) {
$matchingProfiles = @($script:UnattendProfiles | Where-Object { [string]$_.Name -eq $unattendChoice })
if ($matchingProfiles.Count -ne 1) {
Show-DeployCenterMessage -Message "Le profil Unattend '$unattendChoice' est introuvable ou ambigu." -Title 'Profil Unattend' -Icon Error -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
$unattendProfile = $matchingProfiles[0]
$hostName = [string]$script:txtHostName.Text.Trim()
}
if ($null -eq $diskInfo) {
Show-DeployCenterMessage -Message 'Sélectionne le disque de destination.' -Title 'Déploiement Windows' -Icon Warning -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
if ($null -eq $script:SelectedWindowsEdition) {
Show-DeployCenterMessage -Message 'Sélectionne une édition Windows à installer.' -Title 'Déploiement Windows' -Icon Warning -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
if ($null -ne $unattendProfile -and $unattendProfile.ComputerName -and $hostName -notmatch '^(?=.*[A-Za-z])[A-Za-z0-9](?:[A-Za-z0-9-]{0,13}[A-Za-z0-9])?$') {
Show-DeployCenterMessage -Message "Le nom d’hôte doit contenir de 1 à 15 caractères, commencer et finir par une lettre ou un chiffre, et ne contenir que des lettres, chiffres ou tirets." -Title "Nom d’hôte invalide" -Icon Warning -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
if ($null -ne $unattendProfile) {
try {
Test-DeployCenterUnattendConfiguration -Profile $unattendProfile `
-Configuration $script:Config -HostName $hostName
} catch {
Show-DeployCenterMessage -Message $_.Exception.Message -Title 'Configuration Unattend invalide' -Icon Warning -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
}
if ($script:SelectedPartitionSizing -and [int]$script:SelectedPartitionSizing.DiskIndex -ne [int]$diskInfo.Index) {
Show-DeployCenterMessage -Message 'La taille des partitions correspond à un autre disque. Sélectionne de nouveau le profil de partitionnement.' -Title 'Partitionnement' -Icon Warning -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
return
}
$sizeDescription = 'Tailles définies par le profil'
if ($script:SelectedPartitionSizing -and $script:SelectedPartitionSizing.Inputs.Count -gt 0) {
$sizeParts = @()
foreach ($role in $script:SelectedPartitionSizing.Inputs.Keys) {
$sizeParts += ("{0} : {1} Go" -f $role, $script:SelectedPartitionSizing.Inputs[$role])
}
$sizeParts += ("espace restant : {0:N1} Go" -f $script:SelectedPartitionSizing.RemainderGB)
$sizeDescription = $sizeParts -join ', '
}
$unattendDescription = if ($null -ne $unattendProfile) { "Profil Unattend : $($unattendProfile.Name)`r`nNom d’hôte : $hostName" } else { 'Configuration initiale : Avec OOBE' }
$confirmation = Show-DeployCenterConfirmation `
-Title "Confirmer l’effacement du disque" `
-Message ("Disque {0} — {1} ({2:N1} Go)`r`nProfil de partitionnement : {3}`r`nÉdition : {4}`r`nTailles : {5}`r`n{6}`r`nTout le contenu du disque sera effacé. Cette opération est irréversible." -f $diskInfo.Index, $diskInfo.Model, $diskInfo.SizeGB, $script:SelectedPartitionProfile.Name, $script:SelectedWindowsEdition.Name, $sizeDescription, $unattendDescription) `
-Owner $script:form -Theme $script:Theme -Font $script:FontRegular
if (-not $confirmation) { return }
$progress = New-DeployCenterDeploymentProgress -Theme $script:Theme -Font $script:FontRegular
$script:form.Enabled = $false
try {
$progress.Form.Show($script:form)
[System.Windows.Forms.Application]::DoEvents()
$deploymentLogAction = $script:SourceLogAction
$deploymentProgressMessage = 'Préparation du déploiement…'
$deploymentProgressAction = {
param($Message, $Percent)
if ($null -ne $Message) {
$deploymentProgressMessage = [string]$Message
if ($null -eq $Percent) {
$progress.ProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
}
if ($deploymentLogAction -is [scriptblock]) {
& $deploymentLogAction ([string]$Message) 'INFO'
}
}
if ($null -ne $Percent) {
if ($progress.ProgressBar.Style -ne [System.Windows.Forms.ProgressBarStyle]::Continuous) {
$progress.ProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous
}
$progress.ProgressBar.Value = [int][Math]::Max(0, [Math]::Min(100, $Percent))
$progress.StatusLabel.Text = '{0} ({1} %)' -f $deploymentProgressMessage, $progress.ProgressBar.Value
} elseif ($null -ne $Message) {
$progress.StatusLabel.Text = $deploymentProgressMessage
}
[System.Windows.Forms.Application]::DoEvents()
}.GetNewClosure()
Invoke-DeployCenterWindowsDeployment `
-DiskInfo $diskInfo `
-Profile $script:SelectedPartitionProfile `
-SizingState $script:SelectedPartitionSizing `
-Edition $script:SelectedWindowsEdition `
-HostName $hostName `
-UnattendProfile $unattendProfile `
-UnattendConfiguration $script:Config `
-Firmware $script:HardwareInventory.Firmware `
-TemporaryRoot $script:InventoryDir `
-LogAction $script:SourceLogAction `
-ProgressAction $deploymentProgressAction
$progress.Form.Close()
$script:form.Enabled = $true
$restartAfterDeployment = Show-DeployCenterDeploymentComplete `
-Message 'Windows a été appliqué avec succès. Le déploiement est prêt.' `
-Owner $script:form -Theme $script:Theme -Font $script:FontRegular
if ($restartAfterDeployment) {
$script:RestartAfterDeployment = $true
$script:form.Close()
}
} catch {
if (-not $progress.Form.IsDisposed) { $progress.Form.Close() }
$script:form.Enabled = $true
& $script:SourceLogAction "Échec du déploiement : $($_.Exception.Message)" 'ERROR'
Show-DeployCenterMessage -Message $_.Exception.Message -Title 'Échec du déploiement' -Icon Error -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
} finally {
if (-not $progress.Form.IsDisposed) { $progress.Form.Dispose() }
$script:form.Enabled = $true
if (-not $script:form.IsDisposed -and $script:form.Visible) { $script:form.Activate() }
}
})
$script:btnClose.Add_Click({
$script:form.Close()
})
$script:cmbTheme.Add_SelectedIndexChanged({
if ($script:cmbTheme.SelectedItem) {
Set-DeployCenterTheme -ThemeName ([string]$script:cmbTheme.SelectedItem)
}
})
$script:cmbDisk.Add_SizeChanged({
Set-DiskSelectorWidth
})
$script:cmbPartitionProfile.Add_SelectedIndexChanged({
if ($script:ChangingPartitionProfileSelection) { return }
$selectedIndex = $script:cmbPartitionProfile.SelectedIndex
if ($selectedIndex -ge 0 -and $selectedIndex -lt $script:PartitionProfiles.Count) {
$candidateProfile = $script:PartitionProfiles[$selectedIndex]
$sizeInputs = @($candidateProfile.Partitions | Where-Object { $_.Size.Policy -eq 'UserInputGB' })
if ($sizeInputs.Count -gt 0) {
$selectedDiskTag = if ($null -ne $script:cmbDisk.SelectedItem) { [int]$script:cmbDisk.SelectedItem.Tag } else { $null }
$diskInfo = if ($null -ne $selectedDiskTag) { Get-SelectedDeployDiskInfo -HardwareInfo $script:HardwareInventory -DiskIndex $selectedDiskTag } else { $null }
$initialValues = @{}
if ($script:SelectedPartitionSizing -and $script:SelectedPartitionSizing.ProfileId -eq $candidateProfile.Id) {
$initialValues = $script:SelectedPartitionSizing.Inputs
}
if ($null -eq $diskInfo) {
Show-DeployCenterMessage -Message "Sélectionne d’abord un disque physique." -Title 'Partitionnement' -Icon Information -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
}
if ($null -eq $diskInfo -or -not (Set-PartitionProfileSizingSelection -Profile $candidateProfile -DiskInfo $diskInfo -InitialValues $initialValues)) {
$script:ChangingPartitionProfileSelection = $true
try { $script:cmbPartitionProfile.SelectedIndex = $script:SelectedPartitionProfileIndex }
finally { $script:ChangingPartitionProfileSelection = $false }
return
}
} else {
$script:SelectedPartitionSizing = $null
}
$script:SelectedPartitionProfileIndex = $selectedIndex
$script:SelectedPartitionProfile = $candidateProfile
& $script:SourceLogAction "Profil de partitionnement sélectionné : $($candidateProfile.Name)" 'INFO'
if ($script:SelectedPartitionSizing) {
foreach ($role in $script:SelectedPartitionSizing.Inputs.Keys) {
& $script:SourceLogAction "Taille saisie pour $role : $($script:SelectedPartitionSizing.Inputs[$role]) Go ; espace restant : $($script:SelectedPartitionSizing.RemainderGB) Go" 'INFO'
}
}
}
})
$script:cmbDisk.Add_SelectedIndexChanged({
if ($script:RefreshingDiskList -or $script:ChangingPartitionProfileSelection) { return }
$selectedDiskTag = if ($null -ne $script:cmbDisk.SelectedItem) { [int]$script:cmbDisk.SelectedItem.Tag } else { $null }
$diskInfo = if ($null -ne $selectedDiskTag) { Get-SelectedDeployDiskInfo -HardwareInfo $script:HardwareInventory -DiskIndex $selectedDiskTag } else { $null }
if ($null -eq $diskInfo) { $script:SelectedDiskIndex = $null; return }
$profileSizeInputs = @($script:SelectedPartitionProfile.Partitions | Where-Object { $_.Size.Policy -eq 'UserInputGB' })
if ($profileSizeInputs.Count -gt 0) {
$initialValues = if ($script:SelectedPartitionSizing -and $script:SelectedPartitionSizing.ProfileId -eq $script:SelectedPartitionProfile.Id) {
$script:SelectedPartitionSizing.Inputs
} else { @{} }
if (-not (Set-PartitionProfileSizingSelection -Profile $script:SelectedPartitionProfile -DiskInfo $diskInfo -InitialValues $initialValues)) {
$script:RefreshingDiskList = $true
try {
for ($i = 0; $i -lt $script:cmbDisk.Items.Count; $i++) {
if ([string]$script:cmbDisk.Items[$i].Tag -eq [string]$script:SelectedDiskIndex) { $script:cmbDisk.SelectedIndex = $i; break }
}
} finally { $script:RefreshingDiskList = $false }
return
}
}
$script:SelectedDiskIndex = [string]$diskInfo.Index
})
$script:form.Add_SizeChanged({
Update-DeployCenterLayout
})
$script:form.Add_KeyDown({
param($sender, $eventArgs)
if ($eventArgs.KeyCode -eq [System.Windows.Forms.Keys]::Escape) {
$script:form.Close()
}
elseif ($eventArgs.KeyCode -eq [System.Windows.Forms.Keys]::F5) {
Update-HardwareView
}
})
$script:form.Add_Shown({
# Use the real maximized client width to position the theme button.
Update-DeployCenterLayout
$script:cmbTheme.BringToFront()
& $script:SourceLogAction "DeployCenter demarre."
& $script:SourceLogAction "Dossier : $script:DeployRoot"
& $script:SourceLogAction "Journal : $script:LogFile"
Update-HardwareView
})
$script:form.Add_FormClosed({
if ($null -ne $script:pictureLogo -and $null -ne $script:pictureLogo.Image) {
$script:pictureLogo.Image.Dispose()
}
$script:FontRegular.Dispose()
$script:FontSmall.Dispose()
$script:FontTitle.Dispose()
$script:FontSubtitle.Dispose()
$script:FontGroup.Dispose()
})
# ============================================================
# LANCEMENT
# ============================================================
try {
[void]$script:form.ShowDialog()
}
finally {
if ($script:SelectedWindowsEdition -and $script:SelectedWindowsEdition.MountedImage) {
try { [void](Dismount-DeployCenterInstallImage -Image $script:SelectedWindowsEdition) } catch {
& $script:SourceLogAction "Impossible de démonter l'ISO à la fermeture : $($_.Exception.Message)" 'WARNING'
}
}
if ($script:EnableVnc -and (Get-Command Stop-DeployCenterVnc -ErrorAction SilentlyContinue)) {
Stop-DeployCenterVnc
}
$script:form.Dispose()
if ($script:RestartAfterDeployment) {
try {
Restart-DeployCenterWinPE -LogAction $script:SourceLogAction
} catch {
& $script:SourceLogAction "Échec de la demande de redémarrage : $($_.Exception.Message)" 'ERROR'
}
}
}