Files
DeployCenter/Modules/UI.psm1
T

2057 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)
$stepLabel = if ([string]$step -eq 'Applying Windows to ') {
Get-DeployCenterText -Text 'Applying Windows to target partition…'
} else {
Get-DeployCenterText -Text $step
}
$name.Text = $stepLabel
$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
}