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 }