# 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 $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: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 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 -lt 990) { throw "Le paramètre DeployCenter.ini '$configKey' doit être d’au moins 990 Mo." } $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\.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 105 -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:lblWinPE = New-Label -Text "Environnement : -" ` -X 18 -Y 78 -Width 350 -Height 22 ` -Theme $script:Theme -Font $script:FontSmall ` -Color $script:Theme.Secondary $script:groupPC.Controls.AddRange(@( $script:lblManufacturer, $script:lblModel, $script:lblSerial, $script:lblFirmware, $script:lblArchitecture, $script:lblWinPE )) $script:form.Controls.Add($script:groupPC) # ============================================================ # PREPARATION DU DEPLOIEMENT # ============================================================ $script:groupDisks = New-GroupBox -Text "Préparation du déploiement" ` -X $margin -Y 220 -Width $usableWidth -Height 226 -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: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 } }) $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 } $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 ($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 ($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 ', ' } $confirmation = Show-DeployCenterConfirmation ` -Title "Confirmer l’effacement du disque" ` -Message ("Disque {0} — {1} ({2:N1} Go)`r`nProfil : {3}`r`nÉdition : {4}`r`nTailles : {5}`r`nNom d’hôte : {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, $hostName) ` -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 ` -Firmware $script:HardwareInventory.Firmware ` -TemporaryRoot $script:InventoryDir ` -LogAction $script:SourceLogAction ` -ProgressAction $deploymentProgressAction $progress.Form.Close() $script:form.Enabled = $true Show-DeployCenterMessage -Message 'Windows a été appliqué. Le déploiement est prêt à être finalisé par le redémarrage de WinPE.' -Title 'Déploiement terminé' -Icon Information -Owner $script:form -Theme $script:Theme -Font $script:FontRegular } 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 $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() }