All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 5m18s
256 lines
10 KiB
PowerShell
256 lines
10 KiB
PowerShell
#Requires -Version 5.1
|
|
# SilverMetal WinPE pre-config collector (WinForms UI). Runs in WinPE under the
|
|
# ADK WinPE-NetFx + WinPE-PowerShell optional components. ASCII body only (WinPE
|
|
# PowerShell 5.1 mis-parses smart quotes / em-dashes). On Finish it generates the
|
|
# answer file and launches legacy Setup; on Cancel / error it exits 1 so the
|
|
# wrapping Start-Collector.cmd falls back to the default autounattend.xml.
|
|
|
|
try {
|
|
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
|
|
# Validation helpers + answer-file generator live next to this script under X:\sm\.
|
|
. (Join-Path $PSScriptRoot 'Test-SmInput.ps1')
|
|
. (Join-Path $PSScriptRoot 'New-SmAnswerFile.ps1')
|
|
|
|
# --- palette ---------------------------------------------------------------
|
|
$colBg = [Drawing.Color]::FromArgb(18, 18, 22)
|
|
$colPanel = [Drawing.Color]::FromArgb(28, 28, 34)
|
|
$colText = [Drawing.Color]::FromArgb(232, 232, 238)
|
|
$colMuted = [Drawing.Color]::FromArgb(150, 150, 162)
|
|
$colAccent = [Drawing.Color]::FromArgb(96, 200, 170)
|
|
$colError = [Drawing.Color]::FromArgb(236, 110, 110)
|
|
$colField = [Drawing.Color]::FromArgb(40, 40, 48)
|
|
|
|
$fontBase = New-Object Drawing.Font('Segoe UI', 11)
|
|
$fontTitle = New-Object Drawing.Font('Segoe UI Semibold', 26, [Drawing.FontStyle]::Bold)
|
|
$fontLabel = New-Object Drawing.Font('Segoe UI', 10)
|
|
|
|
# --- form ------------------------------------------------------------------
|
|
$form = New-Object Windows.Forms.Form
|
|
$form.Text = 'SilverMetal Setup'
|
|
$form.WindowState = 'Maximized'
|
|
$form.FormBorderStyle = 'None'
|
|
$form.BackColor = $colBg
|
|
$form.ForeColor = $colText
|
|
$form.Font = $fontBase
|
|
$form.KeyPreview = $true
|
|
|
|
# Helpers to build consistent controls.
|
|
function New-Label([string]$text, [int]$x, [int]$y, [int]$w = 220) {
|
|
$l = New-Object Windows.Forms.Label
|
|
$l.Text = $text; $l.AutoSize = $false
|
|
$l.Location = New-Object Drawing.Point($x, $y)
|
|
$l.Size = New-Object Drawing.Size($w, 24)
|
|
$l.ForeColor = $colMuted; $l.Font = $fontLabel
|
|
$form.Controls.Add($l); $l
|
|
}
|
|
function New-Field([int]$x, [int]$y, [int]$w = 360, [bool]$mask = $false) {
|
|
$t = New-Object Windows.Forms.TextBox
|
|
$t.Location = New-Object Drawing.Point($x, $y)
|
|
$t.Size = New-Object Drawing.Size($w, 28)
|
|
$t.BackColor = $colField; $t.ForeColor = $colText
|
|
$t.BorderStyle = 'FixedSingle'; $t.Font = $fontBase
|
|
if ($mask) { $t.UseSystemPasswordChar = $true }
|
|
$form.Controls.Add($t); $t
|
|
}
|
|
|
|
# Title + subtitle (left rail).
|
|
$title = New-Object Windows.Forms.Label
|
|
$title.Text = 'SilverMetal'
|
|
$title.Font = $fontTitle; $title.ForeColor = $colAccent
|
|
$title.AutoSize = $true
|
|
$title.Location = New-Object Drawing.Point(80, 70)
|
|
$form.Controls.Add($title)
|
|
|
|
$subtitle = New-Object Windows.Forms.Label
|
|
$subtitle.Text = 'Pre-install configuration. Set your account, machine and security options before Windows installs.'
|
|
$subtitle.Font = $fontLabel; $subtitle.ForeColor = $colMuted
|
|
$subtitle.AutoSize = $false
|
|
$subtitle.Location = New-Object Drawing.Point(82, 120)
|
|
$subtitle.Size = New-Object Drawing.Size(640, 24)
|
|
$form.Controls.Add($subtitle)
|
|
|
|
# --- left column fields ----------------------------------------------------
|
|
$colX = 80; $fldX = 80; $y = 180; $rowH = 64
|
|
|
|
New-Label 'Display name' $colX $y | Out-Null
|
|
$txtDisplay = New-Field $fldX ($y + 26)
|
|
$y += $rowH
|
|
|
|
New-Label 'Username' $colX $y | Out-Null
|
|
$txtUser = New-Field $fldX ($y + 26)
|
|
$y += $rowH
|
|
|
|
New-Label 'Password' $colX $y | Out-Null
|
|
$txtPass = New-Field $fldX ($y + 26) 360 $true
|
|
$y += $rowH
|
|
|
|
New-Label 'Confirm password' $colX $y | Out-Null
|
|
$txtPassC = New-Field $fldX ($y + 26) 360 $true
|
|
$y += $rowH
|
|
|
|
New-Label 'Computer name' $colX $y | Out-Null
|
|
$txtComputer = New-Field $fldX ($y + 26)
|
|
$y += $rowH
|
|
|
|
# --- right column: flavour + BitLocker -------------------------------------
|
|
$rX = 540; $rY = 180
|
|
|
|
$grpFlavour = New-Object Windows.Forms.GroupBox
|
|
$grpFlavour.Text = 'Flavour'
|
|
$grpFlavour.ForeColor = $colMuted
|
|
$grpFlavour.Location = New-Object Drawing.Point($rX, $rY)
|
|
$grpFlavour.Size = New-Object Drawing.Size(360, 220)
|
|
$form.Controls.Add($grpFlavour)
|
|
|
|
# 'essentials' is the always-on baseline role (no flavour manifest), not a selectable
|
|
# flavour -- the four below are the real flavours; essentials apps ship with every one.
|
|
$flavours = @('developer', 'journalist', 'daily-driver', 'privacy-max')
|
|
$radioFlavours = @()
|
|
$fy = 32
|
|
foreach ($f in $flavours) {
|
|
$rb = New-Object Windows.Forms.RadioButton
|
|
$rb.Text = $f; $rb.ForeColor = $colText; $rb.Font = $fontBase
|
|
$rb.Location = New-Object Drawing.Point(20, $fy)
|
|
$rb.Size = New-Object Drawing.Size(320, 28)
|
|
$rb.Tag = $f
|
|
if ($f -eq 'daily-driver') { $rb.Checked = $true }
|
|
$grpFlavour.Controls.Add($rb)
|
|
$radioFlavours += $rb
|
|
$fy += 36
|
|
}
|
|
|
|
$chkBitLocker = New-Object Windows.Forms.CheckBox
|
|
$chkBitLocker.Text = 'Enable BitLocker (TPM + PIN)'
|
|
$chkBitLocker.ForeColor = $colText; $chkBitLocker.Font = $fontBase
|
|
$chkBitLocker.AutoSize = $true
|
|
$chkBitLocker.Location = New-Object Drawing.Point($rX, ($rY + 240))
|
|
$form.Controls.Add($chkBitLocker)
|
|
|
|
$lblPin = New-Label 'BitLocker PIN' $rX ($rY + 280) | Out-Null
|
|
$lblPin = $form.Controls[$form.Controls.Count - 1]
|
|
$txtPin = New-Field ($rX) ($rY + 306) 360 $true
|
|
$lblPinC = New-Label 'Confirm PIN' $rX ($rY + 348) | Out-Null
|
|
$lblPinC = $form.Controls[$form.Controls.Count - 1]
|
|
$txtPinC = New-Field ($rX) ($rY + 374) 360 $true
|
|
|
|
# PIN fields disabled until BitLocker is checked.
|
|
$setPinEnabled = {
|
|
$on = $chkBitLocker.Checked
|
|
$txtPin.Enabled = $on; $txtPinC.Enabled = $on
|
|
$fg = if ($on) { $colMuted } else { [Drawing.Color]::FromArgb(90, 90, 98) }
|
|
$lblPin.ForeColor = $fg; $lblPinC.ForeColor = $fg
|
|
}
|
|
$chkBitLocker.Add_CheckedChanged($setPinEnabled)
|
|
& $setPinEnabled
|
|
|
|
# --- status line -----------------------------------------------------------
|
|
$lblStatus = New-Object Windows.Forms.Label
|
|
$lblStatus.Text = ''
|
|
$lblStatus.ForeColor = $colError; $lblStatus.Font = $fontBase
|
|
$lblStatus.AutoSize = $false
|
|
$lblStatus.Location = New-Object Drawing.Point(80, ($y + 8))
|
|
$lblStatus.Size = New-Object Drawing.Size(820, 28)
|
|
$form.Controls.Add($lblStatus)
|
|
|
|
# --- buttons ---------------------------------------------------------------
|
|
function Style-Button($b, $primary) {
|
|
$b.FlatStyle = 'Flat'; $b.Font = $fontBase
|
|
$b.Size = New-Object Drawing.Size(150, 40)
|
|
$b.FlatAppearance.BorderSize = 1
|
|
if ($primary) {
|
|
$b.BackColor = $colAccent; $b.ForeColor = $colBg
|
|
$b.FlatAppearance.BorderColor = $colAccent
|
|
} else {
|
|
$b.BackColor = $colPanel; $b.ForeColor = $colText
|
|
$b.FlatAppearance.BorderColor = $colMuted
|
|
}
|
|
}
|
|
|
|
$btnDefaults = New-Object Windows.Forms.Button
|
|
$btnDefaults.Text = 'Use defaults'
|
|
Style-Button $btnDefaults $false
|
|
$btnDefaults.Location = New-Object Drawing.Point(80, ($y + 48))
|
|
$form.Controls.Add($btnDefaults)
|
|
|
|
$btnCancel = New-Object Windows.Forms.Button
|
|
$btnCancel.Text = 'Cancel'
|
|
Style-Button $btnCancel $false
|
|
$btnCancel.Location = New-Object Drawing.Point(580, ($y + 48))
|
|
$form.Controls.Add($btnCancel)
|
|
|
|
$btnFinish = New-Object Windows.Forms.Button
|
|
$btnFinish.Text = 'Finish'
|
|
Style-Button $btnFinish $true
|
|
$btnFinish.Location = New-Object Drawing.Point(750, ($y + 48))
|
|
$form.Controls.Add($btnFinish)
|
|
|
|
# --- behaviour -------------------------------------------------------------
|
|
$btnDefaults.Add_Click({
|
|
$txtDisplay.Text = 'SilverMetal User'
|
|
$txtUser.Text = 'silver'
|
|
$txtComputer.Text = 'SILVER-PC'
|
|
# Leave passwords blank on purpose: the user must set them.
|
|
$txtPass.Text = ''; $txtPassC.Text = ''
|
|
foreach ($rb in $radioFlavours) { $rb.Checked = ($rb.Tag -eq 'daily-driver') }
|
|
$chkBitLocker.Checked = $false
|
|
$txtPin.Text = ''; $txtPinC.Text = ''
|
|
$lblStatus.ForeColor = $colMuted
|
|
$lblStatus.Text = 'Defaults applied. Set a password to continue.'
|
|
})
|
|
|
|
$btnCancel.Add_Click({ [Environment]::Exit(1) })
|
|
$form.Add_KeyDown({ if ($_.KeyCode -eq 'Escape') { [Environment]::Exit(1) } })
|
|
|
|
$btnFinish.Add_Click({
|
|
$lblStatus.ForeColor = $colError
|
|
|
|
$rUser = Test-SmUsername $txtUser.Text
|
|
if (-not $rUser.Ok) { $lblStatus.Text = $rUser.Message; return }
|
|
|
|
$rPass = Test-SmPassword $txtPass.Text $txtPassC.Text
|
|
if (-not $rPass.Ok) { $lblStatus.Text = $rPass.Message; return }
|
|
|
|
$rComp = Test-SmComputerName $txtComputer.Text
|
|
if (-not $rComp.Ok) { $lblStatus.Text = $rComp.Message; return }
|
|
|
|
$blEnabled = $chkBitLocker.Checked
|
|
$pin = ''
|
|
if ($blEnabled) {
|
|
$rPin = Test-SmPin $txtPin.Text $txtPinC.Text
|
|
if (-not $rPin.Ok) { $lblStatus.Text = $rPin.Message; return }
|
|
$pin = $txtPin.Text
|
|
}
|
|
|
|
$flavour = 'daily-driver'
|
|
foreach ($rb in $radioFlavours) { if ($rb.Checked) { $flavour = [string]$rb.Tag } }
|
|
|
|
$display = if ([string]::IsNullOrWhiteSpace($txtDisplay.Text)) { $txtUser.Text } else { $txtDisplay.Text }
|
|
|
|
$lblStatus.ForeColor = $colAccent
|
|
$lblStatus.Text = 'Generating answer file and starting Windows Setup...'
|
|
$form.Refresh()
|
|
|
|
$xml = New-SmAnswerFile -DisplayName $display -Username $txtUser.Text -Password $txtPass.Text `
|
|
-ComputerName $txtComputer.Text -Flavour $flavour `
|
|
-BitLockerEnable $blEnabled -BitLockerPin $pin
|
|
|
|
Set-Content -Path 'X:\sm\unattend.generated.xml' -Value $xml -Encoding UTF8
|
|
|
|
try { [void][xml](Get-Content 'X:\sm\unattend.generated.xml' -Raw) }
|
|
catch { [Environment]::Exit(1) } # bad XML -> fall back to default answer file
|
|
|
|
$setup = if (Test-Path 'X:\sources\setup.exe') { 'X:\sources\setup.exe' } else { 'X:\setup.exe' }
|
|
Start-Process -FilePath $setup -ArgumentList '/unattend:X:\sm\unattend.generated.xml' -Wait
|
|
[Environment]::Exit(0)
|
|
})
|
|
|
|
[void]$form.ShowDialog()
|
|
# If the form closes without Finish/Cancel handling exiting, treat as cancel.
|
|
[Environment]::Exit(1)
|
|
}
|
|
catch {
|
|
# Any failure -> exit 1 so Start-Collector.cmd falls back to the default answer file.
|
|
[Environment]::Exit(1)
|
|
}
|