Files
DeployCenter/Modules/UI.psm1
T
benoit 2212e25d31 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.
2026-10-18 04:14:17 +02:00

2052 lines
100 KiB
PowerShell

function Set-DeployCenterButtonStyle {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.Button]$Button,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[ValidateSet('Button','PrimaryButton','DangerButton')][string]$Role = 'Button'
)
$style = $Theme[$Role]
$Button.BackColor = $style.Background
$Button.ForeColor = $style.Foreground
$Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$Button.UseVisualStyleBackColor = $false
$Button.FlatAppearance.BorderSize = 1
$Button.FlatAppearance.BorderColor = $style.Border
$Button.FlatAppearance.MouseOverBackColor = $style.HoverBackground
$Button.FlatAppearance.MouseDownBackColor = $style.PressedBackground
if (-not $Button.Enabled) {
$Button.BackColor = $style.DisabledBackground
$Button.ForeColor = $style.DisabledForeground
}
}
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)][AllowEmptyCollection()][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((Get-DeployCenterText -Text $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 = Get-DeployCenterText -Text 'Partitioning profile'
$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, 143)
$ComboBox.Location = New-Object System.Drawing.Point(12, 161)
$ComboBox.Size = New-Object System.Drawing.Size([Math]::Max(250, $Width), 26)
}
function New-DeployCenterDeploymentControls {
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
)
$diskLabel = New-Object System.Windows.Forms.Label
$diskLabel.Text = Get-DeployCenterText -Text 'Physical disk'
$diskLabel.Location = New-Object System.Drawing.Point(12, 20)
$diskLabel.Size = New-Object System.Drawing.Size(220, 18)
$diskLabel.Font = $Font
$diskLabel.ForeColor = $Theme.Text
$diskLabel.BackColor = [System.Drawing.Color]::Transparent
$diskCombo = New-DeployCenterComboBox -Name 'cmbDisk' -Theme $Theme -Font $Font -Items @()
$diskCombo.Location = New-Object System.Drawing.Point(12, 39)
$diskCombo.Size = New-Object System.Drawing.Size([Math]::Max(250, $Parent.ClientSize.Width - 24), 26)
$diskCombo.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$systemLabel = New-Object System.Windows.Forms.Label
$systemLabel.Text = Get-DeployCenterText -Text 'System to install'
$systemLabel.Location = New-Object System.Drawing.Point(12, 71)
$systemLabel.Size = New-Object System.Drawing.Size(240, 18)
$systemLabel.Font = $Font
$systemLabel.ForeColor = $Theme.Text
$systemLabel.BackColor = [System.Drawing.Color]::Transparent
$chooseSystemButton = New-Object System.Windows.Forms.Button
$chooseSystemButton.Name = 'btnChooseWindows'
$chooseSystemButton.Text = Get-DeployCenterText -Text 'Browse…'
$chooseSystemButton.Size = New-Object System.Drawing.Size(132, 28)
$chooseSystemButton.Location = New-Object System.Drawing.Point([Math]::Max(12, $Parent.ClientSize.Width - 144), 67)
$chooseSystemButton.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
Set-DeployCenterButtonStyle -Button $chooseSystemButton -Theme $Theme -Role 'Button'
$selectedSystemLabel = New-Object System.Windows.Forms.Label
$selectedSystemLabel.Name = 'lblSelectedWindows'
$selectedSystemLabel.Text = Get-DeployCenterText -Text 'No system selected'
$selectedSystemLabel.Location = New-Object System.Drawing.Point(12, 98)
$selectedSystemLabel.Size = New-Object System.Drawing.Size([Math]::Max(250, $Parent.ClientSize.Width - 24), 22)
$selectedSystemLabel.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$selectedSystemLabel.Font = $Font
$selectedSystemLabel.AutoEllipsis = $true
$selectedSystemLabel.ForeColor = $Theme.Secondary
$selectedSystemLabel.BackColor = [System.Drawing.Color]::Transparent
$driverStatusLabel = New-Object System.Windows.Forms.Label
$driverStatusLabel.Name = 'lblDriverStatus'
$driverStatusLabel.Text = Get-DeployCenterText -Text 'Drivers: select a Windows edition to search for a driver pack.'
$driverStatusLabel.Location = New-Object System.Drawing.Point(12, 119)
$driverStatusLabel.Size = New-Object System.Drawing.Size([Math]::Max(250, $Parent.ClientSize.Width - 24), 18)
$driverStatusLabel.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$driverStatusLabel.Font = $Font
$driverStatusLabel.AutoEllipsis = $true
$driverStatusLabel.ForeColor = $Theme.Secondary
$driverStatusLabel.BackColor = [System.Drawing.Color]::Transparent
$driverToolTip = New-Object System.Windows.Forms.ToolTip
$driverToolTip.SetToolTip($driverStatusLabel, $driverStatusLabel.Text)
$profileSelector = New-DeployCenterPartitionProfileSelector -Parent $Parent -ProfileNames $ProfileNames -Theme $Theme -Font $Font
$profileSelector.ComboBox.Enabled = $false
$unattendLabel = New-Object System.Windows.Forms.Label
$unattendLabel.Name = 'lblUnattendProfile'
$unattendLabel.Text = Get-DeployCenterText -Text 'First-run configuration'
$unattendLabel.Location = New-Object System.Drawing.Point(12, 194)
$unattendLabel.Size = New-Object System.Drawing.Size(320, 17)
$unattendLabel.Font = $Font
$unattendLabel.ForeColor = $Theme.Text
$unattendLabel.BackColor = [System.Drawing.Color]::Transparent
$unattendCombo = New-DeployCenterComboBox -Name 'cmbUnattendProfile' -Theme $Theme -Font $Font `
-Items @('Use OOBE')
$unattendCombo.Location = New-Object System.Drawing.Point(12, 211)
$unattendCombo.Size = New-Object System.Drawing.Size([Math]::Max(250, $Parent.ClientSize.Width - 24), 26)
$unattendCombo.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$unattendCombo.SelectedIndex = 0
$hostLabel = New-Object System.Windows.Forms.Label
$hostLabel.Name = 'lblHostName'
$hostLabel.Text = Get-DeployCenterText -Text "Computer name"
$hostLabel.Location = New-Object System.Drawing.Point(12, 244)
$hostLabel.Size = New-Object System.Drawing.Size(220, 24)
$hostLabel.Font = $Font
$hostLabel.ForeColor = $Theme.Text
$hostLabel.BackColor = [System.Drawing.Color]::Transparent
$hostTextBox = New-Object System.Windows.Forms.TextBox
$hostTextBox.Name = 'txtHostName'
$hostTextBox.Location = New-Object System.Drawing.Point([Math]::Max(244, $Parent.ClientSize.Width - 202), 241)
$hostTextBox.Size = New-Object System.Drawing.Size(190, 26)
$hostTextBox.MaxLength = 15
$hostTextBox.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
$hostTextBox.BackColor = $Theme.PanelAlt
$hostTextBox.ForeColor = $Theme.Text
$hostTextBox.Font = $Font
$hostLabel.Visible = $false
$hostTextBox.Visible = $false
$Parent.Controls.AddRange(@($diskLabel, $diskCombo, $systemLabel, $chooseSystemButton, $selectedSystemLabel, $driverStatusLabel, $unattendLabel, $unattendCombo, $hostLabel, $hostTextBox))
return [pscustomobject]@{
DiskComboBox = $diskCombo
ChooseSystemButton = $chooseSystemButton
SelectedSystemLabel = $selectedSystemLabel
DriverStatusLabel = $driverStatusLabel
DriverToolTip = $driverToolTip
PartitionProfileLabel = $profileSelector.Label
PartitionProfileComboBox = $profileSelector.ComboBox
UnattendProfileLabel = $unattendLabel
UnattendProfileComboBox = $unattendCombo
HostNameLabel = $hostLabel
HostNameTextBox = $hostTextBox
}
}
function Set-DeployCenterUnattendHostNameVisibility {
param([Parameter(Mandatory=$true)]$Controls, [Parameter(Mandatory=$true)][bool]$ShowHostName)
$Controls.HostNameLabel.Visible = $ShowHostName
$Controls.HostNameTextBox.Visible = $ShowHostName
}
function Set-DeployCenterUnattendProfileChoices {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.ComboBox]$ComboBox,
[Parameter(Mandatory=$true)][AllowEmptyCollection()][object[]]$Profiles,
[AllowNull()][string]$EditionName,
[Parameter(Mandatory=$true)][string]$OobeChoice
)
$isServerEdition = -not [string]::IsNullOrWhiteSpace($EditionName) -and
$EditionName.IndexOf('server', [StringComparison]::OrdinalIgnoreCase) -ge 0
$target = if ($isServerEdition) { 'Server' } else { 'Workstation' }
$choices = @($OobeChoice)
if (-not [string]::IsNullOrWhiteSpace($EditionName)) {
$choices += @($Profiles | Where-Object { [string]$_.Target -eq $target } | ForEach-Object { [string]$_.Name })
}
$previousChoice = [string]$ComboBox.SelectedItem
$ComboBox.BeginUpdate()
try {
$ComboBox.Items.Clear()
foreach ($choice in $choices) { [void]$ComboBox.Items.Add((Get-DeployCenterText -Text ([string]$choice))) }
$selectionIndex = $ComboBox.Items.IndexOf($previousChoice)
if ($selectionIndex -lt 0) { $selectionIndex = 0 }
$ComboBox.SelectedIndex = $selectionIndex
} finally {
$ComboBox.EndUpdate()
}
}
function Set-DeployCenterDeploymentControlsLayout {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.GroupBox]$Parent,
[Parameter(Mandatory=$true)]$Controls
)
$width = [Math]::Max(250, $Parent.ClientSize.Width - 24)
$Controls.DiskComboBox.Size = New-Object System.Drawing.Size($width, 26)
$Controls.ChooseSystemButton.Location = New-Object System.Drawing.Point([Math]::Max(12, $Parent.ClientSize.Width - 144), 67)
$Controls.SelectedSystemLabel.Size = New-Object System.Drawing.Size($width, 22)
$Controls.DriverStatusLabel.Size = New-Object System.Drawing.Size($width, 18)
Set-DeployCenterPartitionProfileSelectorLayout -Label $Controls.PartitionProfileLabel -ComboBox $Controls.PartitionProfileComboBox -Width $width
$Controls.UnattendProfileComboBox.Size = New-Object System.Drawing.Size($width, 26)
$Controls.HostNameTextBox.Location = New-Object System.Drawing.Point([Math]::Max(244, $Parent.ClientSize.Width - 202), 241)
$Controls.HostNameTextBox.Size = New-Object System.Drawing.Size(190, 26)
}
function New-DeployCenterLogWindow {
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font
)
$form = New-Object System.Windows.Forms.Form
$form.Text = Get-DeployCenterText -Text 'DeployCenter log'
$form.StartPosition = 'CenterParent'
$form.Size = New-Object System.Drawing.Size(900, 500)
$form.MinimumSize = New-Object System.Drawing.Size(560, 300)
$form.ShowInTaskbar = $false
$form.BackColor = $Theme.Background
$form.ForeColor = $Theme.Text
$form.Font = $Font
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Name = 'txtLog'
$textBox.Location = New-Object System.Drawing.Point(12, 12)
$textBox.Size = New-Object System.Drawing.Size(860, 440)
$textBox.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$textBox.Multiline = $true
$textBox.ReadOnly = $true
$textBox.WordWrap = $false
$textBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Both
$textBox.BackColor = $Theme.PanelAlt
$textBox.ForeColor = $Theme.Text
$textBox.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle
$textBox.Font = [System.Drawing.Font]::new('Consolas', [single]9, [System.Drawing.FontStyle]::Regular)
$form.Controls.Add($textBox)
$form.Add_FormClosing({ if ($_.CloseReason -eq [System.Windows.Forms.CloseReason]::UserClosing) { $_.Cancel = $true; $form.Hide() } }.GetNewClosure())
return [pscustomobject]@{ Form = $form; TextBox = $textBox }
}
function Show-DeployCenterPartitionSizingDialog {
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)][double]$AvailableGB,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[System.Windows.Forms.Form]$Owner = $null,
[System.Collections.IDictionary]$InitialValues = @{}
)
$inputs = @($Profile.Partitions | Where-Object { [string]$_.Size.Policy -eq 'UserInputGB' })
$remainder = @($Profile.Partitions | Where-Object { [string]$_.Size.Policy -eq 'RemainderAfterInputs' })[0]
if ($inputs.Count -eq 0 -or $null -eq $remainder) { return $null }
$minimumRemainder = [double]$remainder.Size.MinimumGB
$minimumInputs = 0.0
foreach ($inputDefinition in $inputs) { $minimumInputs += [double]$inputDefinition.Size.MinimumGB }
if ($AvailableGB -lt ($minimumInputs + $minimumRemainder)) {
Show-DeployCenterMessage `
-Message "The available space on this disk is insufficient for profile '$($Profile.Name)'." `
-Title 'Partitioning unavailable' -Icon Warning -Owner $Owner -Theme $Theme -Font $Font
return $null
}
$dialog = New-Object System.Windows.Forms.Form
$dialog.Text = Get-DeployCenterText -Text 'Partition sizes'
$dialog.StartPosition = if ($Owner) { 'CenterParent' } else { 'CenterScreen' }
$dialog.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$dialog.ShowInTaskbar = $false
$dialog.MinimizeBox = $false
$dialog.MaximizeBox = $false
$dialog.BackColor = $Theme.Background
$dialog.ForeColor = $Theme.Text
$dialog.Font = $Font
$dialog.ClientSize = New-Object System.Drawing.Size(470, (152 + ($inputs.Count * 42)))
$capacityLabel = New-Object System.Windows.Forms.Label
$capacityLabel.Location = New-Object System.Drawing.Point(18, 16)
$capacityLabel.Size = New-Object System.Drawing.Size(430, 34)
$recoveryPartition = @($Profile.Partitions | Where-Object { [string]$_.Role -eq 'Recovery' }) | Select-Object -First 1
$winreSizeMB = if ($recoveryPartition) { [int]$recoveryPartition.Size.Value } else { 1024 }
$capacityLabel.Text = (Get-DeployCenterText -Text 'Estimated capacity after EFI, MSR and WinRE reservations: {0:N1} GB.`r`nConfigured WinRE size: {1} MB.') -f $AvailableGB, $winreSizeMB
$capacityLabel.ForeColor = $Theme.Text
$capacityLabel.BackColor = [System.Drawing.Color]::Transparent
$capacityLabel.Font = $Font
$dialog.Controls.Add($capacityLabel)
$inputControls = [System.Collections.Generic.List[object]]::new()
for ($i = 0; $i -lt $inputs.Count; $i++) {
$definition = $inputs[$i]
$rowY = 60 + ($i * 42)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(18, ($rowY + 4))
$label.Size = New-Object System.Drawing.Size(250, 26)
$label.Text = Get-DeployCenterText -Text ([string]$definition.Size.Label)
$label.ForeColor = $Theme.Text
$label.BackColor = [System.Drawing.Color]::Transparent
$label.Font = $Font
$dialog.Controls.Add($label)
$otherMinimums = 0.0
for ($j = 0; $j -lt $inputs.Count; $j++) {
if ($j -ne $i) { $otherMinimums += [double]$inputs[$j].Size.MinimumGB }
}
$maximum = [math]::Floor($AvailableGB - $minimumRemainder - $otherMinimums)
$minimum = [double]$definition.Size.MinimumGB
$numeric = New-Object System.Windows.Forms.NumericUpDown
$numeric.Location = New-Object System.Drawing.Point(292, $rowY)
$numeric.Size = New-Object System.Drawing.Size(150, 28)
$numeric.Minimum = [decimal]$minimum
$numeric.Maximum = [decimal]$maximum
$numeric.DecimalPlaces = 0
$numeric.Increment = 1
$numeric.BackColor = $Theme.PanelAlt
$numeric.ForeColor = $Theme.Text
$numeric.Font = $Font
$initialValue = $null
if ($InitialValues.Contains([string]$definition.Role)) { $initialValue = [double]$InitialValues[[string]$definition.Role] }
if ($null -eq $initialValue) {
$initialValue = [math]::Floor($AvailableGB * ([double]$definition.Size.DefaultPercent / 100))
}
$initialValue = [math]::Max($minimum, [math]::Min($maximum, $initialValue))
$numeric.Value = [decimal]$initialValue
$inputControls.Add([pscustomobject]@{ Role = [string]$definition.Role; Control = $numeric })
$dialog.Controls.Add($numeric)
}
$remainderLabel = New-Object System.Windows.Forms.Label
$remainderLabel.Location = New-Object System.Drawing.Point(18, (62 + ($inputs.Count * 42)))
$remainderLabel.Size = New-Object System.Drawing.Size(430, 26)
$remainderLabel.ForeColor = $Theme.Secondary
$remainderLabel.BackColor = [System.Drawing.Color]::Transparent
$remainderLabel.Font = $Font
$dialog.Controls.Add($remainderLabel)
$buttonPanel = New-Object System.Windows.Forms.Panel
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$buttonPanel.Height = 58
$buttonPanel.BackColor = $Theme.PanelAlt
$dialog.Controls.Add($buttonPanel)
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Text = Get-DeployCenterText -Text 'Cancel'
$cancelButton.Location = New-Object System.Drawing.Point(258, 10)
$cancelButton.Size = New-Object System.Drawing.Size(92, 36)
$cancelButton.Font = $Font
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
Set-DeployCenterButtonStyle -Button $cancelButton -Theme $Theme -Role 'Button'
$buttonPanel.Controls.Add($cancelButton)
$validateButton = New-Object System.Windows.Forms.Button
$validateButton.Text = Get-DeployCenterText -Text 'Select'
$validateButton.Location = New-Object System.Drawing.Point(358, 10)
$validateButton.Size = New-Object System.Drawing.Size(92, 36)
$validateButton.Font = $Font
Set-DeployCenterButtonStyle -Button $validateButton -Theme $Theme -Role 'PrimaryButton'
$buttonPanel.Controls.Add($validateButton)
$dialog.Tag = [pscustomobject]@{ InputControls = $inputControls; AvailableGB = $AvailableGB }
$updateRemainder = {
$sum = 0.0
foreach ($inputControl in $dialog.Tag.InputControls) {
$inputValue = [double]$inputControl.Control.Value
$typedValue = 0.0
if ([double]::TryParse([string]$inputControl.Control.Text, [ref]$typedValue)) { $inputValue = $typedValue }
$sum += $inputValue
}
$remainingGB = $dialog.Tag.AvailableGB - $sum
$remainderDrive = [string]$remainder.DriveLetter
$remainderName = if ($remainderDrive) { "$remainderDrive`:" } else { [string]$remainder.Name }
$remainderLabel.Text = (Get-DeployCenterText -Text '{0}: {1:N1} GB (remaining space)') -f $remainderName, $remainingGB
$remainderLabel.ForeColor = if ($remainingGB -lt $minimumRemainder) { $Theme.Danger } else { $Theme.Secondary }
$validateButton.Enabled = ($remainingGB -ge $minimumRemainder)
}.GetNewClosure()
foreach ($inputControl in $inputControls) {
$inputControl.Control.Add_ValueChanged($updateRemainder)
$inputControl.Control.Add_TextChanged($updateRemainder)
}
& $updateRemainder
$validateButton.Add_Click({
$inputValues = @{}
foreach ($inputControl in $dialog.Tag.InputControls) { $inputValues[$inputControl.Role] = [double]$inputControl.Control.Value }
$sum = 0.0
foreach ($value in $inputValues.Values) { $sum += [double]$value }
$dialog.Tag = [pscustomobject]@{
Inputs = $inputValues
RemainderRole = [string]$remainder.Role
RemainderGB = [math]::Round(($dialog.Tag.AvailableGB - $sum), 1)
AvailableGB = $dialog.Tag.AvailableGB
}
$dialog.DialogResult = [System.Windows.Forms.DialogResult]::OK
$dialog.Close()
})
$dialog.AcceptButton = $validateButton
$dialog.CancelButton = $cancelButton
if ($Owner) { $result = $dialog.ShowDialog($Owner) } else { $result = $dialog.ShowDialog() }
$selection = if ($result -eq [System.Windows.Forms.DialogResult]::OK) { $dialog.Tag } else { $null }
$dialog.Dispose()
return $selection
}
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 = Get-DeployCenterText -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 = Get-DeployCenterText -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 = Get-DeployCenterText -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()
}
function Show-DeployCenterDeploymentComplete {
param(
[Parameter(Mandatory=$true)][string]$Message,
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Owner,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[bool]$RestartByDefault = $true
)
$dialog = New-Object System.Windows.Forms.Form
$dialog.Text = Get-DeployCenterText -Text 'Deployment complete'
$dialog.StartPosition = 'CenterParent'
$dialog.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$dialog.ShowInTaskbar = $false
$dialog.MinimizeBox = $false
$dialog.MaximizeBox = $false
$dialog.BackColor = $Theme.Background
$dialog.ForeColor = $Theme.Text
$dialog.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)
$dialog.ClientSize = New-Object System.Drawing.Size(470, ($messageHeight + 126))
$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 = 'i'
$iconLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$iconLabel.Font = New-Object System.Drawing.Font('Segoe UI', [single]16, [System.Drawing.FontStyle]::Bold)
$iconLabel.ForeColor = $Theme.Accent
$iconLabel.BackColor = [System.Drawing.Color]::Transparent
$dialog.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 = Get-DeployCenterText -Text $Message
$messageLabel.ForeColor = $Theme.Text
$messageLabel.BackColor = [System.Drawing.Color]::Transparent
$messageLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$messageLabel.Font = $Font
$dialog.Controls.Add($messageLabel)
$restartCheckBox = New-Object System.Windows.Forms.CheckBox
$restartCheckBox.Text = Get-DeployCenterText -Text 'Restart this computer when closing'
$restartCheckBox.Location = New-Object System.Drawing.Point(64, ($messageHeight + 34))
$restartCheckBox.Size = New-Object System.Drawing.Size(380, 26)
$restartCheckBox.Checked = $RestartByDefault
$restartCheckBox.ForeColor = $Theme.Text
$restartCheckBox.BackColor = [System.Drawing.Color]::Transparent
$restartCheckBox.Font = $Font
$dialog.Controls.Add($restartCheckBox)
$buttonPanel = New-Object System.Windows.Forms.Panel
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$buttonPanel.Height = 58
$buttonPanel.BackColor = $Theme.PanelAlt
$dialog.Controls.Add($buttonPanel)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = Get-DeployCenterText -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)
$dialog.AcceptButton = $okButton
$dialog.CancelButton = $okButton
try {
$result = $dialog.ShowDialog($Owner)
return ($result -eq [System.Windows.Forms.DialogResult]::OK -and $restartCheckBox.Checked)
} finally {
$dialog.Dispose()
}
}
function Show-DeployCenterConfirmation {
param(
[Parameter(Mandatory=$true)][string]$Title,
[Parameter(Mandatory=$true)][string]$Message,
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Owner,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font
)
$dialog = New-Object System.Windows.Forms.Form
$dialog.Text = Get-DeployCenterText -Text $Title
$dialog.StartPosition = 'CenterParent'
$dialog.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$dialog.ShowInTaskbar = $false
$dialog.MinimizeBox = $false
$dialog.MaximizeBox = $false
$dialog.BackColor = $Theme.Background
$dialog.ForeColor = $Theme.Text
$dialog.Font = $Font
$dialogWidth = 570
$dialogMargin = 18
$labelTop = 18
$labelWidth = $dialogWidth - (2 * $dialogMargin)
$labelHeight = [Math]::Max(120, [System.Windows.Forms.TextRenderer]::MeasureText(
$Message,
$Font,
(New-Object System.Drawing.Size($labelWidth, 0)),
[System.Windows.Forms.TextFormatFlags]::WordBreak
).Height + 12)
$buttonTop = $labelTop + $labelHeight + 18
$dialog.ClientSize = New-Object System.Drawing.Size($dialogWidth, ($buttonTop + 36 + 16))
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point($dialogMargin, $labelTop)
$label.Size = New-Object System.Drawing.Size($labelWidth, $labelHeight)
$label.Text = Get-DeployCenterText -Text $Message
$label.ForeColor = $Theme.Text
$label.BackColor = [System.Drawing.Color]::Transparent
$label.Font = $Font
$dialog.Controls.Add($label)
$cancel = New-Object System.Windows.Forms.Button
$cancel.Text = Get-DeployCenterText -Text 'Cancel'
$cancelWidth = 100
$confirmWidth = 134
$buttonGap = 10
$confirmLeft = $dialogWidth - $dialogMargin - $confirmWidth
$cancel.Location = New-Object System.Drawing.Point(($confirmLeft - $buttonGap - $cancelWidth), $buttonTop)
$cancel.Size = New-Object System.Drawing.Size($cancelWidth, 36)
$cancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
Set-DeployCenterButtonStyle -Button $cancel -Theme $Theme -Role 'Button'
$dialog.Controls.Add($cancel)
$confirm = New-Object System.Windows.Forms.Button
$confirm.Text = Get-DeployCenterText -Text 'Erase and install'
$confirm.Location = New-Object System.Drawing.Point($confirmLeft, $buttonTop)
$confirm.Size = New-Object System.Drawing.Size($confirmWidth, 36)
$confirm.DialogResult = [System.Windows.Forms.DialogResult]::Yes
Set-DeployCenterButtonStyle -Button $confirm -Theme $Theme -Role 'DangerButton'
$dialog.Controls.Add($confirm)
$dialog.CancelButton = $cancel
$dialog.AcceptButton = $confirm
return ($dialog.ShowDialog($Owner) -eq [System.Windows.Forms.DialogResult]::Yes)
}
function Add-DeployCenterApplicationHeader {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$TargetForm,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$TitleFont,
[Parameter(Mandatory=$true)][System.Drawing.Font]$SubtitleFont,
[Parameter(Mandatory=$true)][System.Drawing.Font]$ThemeFont,
[AllowNull()][System.Drawing.Image]$LogoImage,
[string[]]$ThemeNames = @(),
[string]$SelectedTheme = '',
[bool]$ShowThemeSelector = $true,
[bool]$ThemeSelectorEnabled = $true,
[bool]$CloneLogo = $false
)
$logo = $null
$titleX = 30
if ($null -ne $LogoImage) {
$logo = New-Object System.Windows.Forms.PictureBox
$logo.Location = New-Object System.Drawing.Point(30, 20)
$logo.Size = New-Object System.Drawing.Size(64, 64)
$logo.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom
$logo.BackColor = [System.Drawing.Color]::Transparent
$logo.Image = if ($CloneLogo) { $LogoImage.Clone() } else { $LogoImage }
$TargetForm.Controls.Add($logo)
$titleX = 110
}
$title = New-Label -Text 'DeployCenter' -X $titleX -Y 17 -Width 600 -Height 42 `
-Theme $Theme -Font $TitleFont -Color $Theme.Text
$subtitle = New-Label -Text (Get-DeployCenterText -Text 'Windows deployment environment') `
-X ($titleX + 4) -Y 61 -Width 650 -Height 24 -Theme $Theme -Font $SubtitleFont -Color $Theme.Secondary
$TargetForm.Controls.Add($title)
$TargetForm.Controls.Add($subtitle)
$themeSelector = $null
if ($ShowThemeSelector) {
$themeSelector = New-Object System.Windows.Forms.ComboBox
$themeSelector.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
$themeSelector.DrawMode = [System.Windows.Forms.DrawMode]::OwnerDrawFixed
$themeSelector.ItemHeight = 24
$themeSelector.Font = $ThemeFont
$themeSelector.Width = 190
$themeSelector.Height = 32
$themeSelector.Location = New-Object System.Drawing.Point([Math]::Max(30, ($TargetForm.ClientSize.Width - 220)), 29)
$themeSelector.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
foreach ($themeName in $ThemeNames) { [void]$themeSelector.Items.Add($themeName) }
$themeSelector.SelectedItem = $SelectedTheme
$themeSelector.BackColor = $Theme.Panel
$themeSelector.ForeColor = $Theme.Text
$themeSelector.Enabled = $ThemeSelectorEnabled
$themeSelector.Add_DrawItem({
param($sender, $e)
$itemText = if ($e.Index -lt 0) { [string]$sender.Text } else { [string]$sender.Items[$e.Index] }
$isSelected = (($e.State -band [System.Windows.Forms.DrawItemState]::Selected) -ne 0)
$backgroundColor = if ($isSelected) { $script:Theme.Accent } else { $script:Theme.Panel }
$foregroundColor = if ($isSelected) { [System.Drawing.Color]::White } else { $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()
})
$TargetForm.Controls.Add($themeSelector)
$themeSelector.BringToFront()
}
return [pscustomobject]@{ Logo = $logo; Title = $title; Subtitle = $subtitle; ThemeSelector = $themeSelector }
}
function New-DeployCenterDeploymentProgress {
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[Parameter(Mandatory=$true)][System.Drawing.Font]$FooterFont,
[Parameter(Mandatory=$true)][AllowEmptyCollection()][string[]]$Steps,
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Owner,
[AllowNull()][System.Drawing.Image]$LogoImage,
[Parameter(Mandatory=$true)][System.Drawing.Font]$TitleFont,
[Parameter(Mandatory=$true)][System.Drawing.Font]$SubtitleFont,
[bool]$Windowed = $false,
[AllowNull()][string]$IPv4,
[AllowNull()][string]$VncPassword
)
$form = New-Object System.Windows.Forms.Form
$form.Text = Get-DeployCenterText -Text 'Windows deployment'
$form.StartPosition = if ($Windowed) { 'CenterParent' } else { 'Manual' }
$form.FormBorderStyle = if ($Windowed) { [System.Windows.Forms.FormBorderStyle]::FixedDialog } else { [System.Windows.Forms.FormBorderStyle]::None }
$form.ControlBox = $false
$form.ShowInTaskbar = $false
$screen = [System.Windows.Forms.Screen]::FromControl($Owner)
$workingArea = $screen.WorkingArea
if ($Windowed) {
$clientWidth = [Math]::Min(($Owner.ClientSize.Width - 40), ($workingArea.Width - 48))
$clientHeight = [Math]::Min(($Owner.ClientSize.Height - 28), ($workingArea.Height - 28))
$clientWidth = [Math]::Max(420, $clientWidth)
$clientHeight = [Math]::Max(360, $clientHeight)
} else {
$clientWidth = $screen.Bounds.Width
$clientHeight = $screen.Bounds.Height
$form.Bounds = $screen.Bounds
}
$form.ClientSize = New-Object System.Drawing.Size($clientWidth, $clientHeight)
$form.BackColor = $Theme.Background
$form.ForeColor = $Theme.Text
$form.Font = $Font
[void](Add-DeployCenterApplicationHeader -TargetForm $form -Theme $Theme `
-TitleFont $TitleFont -SubtitleFont $SubtitleFont -ThemeFont $Font `
-LogoImage $LogoImage -ShowThemeSelector:$false -CloneLogo:$true)
$heading = New-Object System.Windows.Forms.Label
$heading.Location = New-Object System.Drawing.Point(30, 95)
$heading.Size = New-Object System.Drawing.Size(($clientWidth - 60), 22)
$heading.Text = Get-DeployCenterText -Text 'Deployment steps'
$heading.ForeColor = $Theme.Text
$heading.BackColor = [System.Drawing.Color]::Transparent
$heading.Font = $Font
$form.Controls.Add($heading)
$list = New-Object System.Windows.Forms.Panel
$list.Location = New-Object System.Drawing.Point(12, 120)
$footerHeight = 26
$list.Size = New-Object System.Drawing.Size(($clientWidth - 24), ($clientHeight - 120 - $footerHeight - 12))
$list.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right -bor [System.Windows.Forms.AnchorStyles]::Bottom
$list.AutoScroll = $true
$list.BackColor = $Theme.Background
$form.Controls.Add($list)
$rows = @()
$rowTop = 0
foreach ($step in $Steps) {
$rowPanel = New-Object System.Windows.Forms.Panel
$rowPanel.Location = New-Object System.Drawing.Point(0, $rowTop)
$listWidth = $list.ClientSize.Width
$rowPanel.Size = New-Object System.Drawing.Size(($listWidth - 8), 20)
$rowPanel.BackColor = $Theme.Background
$name = New-Object System.Windows.Forms.Label
$name.Location = New-Object System.Drawing.Point(6, 0)
$name.Size = New-Object System.Drawing.Size([Math]::Max(140, ($listWidth - 380)), 20)
$name.Text = Get-DeployCenterText -Text $step
$name.ForeColor = $Theme.Text
$name.BackColor = [System.Drawing.Color]::Transparent
$name.Font = $Font
$name.AutoEllipsis = $true
$rowPanel.Controls.Add($name)
$state = New-Object System.Windows.Forms.Label
$state.Location = New-Object System.Drawing.Point(($listWidth - 158), 0)
$state.Size = New-Object System.Drawing.Size(145, 20)
$state.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$state.ForeColor = $Theme.Secondary
$state.BackColor = [System.Drawing.Color]::Transparent
$state.Font = $Font
$state.Text = Get-DeployCenterText -Text 'Upcoming'
$rowPanel.Controls.Add($state)
$bar = New-Object System.Windows.Forms.ProgressBar
$bar.Location = New-Object System.Drawing.Point(($listWidth - 350), 4)
$bar.Size = New-Object System.Drawing.Size(180, 12)
$bar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous
$bar.Visible = $false
$rowPanel.Controls.Add($bar)
$list.Controls.Add($rowPanel)
$rows += [pscustomobject]@{ Key = [string]$step; Name = $name; Panel = $rowPanel; State = $state; Bar = $bar; Started = $null; Complete = $false }
$rowTop += 20
}
$completionPanel = New-Object System.Windows.Forms.Panel
$completionPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$completionPanel.Height = 88
$completionPanel.BackColor = $Theme.PanelAlt
$completionPanel.Visible = $false
$completionLabel = New-Object System.Windows.Forms.Label
$completionLabel.Location = New-Object System.Drawing.Point(12, 5)
$completionLabel.Size = New-Object System.Drawing.Size(($clientWidth - 40), 34)
$completionLabel.ForeColor = $Theme.Text
$completionLabel.BackColor = [System.Drawing.Color]::Transparent
$completionLabel.Font = $Font
$completionLabel.AutoEllipsis = $true
$completionPanel.Controls.Add($completionLabel)
$restartCheckBox = New-Object System.Windows.Forms.CheckBox
$restartCheckBox.Text = Get-DeployCenterText -Text 'Restart this computer when closing'
$restartCheckBox.Location = New-Object System.Drawing.Point(12, 48)
$restartCheckBox.Size = New-Object System.Drawing.Size(($clientWidth - 160), 28)
$restartCheckBox.ForeColor = $Theme.Text
$restartCheckBox.BackColor = [System.Drawing.Color]::Transparent
$restartCheckBox.Font = $Font
$completionPanel.Controls.Add($restartCheckBox)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = Get-DeployCenterText -Text 'OK'
$okButton.Location = New-Object System.Drawing.Point(($clientWidth - 110), 45)
$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'
$completionPanel.Controls.Add($okButton)
$form.Controls.Add($completionPanel)
$completionPanel.BringToFront()
Add-SessionFooter -TargetForm $form -Theme $Theme -Font $FooterFont `
-IPv4 $IPv4 -VncPassword $VncPassword -ShowPassword $true
$form.AcceptButton = $okButton
$form.CancelButton = $okButton
$progressState = [pscustomobject]@{
Form = $form
Theme = $Theme
ListPanel = $list
HeaderHeight = 120
FooterHeight = $footerHeight
Steps = $rows
CurrentIndex = -1
CompletionPanel = $completionPanel
CompletionLabel = $completionLabel
RestartCheckBox = $restartCheckBox
OkButton = $okButton
ElapsedTimer = New-Object System.Windows.Forms.Timer
UpdateAction = $null
Action = $null
}
$inProgressText = Get-DeployCenterText -Text 'In progress…'
$completedText = Get-DeployCenterText -Text 'Completed'
$formatElapsedTime = {
param([TimeSpan]$Duration)
if ($Duration.TotalHours -ge 1) { return $Duration.ToString('h\:mm\:ss') }
return $Duration.ToString('m\:ss')
}.GetNewClosure()
$progressState.ElapsedTimer.Interval = 500
$progressState.ElapsedTimer.Add_Tick({
if ($progressState.CurrentIndex -lt 0) { return }
$active = $progressState.Steps[$progressState.CurrentIndex]
if (-not $active.Started -or -not $active.Started.IsRunning) { return }
$elapsed = & $formatElapsedTime $active.Started.Elapsed
if ($active.Bar.Visible) {
$active.State.Text = '{0} % · {1}' -f $active.Bar.Value, $elapsed
} else {
$active.State.Text = '{0} {1}' -f $inProgressText, $elapsed
}
}.GetNewClosure())
$form.Add_Shown({ $progressState.ElapsedTimer.Start() }.GetNewClosure())
$form.Add_FormClosed({
$progressState.ElapsedTimer.Stop()
$progressState.ElapsedTimer.Dispose()
}.GetNewClosure())
$progressState.UpdateAction = {
param($Message, $Percent, $DisplayMessage)
if ($null -ne $Message) {
$nextIndex = -1
for ($index = 0; $index -lt $progressState.Steps.Count; $index++) {
if ([string]$Message -and ([string]$Message).StartsWith([string]$progressState.Steps[$index].Key, [StringComparison]::Ordinal)) {
$nextIndex = $index
break
}
}
if ($nextIndex -ge 0 -and $nextIndex -ne $progressState.CurrentIndex) {
if ($progressState.CurrentIndex -ge 0) {
$previous = $progressState.Steps[$progressState.CurrentIndex]
if ($previous.Started) {
$previous.Started.Stop()
$elapsed = if ($previous.Started.Elapsed.TotalHours -ge 1) { $previous.Started.Elapsed.ToString('h\:mm\:ss') } else { $previous.Started.Elapsed.ToString('m\:ss') }
$previous.State.Text = '{0}: {1}' -f $completedText, $elapsed
$previous.State.ForeColor = $progressState.Theme.Success
$previous.Bar.Visible = $false
$previous.Complete = $true
}
}
$progressState.CurrentIndex = $nextIndex
$current = $progressState.Steps[$nextIndex]
$current.Started = [System.Diagnostics.Stopwatch]::StartNew()
if (-not [string]::IsNullOrWhiteSpace([string]$DisplayMessage)) { $current.Name.Text = [string]$DisplayMessage }
$current.State.Text = $inProgressText
$current.State.ForeColor = $progressState.Theme.Success
}
}
if ($null -ne $Percent -and $progressState.CurrentIndex -ge 0) {
$active = $progressState.Steps[$progressState.CurrentIndex]
$active.Bar.Visible = $true
$active.Bar.Value = [int][Math]::Max(0, [Math]::Min(100, [int]$Percent))
$active.State.Text = '{0} % · {1}' -f $active.Bar.Value, (& $formatElapsedTime $active.Started.Elapsed)
}
[System.Windows.Forms.Application]::DoEvents()
}.GetNewClosure()
return $progressState
}
function Show-DeployCenterDeploymentCompletion {
param(
[Parameter(Mandatory=$true)]$Progress,
[Parameter(Mandatory=$true)][string]$Message,
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Owner,
[bool]$RestartByDefault = $true
)
$Progress.CompletionLabel.Text = Get-DeployCenterText -Text $Message
$Progress.RestartCheckBox.Checked = $RestartByDefault
$Progress.CompletionPanel.Visible = $true
$Progress.ListPanel.Size = New-Object System.Drawing.Size($Progress.ListPanel.Width, ($Progress.Form.ClientSize.Height - $Progress.HeaderHeight - $Progress.CompletionPanel.Height - $Progress.FooterHeight - 12))
$Progress.CompletionPanel.BringToFront()
[System.Windows.Forms.Application]::DoEvents()
# Reopen the same progress window modally so its completed timings remain visible until confirmation.
$Progress.Form.Hide()
[void]$Progress.Form.ShowDialog($Owner)
$restart = [bool]$Progress.RestartCheckBox.Checked
if (-not $Progress.Form.IsDisposed) { $Progress.Form.Close() }
return $restart
}
function Set-DeployCenterDeploymentProgress {
param(
[Parameter(Mandatory=$true)]$Progress,
[AllowNull()][string]$Message,
[AllowNull()][object]$Percent
)
if ($null -ne $Message) {
$nextIndex = -1
for ($index = 0; $index -lt $Progress.Steps.Count; $index++) {
if ($Message.StartsWith([string]$Progress.Steps[$index].Key, [StringComparison]::Ordinal)) { $nextIndex = $index; break }
}
if ($nextIndex -ge 0 -and $nextIndex -ne $Progress.CurrentIndex) {
if ($Progress.CurrentIndex -ge 0) {
$previous = $Progress.Steps[$Progress.CurrentIndex]
if ($previous.Started) {
$previous.Started.Stop()
$previous.State.Text = '{0}: {1}' -f (Get-DeployCenterText -Text 'Completed'), (Format-DeployCenterElapsedTime -Duration $previous.Started.Elapsed)
$previous.State.ForeColor = $Progress.Theme.Success
$previous.Bar.Visible = $false
$previous.Complete = $true
}
}
$Progress.CurrentIndex = $nextIndex
$current = $Progress.Steps[$nextIndex]
$current.Started = [System.Diagnostics.Stopwatch]::StartNew()
$current.State.Text = Get-DeployCenterText -Text 'In progress…'
$current.State.ForeColor = $Progress.Theme.Success
}
}
if ($null -ne $Percent -and $Progress.CurrentIndex -ge 0) {
$active = $Progress.Steps[$Progress.CurrentIndex]
$active.Bar.Visible = $true
$active.Bar.Value = [int][Math]::Max(0, [Math]::Min(100, [int]$Percent))
$active.State.Text = '{0} % · {1}' -f $active.Bar.Value, (Format-DeployCenterElapsedTime -Duration $active.Started.Elapsed)
}
[System.Windows.Forms.Application]::DoEvents()
}
function Complete-DeployCenterDeploymentProgress {
param([Parameter(Mandatory=$true)]$Progress)
if ($Progress.CurrentIndex -ge 0) {
$active = $Progress.Steps[$Progress.CurrentIndex]
if ($active.Started -and $active.Started.IsRunning) { $active.Started.Stop() }
if ($active.Started) {
$active.State.Text = '{0}: {1}' -f (Get-DeployCenterText -Text 'Completed'), (Format-DeployCenterElapsedTime -Duration $active.Started.Elapsed)
}
$active.State.ForeColor = $Progress.Theme.Success
$active.Bar.Visible = $false
$active.Complete = $true
}
}
function Fail-DeployCenterDeploymentProgress {
param([Parameter(Mandatory=$true)]$Progress)
if ($Progress.CurrentIndex -ge 0) {
$active = $Progress.Steps[$Progress.CurrentIndex]
if ($active.Started -and $active.Started.IsRunning) { $active.Started.Stop() }
$active.State.Text = '{0}: {1}' -f (Get-DeployCenterText -Text 'Failed'), (Format-DeployCenterElapsedTime -Duration $active.Started.Elapsed)
$active.State.ForeColor = $Progress.Theme.DangerButton.Background
$active.Bar.Visible = $false
}
}
function Format-DeployCenterElapsedTime {
param([Parameter(Mandatory=$true)][TimeSpan]$Duration)
if ($Duration.TotalHours -ge 1) { return $Duration.ToString('h\:mm\:ss') }
return $Duration.ToString('m\:ss')
}
function Set-DeployCenterFormSizing {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$Form,
[switch]$Resizable
)
if ($Resizable) {
$Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Sizable
$Form.MaximizeBox = $true
} else {
$Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$Form.MaximizeBox = $false
}
}
function Add-SessionFooter {
param(
[Parameter(Mandatory=$true)][System.Windows.Forms.Form]$TargetForm,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[AllowNull()][string]$IPv4,
[AllowNull()][string]$VncPassword,
[bool]$ShowFooter = $true,
[bool]$ShowPassword = $true
)
if (-not $ShowFooter) { return }
$footer = New-Object System.Windows.Forms.Label
$footer.Name = 'DeployCenterSessionFooter'
$footer.Text = if ($IPv4) { (Get-DeployCenterText -Text 'Deployment IP:') + " $IPv4" } else { Get-DeployCenterText -Text 'Deployment IP unavailable' }
if ($ShowPassword -and -not [string]::IsNullOrWhiteSpace($VncPassword)) {
$footer.Text += " | " + (Get-DeployCenterText -Text 'VNC password:') + " $VncPassword"
}
$footer.Height = 26
$footer.Dock = [System.Windows.Forms.DockStyle]::Bottom
$footer.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$footer.Padding = New-Object System.Windows.Forms.Padding(12, 0, 0, 0)
$footer.BackColor = $Theme.PanelAlt
$footer.ForeColor = $Theme.Text
$footer.Font = $Font
$TargetForm.Controls.Add($footer)
$footer.BringToFront()
}
function New-Label {
param(
[Parameter(Mandatory=$true)][string]$Text,
[Parameter(Mandatory=$true)][int]$X,
[Parameter(Mandatory=$true)][int]$Y,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[int]$Width = 180,
[int]$Height = 24,
[AllowNull()]$Color = $null
)
$label = New-Object System.Windows.Forms.Label
$label.Text = Get-DeployCenterText -Text $Text
$label.Location = New-Object System.Drawing.Point($X, $Y)
$label.Size = New-Object System.Drawing.Size($Width, $Height)
$label.AutoEllipsis = $true
$label.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$label.BackColor = [System.Drawing.Color]::Transparent
$label.ForeColor = if ($null -eq $Color) { $Theme.Text } else { $Color }
$label.Font = $Font
return $label
}
function New-GroupBox {
param(
[Parameter(Mandatory=$true)][string]$Text,
[Parameter(Mandatory=$true)][int]$X,
[Parameter(Mandatory=$true)][int]$Y,
[Parameter(Mandatory=$true)][int]$Width,
[Parameter(Mandatory=$true)][int]$Height,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font
)
$group = New-Object DeployCenter.ThemedGroupBox
$group.Text = Get-DeployCenterText -Text $Text
$group.Location = New-Object System.Drawing.Point($X, $Y)
$group.Size = New-Object System.Drawing.Size($Width, $Height)
$group.ForeColor = $Theme.Text
$group.BackColor = $Theme.Panel
$group.PanelColor = $Theme.Panel
$group.BorderColor = $Theme.Border
$group.TextColor = $Theme.Text
$group.Font = $Font
return $group
}
function New-Button {
param(
[Parameter(Mandatory=$true)][string]$Text,
[Parameter(Mandatory=$true)][int]$X,
[Parameter(Mandatory=$true)][int]$Y,
[Parameter(Mandatory=$true)][int]$Width,
[Parameter(Mandatory=$true)][int]$Height,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
[Parameter(Mandatory=$true)][System.Drawing.Font]$Font,
[ValidateSet('Button','PrimaryButton','DangerButton')][string]$Role = 'Button'
)
$button = New-Object System.Windows.Forms.Button
$button.Text = Get-DeployCenterText -Text $Text
$button.Location = New-Object System.Drawing.Point($X, $Y)
$button.Size = New-Object System.Drawing.Size($Width, $Height)
$button.Tag = $Role
Set-DeployCenterButtonStyle -Button $button -Theme $Theme -Role $Role
$button.Font = $Font
$button.Cursor = [System.Windows.Forms.Cursors]::Hand
$button.AutoSize = $false
$button.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
return $button
}
function Get-DeployCenterLogoPathForTheme {
param(
[Parameter(Mandatory=$true)][string]$ThemeName,
[Parameter(Mandatory=$true)][string]$LogoPath,
[Parameter(Mandatory=$true)][string]$LogoDarkPath
)
if ($ThemeName -eq 'Dark' -and (Test-Path -LiteralPath $LogoDarkPath -PathType Leaf)) { return $LogoDarkPath }
if (Test-Path -LiteralPath $LogoPath -PathType Leaf) { return $LogoPath }
if (Test-Path -LiteralPath $LogoDarkPath -PathType Leaf) { return $LogoDarkPath }
return $null
}
function Update-DeployCenterLogo {
param(
[Parameter(Mandatory=$true)][AllowNull()][System.Windows.Forms.PictureBox]$PictureBox,
[Parameter(Mandatory=$true)][string]$ThemeName,
[Parameter(Mandatory=$true)][string]$LogoPath,
[Parameter(Mandatory=$true)][string]$LogoDarkPath
)
if ($null -eq $PictureBox) { return }
$selectedLogoPath = Get-DeployCenterLogoPathForTheme -ThemeName $ThemeName -LogoPath $LogoPath -LogoDarkPath $LogoDarkPath
if ([string]::IsNullOrWhiteSpace($selectedLogoPath)) { return }
try {
$newImage = [System.Drawing.Image]::FromFile($selectedLogoPath)
$oldImage = $PictureBox.Image
$PictureBox.Image = $newImage
if ($null -ne $oldImage) { $oldImage.Dispose() }
$PictureBox.Visible = $true
} catch {
# Keep the current logo if the new file is missing or invalid.
}
}
# Main UI workflows; dot-sourced to explicitly share DeployCenter session state.
function Set-DiskSelectorWidth {
if ($null -eq $script:cmbDisk -or $null -eq $script:groupDisks) { return }
Set-DeployCenterDeploymentControlsLayout -Parent $script:groupDisks -Controls $deploymentControls
}
function Set-ControlTheme {
param([System.Windows.Forms.Control]$Control)
# The main form background must also change.
if ($Control -is [System.Windows.Forms.Form]) {
$Control.BackColor = $script:Theme.Background
$Control.ForeColor = $script:Theme.Text
}
elseif ($Control -is [DeployCenter.ThemedGroupBox]) {
$Control.BackColor = $script:Theme.Panel
$Control.PanelColor = $script:Theme.Panel
$Control.BorderColor = $script:Theme.Border
$Control.TextColor = $script:Theme.Text
$Control.ForeColor = $script:Theme.Text
$Control.Invalidate()
}
elseif ($Control -is [System.Windows.Forms.Label]) {
$Control.ForeColor = $script:Theme.Text
if ($Control.Name -eq 'lblSelectedWindows') {
$Control.ForeColor = if ($script:SelectedWindowsEdition) { $script:Theme.Success } else { $script:Theme.Secondary }
}
if ($Control.Name -eq 'lblDriverStatus') {
$Control.ForeColor = if ($script:DriverPackSelection -and $script:DriverPackSelection.Found) { $script:Theme.Success } else { $script:Theme.Secondary }
}
# The VNC footer also follows the active theme palette.
if ($Control.Name -eq 'DeployCenterSessionFooter') {
$Control.BackColor = $script:Theme.PanelAlt
$Control.ForeColor = $script:Theme.Secondary
$Control.Invalidate()
}
}
elseif ($Control -is [System.Windows.Forms.ListView]) {
$Control.BackColor = $script:Theme.PanelAlt
$Control.ForeColor = $script:Theme.Text
}
elseif ($Control -is [System.Windows.Forms.ComboBox]) {
Set-DeployCenterComboBoxTheme -ComboBox $Control -Theme $script:Theme
}
elseif ($Control -is [System.Windows.Forms.TextBox]) {
$Control.BackColor = $script:Theme.PanelAlt
$Control.ForeColor = $script:Theme.Text
}
foreach ($child in $Control.Controls) {
Set-ControlTheme -Control $child
}
}
function Set-DeployCenterTheme {
param([Parameter(Mandatory=$true)][string]$ThemeName)
$availableThemes = @(Get-DeployCenterThemeNames -ThemesRoot $script:ThemesRoot)
if ($availableThemes -notcontains $ThemeName) { return }
try {
$script:Theme = Import-DeployCenterTheme -ThemesRoot $script:ThemesRoot -ThemeName $ThemeName
} catch {
& $script:SourceLogAction ("Unable to load theme '$ThemeName' : " + $_.Exception.Message) "ERROR"
return
}
$script:CurrentTheme = $ThemeName
$script:cmbTheme.BackColor = $script:Theme.Panel
$script:cmbTheme.ForeColor = $script:Theme.Text
$script:cmbTheme.Invalidate()
$script:cmbTheme.Refresh()
Set-ControlTheme -Control $script:form
Set-ControlTheme -Control $script:logForm
# Update the logo if a theme-specific variant exists.
Update-DeployCenterLogo -PictureBox $script:pictureLogo -ThemeName $script:CurrentTheme `
-LogoPath $script:LogoPath -LogoDarkPath $script:LogoDarkPath
$script:lblTitle.ForeColor = $script:Theme.Text
$script:lblSubtitle.ForeColor = $script:Theme.Secondary
Set-DeployCenterButtonStyle -Button $script:btnChooseWindows -Theme $script:Theme -Role 'Button'
Set-DeployCenterButtonStyle -Button $script:btnLog -Theme $script:Theme -Role 'Button'
Set-DeployCenterButtonStyle -Button $script:btnRefresh -Theme $script:Theme -Role 'PrimaryButton'
Set-DeployCenterButtonStyle -Button $script:btnDeploy -Theme $script:Theme -Role 'PrimaryButton'
Set-DeployCenterButtonStyle -Button $script:btnClose -Theme $script:Theme -Role 'DangerButton'
# Explicitly refresh the main footer when the theme changes.
foreach ($footer in $script:form.Controls.Find('DeployCenterSessionFooter', $true)) {
$footer.BackColor = $script:Theme.PanelAlt
$footer.ForeColor = $script:Theme.Secondary
$footer.Invalidate()
}
$script:cmbTheme.BackColor = $script:Theme.Panel
$script:cmbTheme.ForeColor = $script:Theme.Text
$script:cmbTheme.Invalidate()
$script:groupPC.Invalidate()
$script:groupDisks.Invalidate()
$script:cmbDisk.BackColor = $script:Theme.PanelAlt
$script:cmbDisk.ForeColor = $script:Theme.Text
$script:cmbDisk.Invalidate()
# Keep the button above the other header controls.
$script:cmbTheme.BringToFront()
# Force a repaint after changing the palette.
$script:form.SuspendLayout()
try {
foreach ($control in $script:form.Controls) {
$control.Invalidate($true)
}
$script:form.Invalidate($true)
$script:form.Refresh()
}
finally {
$script:form.ResumeLayout($true)
}
}
function Show-WindowsSourcesDialog {
$dlg = New-Object System.Windows.Forms.Form
$dlg.Text = Get-DeployCenterText -Text 'Windows sources - DeployCenter drive and SMB share'
$dlg.StartPosition = 'CenterParent'
$dlg.Size = New-Object System.Drawing.Size(1100, 700)
$dlg.MinimumSize = New-Object System.Drawing.Size(700, 450)
Set-DeployCenterFormSizing -Form $dlg -Resizable
# Open directly in fullscreen to use the entire available area.
$dlg.WindowState = [System.Windows.Forms.FormWindowState]::Maximized
$dlg.ShowInTaskbar = $false
$dlg.BackColor = $script:Theme.Background
$dlg.ForeColor = $script:Theme.Text
$dlg.Font = $script:FontRegular
$dlg.KeyPreview = $true
$dlg.Add_KeyDown({
param($sender, $eventArgs)
if ($eventArgs.KeyCode -eq [System.Windows.Forms.Keys]::Escape) {
$eventArgs.Handled = $true
$eventArgs.SuppressKeyPress = $true
$dlg.Close()
}
}.GetNewClosure())
$lblPath = New-Object System.Windows.Forms.Label
$lblPath.Text = Get-DeployCenterText -Text 'Paths to scan (separated by ;):'
$lblPath.Location = New-Object System.Drawing.Point(16, 16)
$lblPath.Size = New-Object System.Drawing.Size(260, 24)
$lblPath.ForeColor = $script:Theme.Text
$dlg.Controls.Add($lblPath)
$txtPath = New-Object System.Windows.Forms.TextBox
$txtPath.Location = New-Object System.Drawing.Point(16, 42)
$txtPath.Size = New-Object System.Drawing.Size(560, 26)
$txtPath.Anchor = 'Top,Left,Right'
$defaultSourceRoot = $script:DeployVolumeRoot
if ([string]::IsNullOrWhiteSpace($defaultSourceRoot)) { $defaultSourceRoot = $script:DeployRoot }
$configuredPaths = [string]$script:Config.SourcePaths
if ([string]::IsNullOrWhiteSpace($configuredPaths)) {
$txtPath.Text = $defaultSourceRoot
} else {
$txtPath.Text = $configuredPaths.Replace("%DEPLOY_VOLUME%", $defaultSourceRoot)
}
$txtPath.BackColor = $script:Theme.PanelAlt
$txtPath.ForeColor = $script:Theme.Text
$dlg.Controls.Add($txtPath)
$btnScan = New-Object System.Windows.Forms.Button
$btnScan.Text = Get-DeployCenterText -Text 'Search'
$btnScan.Location = New-Object System.Drawing.Point(650, 40)
$btnScan.Size = New-Object System.Drawing.Size(100, 30)
$btnScan.Anchor = 'Top,Right'
$btnScan.UseVisualStyleBackColor = $false
$btnScan.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$btnScan.FlatAppearance.BorderColor = $script:Theme.Border
$btnScan.BackColor = $script:Theme.Accent
$btnScan.ForeColor = [System.Drawing.Color]::White
$dlg.Controls.Add($btnScan)
$btnEditions = New-Object System.Windows.Forms.Button
$btnEditions.Text = Get-DeployCenterText -Text 'Show editions'
$btnEditions.Location = New-Object System.Drawing.Point(760, 40)
$btnEditions.Size = New-Object System.Drawing.Size(150, 30)
$btnEditions.Anchor = 'Top,Right'
$btnEditions.UseVisualStyleBackColor = $false
$btnEditions.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$btnEditions.FlatAppearance.BorderColor = $script:Theme.Border
$btnEditions.BackColor = $script:Theme.Panel
$btnEditions.ForeColor = $script:Theme.Text
$dlg.Controls.Add($btnEditions)
$list = New-Object System.Windows.Forms.ListView
$list.Location = New-Object System.Drawing.Point(16, 84)
$list.Size = New-Object System.Drawing.Size(770, 420)
$list.Anchor = 'Top,Bottom,Left,Right'
$list.View = 'Details'; $list.FullRowSelect = $true; $list.GridLines = $true
$list.MultiSelect = $false
$list.HideSelection = $true
$list.BackColor = $script:Theme.PanelAlt
$list.ForeColor = $script:Theme.Text
$list.OwnerDraw = $true
[void]$list.Columns.Add((Get-DeployCenterText -Text 'Type'), 135)
[void]$list.Columns.Add((Get-DeployCenterText -Text 'Name'), 260)
[void]$list.Columns.Add((Get-DeployCenterText -Text 'Size'), 80)
[void]$list.Columns.Add((Get-DeployCenterText -Text 'Path'), 250)
# The Name column has priority and absorbs the remaining space.
$resizeSourceColumns = {
if ($list.Columns.Count -ge 4) {
$availableWidth = $list.ClientSize.Width - $list.Columns[0].Width - $list.Columns[2].Width - $list.Columns[3].Width - 4
if ($list.Items.Count -gt 0 -and $list.Items.Count -gt [math]::Floor($list.ClientSize.Height / 20)) {
$availableWidth -= [System.Windows.Forms.SystemInformation]::VerticalScrollBarWidth
}
$list.Columns[1].Width = [Math]::Max(180, $availableWidth)
}
}
$list.Add_SizeChanged($resizeSourceColumns)
$list.Add_HandleCreated($resizeSourceColumns)
# Custom-draw headers and rows so the ListView
# follows the active theme instead of using the native white headers.
$list.Add_DrawColumnHeader({
param($sender, $e)
$headerColor = if ($script:Theme.Contains('TableHeader')) { $script:Theme.TableHeader } else { $script:Theme.PanelAlt }
$headerBrush = New-Object System.Drawing.SolidBrush($headerColor)
$textBrush = New-Object System.Drawing.SolidBrush($script:Theme.Text)
$borderPen = New-Object System.Drawing.Pen($script:Theme.Border)
try {
$e.Graphics.FillRectangle($headerBrush, $e.Bounds)
$e.Graphics.DrawRectangle($borderPen, $e.Bounds.X, $e.Bounds.Y, $e.Bounds.Width - 1, $e.Bounds.Height - 1)
$flags = [System.Windows.Forms.TextFormatFlags]::Left -bor [System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor [System.Windows.Forms.TextFormatFlags]::EndEllipsis
[System.Windows.Forms.TextRenderer]::DrawText($e.Graphics, $e.Header.Text, $list.Font, $e.Bounds, $script:Theme.Text, $flags)
} finally {
$headerBrush.Dispose(); $textBrush.Dispose(); $borderPen.Dispose()
}
})
$list.Add_DrawItem({
param($sender, $e)
if ($list.View -ne [System.Windows.Forms.View]::Details) {
$e.DrawDefault = $true
}
})
$list.Add_DrawSubItem({
param($sender, $e)
$rowBack = if (($e.Item.Index % 2) -eq 0) { $script:Theme.TableRowEven } else { $script:Theme.TableRowOdd }
$rowFore = $script:Theme.Text
# Highlight only the selected item, never every row.
if ($e.Item.Selected -and $list.Focused) {
$rowBack = $script:Theme.Accent
$rowFore = [System.Drawing.Color]::White
}
$rowBrush = New-Object System.Drawing.SolidBrush($rowBack)
$textBrush = New-Object System.Drawing.SolidBrush($rowFore)
$borderPen = New-Object System.Drawing.Pen($script:Theme.Border)
try {
$e.Graphics.FillRectangle($rowBrush, $e.Bounds)
$e.Graphics.DrawRectangle($borderPen, $e.Bounds.X, $e.Bounds.Y, $e.Bounds.Width - 1, $e.Bounds.Height - 1)
$flags = [System.Windows.Forms.TextFormatFlags]::Left -bor [System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor [System.Windows.Forms.TextFormatFlags]::EndEllipsis -bor [System.Windows.Forms.TextFormatFlags]::NoPrefix
[System.Windows.Forms.TextRenderer]::DrawText($e.Graphics, $e.SubItem.Text, $list.Font, $e.Bounds, $rowFore, $flags)
} finally {
$rowBrush.Dispose(); $textBrush.Dispose(); $borderPen.Dispose()
}
})
# Local filter: it never reruns the scan of drives or shares.
$lblFilter = New-Object System.Windows.Forms.Label
$lblFilter.Text = Get-DeployCenterText -Text 'Filter list:'
$lblFilter.Location = New-Object System.Drawing.Point(16, 80)
$lblFilter.Size = New-Object System.Drawing.Size(100, 24)
$lblFilter.ForeColor = $script:Theme.Text
$dlg.Controls.Add($lblFilter)
$txtFilter = New-Object System.Windows.Forms.TextBox
$txtFilter.Location = New-Object System.Drawing.Point(120, 78)
$txtFilter.Size = New-Object System.Drawing.Size(500, 26)
$txtFilter.Anchor = 'Top,Left,Right'
$txtFilter.BackColor = $script:Theme.PanelAlt
$txtFilter.ForeColor = $script:Theme.Text
$dlg.Controls.Add($txtFilter)
$list.Location = New-Object System.Drawing.Point(16, 112)
$dlg.Controls.Add($list)
# Explicit shared state between WinForms event handlers.
# In PowerShell, callbacks may run in a different scope:
# a local $allFound variable may then remain empty in the filter.
$sourceState = [hashtable]::Synchronized(@{ AllFound = @() })
$renderSourceList = {
param([string]$Query)
$previousSelection = $null
if ($list.SelectedItems.Count -gt 0) {
$previousSelection = [string]$list.SelectedItems[0].SubItems[3].Text
}
$needle = ([string]$Query).Trim()
$visibleEntries = New-Object System.Collections.Generic.List[object]
$entries = @($sourceState.AllFound)
foreach ($entry in $entries) {
if ([string]::IsNullOrWhiteSpace($needle)) {
[void]$visibleEntries.Add($entry)
continue
}
$searchable = '{0} {1} {2} {3} {4}' -f `
[string]$entry.Type, [string]$entry.Name, [string]$entry.Path, `
[string]$entry.Size, [string]$entry.Detail
if ($searchable.IndexOf($needle, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
[void]$visibleEntries.Add($entry)
}
}
$list.BeginUpdate()
try {
$list.Items.Clear()
foreach ($entry in $visibleEntries) {
$item = New-Object System.Windows.Forms.ListViewItem([string]$entry.Type)
[void]$item.SubItems.Add([string]$entry.Name)
[void]$item.SubItems.Add([string]$entry.Size)
[void]$item.SubItems.Add([string]$entry.Path)
if ($previousSelection -and ([string]$entry.Path -eq $previousSelection)) {
$item.Selected = $true
}
[void]$list.Items.Add($item)
}
} finally {
$list.EndUpdate()
}
if ($entries.Count -gt 0) {
$lblStatus.Text = (Get-DeployCenterText -Text '{0} result(s) shown out of {1}. Instant filter; no files changed.' -FormatArguments @($list.Items.Count, $entries.Count))
} elseif ([string]::IsNullOrWhiteSpace($needle)) {
$lblStatus.Text = Get-DeployCenterText -Text 'No ISO/WIM/ESD source or extracted installation was found in the accessible paths.'
} else {
$lblStatus.Text = (Get-DeployCenterText -Text 'No results for “{0}”. Clear the filter to see the list again.' -FormatArguments @($needle))
}
}
$txtFilter.Add_TextChanged({ & $renderSourceList $txtFilter.Text })
$lblStatus = New-Object System.Windows.Forms.Label
$lblStatus.Text = Get-DeployCenterText -Text 'Read-only scan of the DeployCenter drive and/or SMB share. Separate multiple paths with semicolons.'
$lblStatus.Location = New-Object System.Drawing.Point(16, 515)
$lblStatus.Size = New-Object System.Drawing.Size(770, 36)
$lblStatus.Anchor = 'Bottom,Left,Right'
$lblStatus.AutoSize = $false
$lblStatus.ForeColor = $script:Theme.Secondary
$dlg.Controls.Add($lblStatus)
$progressAnalysis = New-Object System.Windows.Forms.ProgressBar
$progressAnalysis.Name = 'progressWindowsSourceAnalysis'
$progressAnalysis.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
$progressAnalysis.MarqueeAnimationSpeed = 30
$progressAnalysis.Visible = $false
$progressAnalysis.Anchor = 'Bottom,Right'
$dlg.Controls.Add($progressAnalysis)
# Recalculate positions from the actual window size:
# the table uses the remaining space and the status stays visible at the bottom.
$resizeLayout = {
$clientWidth = $dlg.ClientSize.Width
$clientHeight = $dlg.ClientSize.Height
$rightMargin = 16
$gap = 12
$btnWidth = 88
$editionsWidth = 148
$left = 12
$right = [Math]::Max($left + $btnWidth, $clientWidth - $rightMargin)
$btnEditionsLeft = [Math]::Max($left, $right - $editionsWidth)
$btnScanLeft = [Math]::Max($left, $btnEditionsLeft - $gap - $btnWidth)
$txtPath.Width = [Math]::Max(100, $btnScanLeft - $left - $gap)
$btnEditions.Location = New-Object System.Drawing.Point($btnEditionsLeft, 40)
$btnEditions.Width = [Math]::Max(120, $right - $btnEditionsLeft)
$btnScan.Location = New-Object System.Drawing.Point($btnScanLeft, 40)
$btnScan.Width = [Math]::Max(80, $btnEditionsLeft - $gap - $btnScanLeft)
$btnScan.Anchor = 'Top,Right'
$btnEditions.Anchor = 'Top,Right'
$txtPath.Anchor = 'Top,Left,Right'
$lblFilter.Location = New-Object System.Drawing.Point(16, 80)
$txtFilter.Location = New-Object System.Drawing.Point(120, 78)
$txtFilter.Size = New-Object System.Drawing.Size([Math]::Max(180, $clientWidth - 152), 26)
$list.Location = New-Object System.Drawing.Point(16, 112)
$list.Size = New-Object System.Drawing.Size([Math]::Max(300, $clientWidth - 32), [Math]::Max(120, $clientHeight - 205))
$list.Anchor = 'Top,Bottom,Left,Right'
$lblStatus.Location = New-Object System.Drawing.Point(16, ($clientHeight - 52))
$lblStatus.Size = New-Object System.Drawing.Size([Math]::Max(150, $clientWidth - 164), 24)
$lblStatus.Anchor = 'Bottom,Left,Right'
$progressAnalysis.Location = New-Object System.Drawing.Point(($clientWidth - 136), ($clientHeight - 52))
$progressAnalysis.Size = New-Object System.Drawing.Size(120, 20)
}
$dlg.Add_Shown({
& $resizeLayout
# Automatically start the first scan when the window opens.
# The Search button remains available if the user changes the paths.
$btnScan.PerformClick()
})
$dlg.Add_SizeChanged({ & $resizeLayout })
$btnEditions.Add_Click({
if ($list.SelectedItems.Count -eq 0) {
Show-DeployCenterMessage -Message 'Select a source from the list first.' -Title 'Windows editions' -Icon Information -Owner $dlg -Theme $script:Theme -Font $script:FontRegular
return
}
$selectedPath = [string]$list.SelectedItems[0].SubItems[3].Text
$selectedName = [string]$list.SelectedItems[0].SubItems[1].Text
$resolvedImage = $null
$mountTransferred = $false
$btnEditions.Enabled = $false
$btnScan.Enabled = $false
$dlg.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
$lblStatus.Text = (Get-DeployCenterText -Text 'Analyzing editions in {0}...' -FormatArguments @($selectedName))
$progressAnalysis.Visible = $true
$progressAnalysis.BringToFront()
$dlg.Refresh()
try {
$resolvedImage = Resolve-InstallImagePath -SelectedPath $selectedPath -ExistingImage $script:SelectedWindowsEdition
$imagePath = [string]$resolvedImage.ImagePath
$editionResult = Get-WindowsImageEditions -ImagePath $imagePath -LogAction $script:SourceLogAction
$editions = @($editionResult.Editions)
$globalImageVersion = [string]$editionResult.GlobalVersion
$lines = foreach ($edition in $editions) {
$index = if ($edition.ImageIndex) { $edition.ImageIndex } elseif ($edition.Index) { $edition.Index } else { '?' }
$name = if ($edition.ImageName) { $edition.ImageName } elseif ($edition.Name) { $edition.Name } else { Get-DeployCenterText -Text 'Name unavailable' }
$arch = if ($edition.Architecture) { [string]$edition.Architecture } else { Get-DeployCenterText -Text 'Architecture unavailable' }
$version = if ($edition.Version) { [string]$edition.Version } else { Get-DeployCenterText -Text 'Version unavailable' }
"Index: $index`r`nName: $name`r`nArchitecture: $arch`r`nVersion: $version"
}
# The child form is modal: refresh the status before showing it,
# otherwise the parent form remains visually stuck on "scan in progress".
$lblStatus.Text = (Get-DeployCenterText -Text '{0} edition(s) found in {1}. Select an edition.' -FormatArguments @($editions.Count, $selectedName))
$dlg.Refresh()
# Edition table; retain the selected DISM index for later use.
$resultForm = New-Object System.Windows.Forms.Form
$resultForm.Text = Get-DeployCenterText -Text 'Available Windows editions'
$resultForm.StartPosition = 'CenterParent'
$resultForm.Size = New-Object System.Drawing.Size(700, 480)
$resultForm.MinimumSize = New-Object System.Drawing.Size(520, 320)
Set-DeployCenterFormSizing -Form $resultForm
$resultForm.ShowInTaskbar = $false
$resultForm.BackColor = $script:Theme.Background
$resultForm.ForeColor = $script:Theme.Text
$sourceLabel = New-Object System.Windows.Forms.Label
$sourceLabel.Text = (Get-DeployCenterText -Text 'Source:' ) + " $selectedPath"
$sourceLabel.Dock = [System.Windows.Forms.DockStyle]::Top
$sourceLabel.Height = 38
$sourceLabel.Padding = New-Object System.Windows.Forms.Padding(10, 8, 10, 4)
$sourceLabel.AutoEllipsis = $true
$sourceLabel.BackColor = $script:Theme.Background
$sourceLabel.ForeColor = $script:Theme.Text
$editionList = New-Object System.Windows.Forms.ListView
$editionList.View = [System.Windows.Forms.View]::Details
$editionList.FullRowSelect = $true
$editionList.GridLines = $true
$editionList.MultiSelect = $false
$editionList.HideSelection = $false
# Only one edition can be selected at a time.
$editionList.Sorting = [System.Windows.Forms.SortOrder]::None
$editionList.Dock = [System.Windows.Forms.DockStyle]::Fill
$editionList.BackColor = $script:Theme.Panel
$editionList.ForeColor = $script:Theme.Text
[void]$editionList.Columns.Add((Get-DeployCenterText -Text 'Index'), 65)
[void]$editionList.Columns.Add((Get-DeployCenterText -Text 'Edition'), 260)
[void]$editionList.Columns.Add((Get-DeployCenterText -Text 'Architecture'), 100)
[void]$editionList.Columns.Add((Get-DeployCenterText -Text 'Version'), 105)
# Edition is the priority column; Version keeps a fixed width.
$resizeEditionColumns = {
if ($editionList.Columns.Count -ge 4) {
$availableWidth = $editionList.ClientSize.Width - $editionList.Columns[0].Width - $editionList.Columns[2].Width - $editionList.Columns[3].Width - 4
if ($editionList.Items.Count -gt 0 -and $editionList.Items.Count -gt [Math]::Floor($editionList.ClientSize.Height / 20)) {
$availableWidth -= [System.Windows.Forms.SystemInformation]::VerticalScrollBarWidth
}
$editionList.Columns[1].Width = [Math]::Max(220, $availableWidth)
}
}
$editionList.Add_SizeChanged($resizeEditionColumns)
$resultForm.Add_Shown($resizeEditionColumns)
# Custom-draw headers and cells so the ListView
# follows the Light/Dark theme instead of keeping a white header.
$editionList.OwnerDraw = $true
$editionList.Add_DrawColumnHeader({
param($sender, $e)
$headerColor = if ($script:Theme.Contains('TableHeader')) { $script:Theme.TableHeader } else { $script:Theme.PanelAlt }
$headerBrush = New-Object System.Drawing.SolidBrush($headerColor)
$headerPen = New-Object System.Drawing.Pen($script:Theme.Border)
try {
$e.Graphics.FillRectangle($headerBrush, $e.Bounds)
$e.Graphics.DrawRectangle($headerPen, $e.Bounds.X, $e.Bounds.Y, $e.Bounds.Width - 1, $e.Bounds.Height - 1)
[System.Windows.Forms.TextRenderer]::DrawText(
$e.Graphics, $e.Header.Text, $sender.Font, $e.Bounds,
$script:Theme.Text,
([System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor
[System.Windows.Forms.TextFormatFlags]::Left -bor
[System.Windows.Forms.TextFormatFlags]::EndEllipsis)
)
} finally {
$headerBrush.Dispose()
$headerPen.Dispose()
}
})
$editionList.Add_DrawItem({ param($sender, $e) })
$editionList.Add_DrawSubItem({
param($sender, $e)
$cellBack = if (($e.Item.Index % 2) -eq 0) { $script:Theme.TableRowEven } else { $script:Theme.TableRowOdd }
$cellFore = $script:Theme.Text
if ($e.Item.Selected) {
$cellBack = $script:Theme.Accent
$cellFore = [System.Drawing.Color]::White
}
$cellBrush = New-Object System.Drawing.SolidBrush($cellBack)
$cellPen = New-Object System.Drawing.Pen($script:Theme.Border)
try {
$e.Graphics.FillRectangle($cellBrush, $e.Bounds)
if ($sender.GridLines) {
$e.Graphics.DrawRectangle($cellPen, $e.Bounds.X, $e.Bounds.Y, $e.Bounds.Width - 1, $e.Bounds.Height - 1)
}
[System.Windows.Forms.TextRenderer]::DrawText(
$e.Graphics, $e.SubItem.Text, $sender.Font, $e.Bounds,
$cellFore,
([System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor
[System.Windows.Forms.TextFormatFlags]::Left -bor
[System.Windows.Forms.TextFormatFlags]::EndEllipsis)
)
} finally {
$cellBrush.Dispose()
$cellPen.Dispose()
}
})
foreach ($edition in $editions) {
$index = if ($edition.ImageIndex) { [string]$edition.ImageIndex } elseif ($edition.Index) { [string]$edition.Index } else { '?' }
$name = if ($edition.ImageName) { [string]$edition.ImageName } elseif ($edition.Name) { [string]$edition.Name } elseif ($edition.ImageDescription) { [string]$edition.ImageDescription } else { 'Name unavailable' }
$archValue = $null
foreach ($propertyName in @('Architecture', 'ImageArchitecture')) {
if ($edition.PSObject.Properties[$propertyName] -and $edition.$propertyName) {
$archValue = $edition.$propertyName
break
}
}
$arch = if ($null -ne $archValue) {
switch ([string]$archValue) {
'0' { 'x86' }
'5' { 'ARM' }
'9' { 'x64' }
'12' { 'ARM64' }
default { [string]$archValue }
}
} else { 'Unavailable' }
$version = if ($edition.Version) { [string]$edition.Version } else { 'Unavailable' }
$row = New-Object System.Windows.Forms.ListViewItem($index)
[void]$row.SubItems.Add($name)
[void]$row.SubItems.Add($arch)
[void]$row.SubItems.Add($version)
$row.Tag = $edition
[void]$editionList.Items.Add($row)
}
$buttonPanel = New-Object System.Windows.Forms.Panel
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$buttonPanel.Height = 52
$buttonPanel.BackColor = $script:Theme.Background
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Text = Get-DeployCenterText -Text 'Cancel'
$cancelButton.Size = New-Object System.Drawing.Size(100, 30)
$cancelButton.Anchor = 'Right,Top'
$cancelButton.Location = New-Object System.Drawing.Point(0, 10)
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$validateButton = New-Object System.Windows.Forms.Button
$validateButton.Text = Get-DeployCenterText -Text 'Select edition'
$validateButton.Size = New-Object System.Drawing.Size(140, 30)
$validateButton.Anchor = 'Right,Top'
$validateButton.Location = New-Object System.Drawing.Point(0, 10)
$validateButton.Enabled = $false
$editionList.Add_SelectedIndexChanged({
$validateButton.Enabled = ($editionList.SelectedItems.Count -gt 0 -and $editionList.SelectedItems[0].SubItems[0].Text -ne '?')
})
$validateButton.Add_Click({
if ($editionList.SelectedItems.Count -gt 0) {
$resultForm.Tag = [pscustomobject]@{
SourcePath = $selectedPath
ImagePath = $imagePath
MountedImage = $resolvedImage.MountedImage
Edition = $editionList.SelectedItems[0].Tag
Index = [int]$editionList.SelectedItems[0].SubItems[0].Text
Name = $editionList.SelectedItems[0].SubItems[1].Text
}
$resultForm.DialogResult = [System.Windows.Forms.DialogResult]::OK
$resultForm.Close()
}
})
$editionList.Add_DoubleClick({
if ($editionList.SelectedItems.Count -gt 0 -and $validateButton.Enabled) {
$validateButton.PerformClick()
}
})
$buttonPanel.Controls.Add($cancelButton)
$buttonPanel.Controls.Add($validateButton)
$buttonPanel.Add_Resize({
$validateButton.Location = New-Object System.Drawing.Point(($buttonPanel.ClientSize.Width - $validateButton.Width - 12), 10)
$cancelButton.Location = New-Object System.Drawing.Point(($validateButton.Left - $cancelButton.Width - 8), 10)
})
$resultForm.Controls.Add($editionList)
$resultForm.Controls.Add($buttonPanel)
$resultForm.Controls.Add($sourceLabel)
$resultForm.AcceptButton = $validateButton
$resultForm.CancelButton = $cancelButton
Add-SessionFooter -TargetForm $resultForm -Theme $script:Theme -Font $script:FontSmall `
-IPv4 $script:DeployIPv4 -VncPassword $script:VncPassword -ShowFooter $false
$dialogResult = $resultForm.ShowDialog($dlg)
$chosenEdition = $resultForm.Tag
$resultForm.Dispose()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK -and $chosenEdition) {
if ($script:SelectedWindowsEdition -and
-not [string]::Equals([string]$script:SelectedWindowsEdition.SourcePath, [string]$chosenEdition.SourcePath, [StringComparison]::OrdinalIgnoreCase)) {
[void](Dismount-DeployCenterInstallImage -Image $script:SelectedWindowsEdition)
}
$script:SelectedWindowsEdition = $chosenEdition
$mountTransferred = $true
$lblStatus.Text = (Get-DeployCenterText -Text 'Selected edition:' ) + " $($chosenEdition.Name) (index $($chosenEdition.Index))."
& $script:SourceLogAction "Edition selected: $($chosenEdition.Name), index $($chosenEdition.Index), image $imagePath" 'SUCCESS'
$dlg.DialogResult = [System.Windows.Forms.DialogResult]::OK
$dlg.Close()
} else {
$lblStatus.Text = (Get-DeployCenterText -Text '{0} edition(s) found in {1}. No new edition selected.' -FormatArguments @($editions.Count, $selectedName))
}
& $script:SourceLogAction "Edition analysis complete: $imagePath ($($editions.Count) edition(s))." 'SUCCESS'
} catch {
$lblStatus.Text = (Get-DeployCenterText -Text "Failed to analyze editions in {0}." -FormatArguments @($selectedName))
& $script:SourceLogAction "Edition analysis error $selectedPath : $($_.Exception.Message)" 'ERROR'
Show-DeployCenterMessage -Message $_.Exception.Message -Title 'Edition analysis' -Icon Warning -Owner $dlg -Theme $script:Theme -Font $script:FontRegular
} finally {
if ($resolvedImage -and $resolvedImage.MountedImage -and -not $mountTransferred -and
(-not $script:SelectedWindowsEdition -or $resolvedImage.MountedImage -ne $script:SelectedWindowsEdition.MountedImage)) {
try { [void](Dismount-DeployCenterInstallImage -Image $resolvedImage) } catch {
& $script:SourceLogAction "Unable to dismount the ISO after analysis: $($_.Exception.Message)" 'WARNING'
}
}
$dlg.Cursor = [System.Windows.Forms.Cursors]::Default
$progressAnalysis.Visible = $false
$btnEditions.Enabled = $true
$btnScan.Enabled = $true
}
})
$list.Add_DoubleClick({
if ($list.SelectedItems.Count -gt 0 -and $btnEditions.Enabled) {
$btnEditions.PerformClick()
}
})
$btnScan.Add_Click({
$btnScan.Enabled = $false
$dlg.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
$lblStatus.Text = Get-DeployCenterText -Text 'Searching the specified paths...'
$sourceState.AllFound = @()
$list.Items.Clear()
try {
# The scan never modifies DeployCenter.ini.
$sourceState.AllFound = @(Find-WindowsSources -RootPath $txtPath.Text -LogAction $script:SourceLogAction)
& $renderSourceList $txtFilter.Text
if (@($sourceState.AllFound).Count -eq 0) {
$lblStatus.Text = Get-DeployCenterText -Text 'No ISO/WIM/ESD source or extracted installation was found in the accessible paths.'
& $script:SourceLogAction 'No Windows source found in the accessible paths.' 'WARNING'
}
} catch {
$lblStatus.Text = Get-DeployCenterText -Text 'Search failed. See the message.'
& $script:SourceLogAction "Source detection error: $($_.Exception.Message)" 'ERROR'
Show-DeployCenterMessage -Message $_.Exception.Message -Title 'Source detection' -Icon Warning -Owner $dlg -Theme $script:Theme -Font $script:FontRegular
} finally {
$dlg.Cursor = [System.Windows.Forms.Cursors]::Default
$btnScan.Enabled = $true
}
})
Add-SessionFooter -TargetForm $dlg -Theme $script:Theme -Font $script:FontSmall `
-IPv4 $script:DeployIPv4 -VncPassword $script:VncPassword
[void]$dlg.ShowDialog($script:form)
$dlg.Dispose()
}
function Update-HardwareView {
$script:btnRefresh.Enabled = $false
$script:form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
try {
if (-not (Get-Command Get-HardwareInfo -ErrorAction SilentlyContinue)) {
throw 'The HardwareInventory module does not provide Get-HardwareInfo.'
}
$info = Get-HardwareInfo
$script:lblManufacturer.Text = (Get-DeployCenterText -Text 'Manufacturer:') + " $(Get-DeployCenterText -Text ([string]$info.Manufacturer))"
$script:lblModel.Text = (Get-DeployCenterText -Text 'Model:') + " $(Get-DeployCenterText -Text ([string]$info.Model))"
$script:lblSerial.Text = (Get-DeployCenterText -Text 'Serial number:') + " $(Get-DeployCenterText -Text ([string]$info.SerialNumber))"
$script:lblFirmware.Text = (Get-DeployCenterText -Text 'Firmware:') + " $(Get-DeployCenterText -Text ([string]$info.Firmware))"
$script:lblArchitecture.Text = (Get-DeployCenterText -Text 'Architecture:') + " $($info.Architecture)"
$script:HardwareInventory = $info
if (Get-Command Update-DeployCenterDriverStatus -ErrorAction SilentlyContinue) { Update-DeployCenterDriverStatus }
$previousDiskIndex = $null
if ($null -ne $script:cmbDisk.SelectedItem -and $null -ne $script:cmbDisk.SelectedItem.Tag) {
$previousDiskIndex = [string]$script:cmbDisk.SelectedItem.Tag
}
$script:RefreshingDiskList = $true
$script:cmbDisk.BeginUpdate()
try {
$script:cmbDisk.Items.Clear()
foreach ($disk in $info.Disks) {
$diskLabel = "Disk {0} - {1} - {2:N1} GB - {3} - {4}" -f $disk.Index, $disk.Model, $disk.SizeGB, $disk.BusType, $disk.Status
$diskChoice = [pscustomobject]@{ Text = $diskLabel; Tag = [string]$disk.Index }
[void]$script:cmbDisk.Items.Add($diskChoice)
}
$script:cmbDisk.DisplayMember = 'Text'
if ($script:cmbDisk.Items.Count -gt 0) {
$selectedIndex = -1
if ($null -ne $previousDiskIndex) {
for ($i = 0; $i -lt $script:cmbDisk.Items.Count; $i++) {
if ([string]$script:cmbDisk.Items[$i].Tag -eq $previousDiskIndex) { $selectedIndex = $i; break }
}
}
if ($selectedIndex -lt 0 -and $script:cmbDisk.Items.Count -eq 1) { $selectedIndex = 0 }
$script:cmbDisk.SelectedIndex = $selectedIndex
}
}
finally {
$script:cmbDisk.EndUpdate()
$script:RefreshingDiskList = $false
}
$script:SelectedDiskIndex = if ($script:cmbDisk.SelectedIndex -ge 0) { [string]$script:cmbDisk.SelectedItem.Tag } else { $null }
Write-DeployInventory -InventoryDir $script:InventoryDir -InventoryFile $script:InventoryFile -HardwareInfo $info -DeploymentIPv4 $script:DeployIPv4 -VncPassword $script:VncPassword -LogAction $script:SourceLogAction
& $script:SourceLogAction "Hardware inventory refreshed." "SUCCESS"
foreach ($errorMessage in $info.Errors) {
& $script:SourceLogAction $errorMessage "WARNING"
}
if ($info.Disks.Count -eq 0) {
& $script:SourceLogAction "No physical disk detected." "WARNING"
}
}
catch {
& $script:SourceLogAction "Inventory error: $($_.Exception.Message)" "ERROR"
if (-not $script:NonInteractive) {
Show-DeployCenterMessage -Message $_.Exception.Message -Title 'DeployCenter error' -Icon Error -Owner $script:form -Theme $script:Theme -Font $script:FontRegular
}
}
finally {
$script:btnRefresh.Enabled = $true
$script:form.Cursor = [System.Windows.Forms.Cursors]::Default
}
}
function Get-PartitionProfileSizingSelection {
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)]$DiskInfo,
[System.Collections.IDictionary]$InitialValues = @{}
)
$capacity = Get-DeployCenterPartitionSizingCapacity -Profile $Profile -DiskSizeBytes ([long]$DiskInfo.SizeBytes)
return Show-DeployCenterPartitionSizingDialog `
-Profile $Profile `
-AvailableGB $capacity.UsableGB `
-Theme $script:Theme `
-Font $script:FontRegular `
-Owner $script:form `
-InitialValues $InitialValues
}
function Set-PartitionProfileSizingSelection {
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)]$DiskInfo,
[System.Collections.IDictionary]$InitialValues = @{}
)
$selection = Get-PartitionProfileSizingSelection -Profile $Profile -DiskInfo $DiskInfo -InitialValues $InitialValues
if ($null -eq $selection) { return $false }
$script:SelectedPartitionSizing = New-DeployCenterPartitionSizingState -Profile $Profile -DiskInfo $DiskInfo -Selection $selection
return $true
}
function Update-DeployCenterLayout {
if ($null -eq $script:form -or $script:form.IsDisposed) { return }
$w = $script:form.ClientSize.Width
$h = $script:form.ClientSize.Height
$m = 24
$contentWidth = [Math]::Max(300, $w - (2 * $m))
# Header: keep the theme selector aligned to the right edge.
if ($null -ne $script:cmbTheme) {
$script:cmbTheme.Location = New-Object System.Drawing.Point([Math]::Max($m, $w - $m - $script:cmbTheme.Width), 25)
}
# The two upper panels keep their height but stretch horizontally.
$script:groupPC.Location = New-Object System.Drawing.Point($m, 100)
$script:groupPC.Size = New-Object System.Drawing.Size($contentWidth, 90)
$script:groupDisks.Location = New-Object System.Drawing.Point($m, 205)
# Keep the panel bottom clear of the action buttons at the minimum 800x600 layout.
$script:groupDisks.Size = New-Object System.Drawing.Size($contentWidth, 270)
Set-DiskSelectorWidth
$footerHeight = 26
$buttonHeightLocal = 38
$buttonYLocal = $h - $footerHeight - 14 - $buttonHeightLocal
$buttonWidthLocal = 140
$buttonGapLocal = 12
$script:btnClose.Size = New-Object System.Drawing.Size($buttonWidthLocal, $buttonHeightLocal)
$script:btnRefresh.Size = New-Object System.Drawing.Size($buttonWidthLocal, $buttonHeightLocal)
$script:btnDeploy.Size = New-Object System.Drawing.Size($buttonWidthLocal, $buttonHeightLocal)
$script:btnLog.Size = New-Object System.Drawing.Size($buttonWidthLocal, $buttonHeightLocal)
$script:btnClose.Location = New-Object System.Drawing.Point([Math]::Max($m, $w - $m - $buttonWidthLocal), $buttonYLocal)
$script:btnDeploy.Location = New-Object System.Drawing.Point([Math]::Max($m, $script:btnClose.Left - $buttonGapLocal - $buttonWidthLocal), $buttonYLocal)
$script:btnRefresh.Location = New-Object System.Drawing.Point([Math]::Max($m, $script:btnDeploy.Left - $buttonGapLocal - $buttonWidthLocal), $buttonYLocal)
$script:btnLog.Location = New-Object System.Drawing.Point($m, $buttonYLocal)
Set-DiskSelectorWidth
}