feat: Dell Command | Update step (step 11) + download link on landing page

scripts/11-dell-update.ps1:
- Detects Dell via Win32_ComputerSystem.Manufacturer (skips silently on non-Dell)
- Installs Dell.CommandUpdate.Universal via winget (silent)
- Runs dcu-cli.exe /applyUpdates -silent -reboot=disable (all categories)
- BIOS/firmware staged, completes on restart after deployment
- Exit codes 0/1/5 all treated as success

Deploy-Windows.ps1:
- Added Step 11 - Dell Command | Update (dellUpdate=true default)

internal/runner/runner.go, internal/config/config.go:
- dellUpdate step registered in AllSteps() and DefaultConfig

web/spec/index.html:
- Step 11 card with flag-done rows, sidebar link, comment-widget issue #16
- STEP_SCRIPT map updated for step-dell

web/index.html:
- Dynamic download strip: fetches latest Forgejo release via API,
  shows Download xetup.exe with version + file size
- Updated Go TUI card text (no longer "zatim ve vyvoji")

web/data/descriptions.json: regenerated (13 scripts, 80 items)

Forgejo: issue #16 created, release v0.1.0 published with xetup.exe (5.2 MB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
X9 Dev 2026-04-16 10:49:15 +02:00
parent 1198de3c49
commit be7a7236df
7 changed files with 271 additions and 103 deletions

View file

@ -113,6 +113,7 @@ $stepsEnabled = @{
network = $true network = $true
pcIdentity = $true pcIdentity = $true
activation = $true activation = $true
dellUpdate = $true
} }
if ($Config -and $Config.steps) { if ($Config -and $Config.steps) {
foreach ($key in @($stepsEnabled.Keys)) { foreach ($key in @($stepsEnabled.Keys)) {
@ -221,6 +222,15 @@ if ($stepsEnabled['network']) {
} }
} else { Skip-Step "Step 9 - Network" } } else { Skip-Step "Step 9 - Network" }
# -----------------------------------------------------------------------
# Step 11 - Dell Command | Update (auto-skipped on non-Dell hardware)
# -----------------------------------------------------------------------
if ($stepsEnabled['dellUpdate']) {
Invoke-Step -Name "Step 11 - Dell Command | Update" -Action {
& "$ScriptRoot\scripts\11-dell-update.ps1" -Config $Config -LogFile $LogFile
}
} else { Skip-Step "Step 11 - Dell Command | Update" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 10 - PC identity (rename + C:\X9) - runs last, rename needs restart # Step 10 - PC identity (rename + C:\X9) - runs last, rename needs restart
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------

View file

@ -61,17 +61,18 @@ func DefaultConfig() Config {
}, },
}, },
Steps: map[string]bool{ Steps: map[string]bool{
"adminAccount": true, "adminAccount": true,
"bloatware": true, "bloatware": true,
"software": true, "software": true,
"systemRegistry": true, "systemRegistry": true,
"defaultProfile": true, "defaultProfile": true,
"personalization": true, "personalization": true,
"scheduledTasks": true, "scheduledTasks": true,
"backinfo": true, "backinfo": true,
"activation": true, "activation": true,
"network": true, "dellUpdate": true,
"pcIdentity": true, "network": true,
"pcIdentity": true,
}, },
} }
} }

View file

@ -34,6 +34,7 @@ func AllSteps() []Step {
{ID: "scheduledTasks", Num: "06", Name: "Scheduled Tasks", ScriptName: "06-scheduled-tasks.ps1"}, {ID: "scheduledTasks", Num: "06", Name: "Scheduled Tasks", ScriptName: "06-scheduled-tasks.ps1"},
{ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"}, {ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"},
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"}, {ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
{ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"},
{ID: "network", Num: "09", Name: "Network discovery", ScriptName: "10-network.ps1"}, {ID: "network", Num: "09", Name: "Network discovery", ScriptName: "10-network.ps1"},
{ID: "pcIdentity", Num: "10", Name: "PC identita", ScriptName: "09-pc-identity.ps1"}, {ID: "pcIdentity", Num: "10", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
} }

122
scripts/11-dell-update.ps1 Normal file
View file

@ -0,0 +1,122 @@
<#
.SYNOPSIS
Detects Dell hardware, installs Dell Command | Update, and applies all available updates.
.DESCRIPTION
Checks Win32_ComputerSystem.Manufacturer - if not Dell, the step exits silently without
error so the same deployment script works on any hardware. On Dell machines, installs
Dell Command | Update (Universal) via winget and immediately runs /applyUpdates with
-reboot=disable. This covers drivers, firmware, and BIOS. BIOS and firmware updates are
staged at this point and finalize automatically during the restart that closes the
deployment. The operator does not need to run a separate update pass after setup.
.ITEMS
detekce-dell-hw-win32-computersystem: Reads Win32_ComputerSystem.Manufacturer. If the string does not contain "Dell", the entire step is skipped without error. The deployment continues normally on HP, Lenovo, or any other brand.
instalace-dell-command-update-via-winget: Installs Dell.CommandUpdate.Universal silently via winget. This is the current DCU generation (v5+) that supports Latitude, OptiPlex, Precision, Vostro, and XPS on Win10 and Win11.
spusteni-vsech-aktualizaci-drivery-firmware-bios: Runs dcu-cli.exe /applyUpdates -silent -reboot=disable. Covers driver, firmware, and BIOS categories in a single pass. The -reboot=disable flag prevents DCU from rebooting mid-deployment.
bios-firmware-staging-reboot: BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed.
#>
param(
[object]$Config,
[string]$LogFile
)
$ErrorActionPreference = "Continue"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
Add-Content -Path $LogFile -Value $line -Encoding UTF8
switch ($Level) {
"OK" { Write-Host $line -ForegroundColor Green }
"ERROR" { Write-Host $line -ForegroundColor Red }
"WARN" { Write-Host $line -ForegroundColor Yellow }
"STEP" { Write-Host $line -ForegroundColor Cyan }
default { Write-Host $line }
}
}
# -----------------------------------------------------------------------
# Detect Dell hardware
# -----------------------------------------------------------------------
Write-Log "Checking hardware manufacturer" -Level INFO
try {
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
$manufacturer = $cs.Manufacturer
$model = $cs.Model
Write-Log " Manufacturer: $manufacturer Model: $model" -Level INFO
}
catch {
Write-Log " Failed to query Win32_ComputerSystem: $_" -Level ERROR
return
}
if ($manufacturer -notmatch "Dell") {
Write-Log "Not a Dell machine ($manufacturer) - step skipped" -Level WARN
return
}
Write-Log "Dell confirmed: $model" -Level OK
# -----------------------------------------------------------------------
# Install Dell Command | Update via winget
# -----------------------------------------------------------------------
Write-Log "Installing Dell Command | Update (Universal)..." -Level STEP
$wingetArgs = @(
"install",
"--id", "Dell.CommandUpdate.Universal",
"--silent",
"--accept-package-agreements",
"--accept-source-agreements"
)
$wingetOutput = & winget @wingetArgs 2>&1
$wingetExit = $LASTEXITCODE
$wingetOutput | ForEach-Object { Write-Log " [winget] $_" -Level INFO }
if ($wingetExit -ne 0 -and $wingetExit -ne 1638) { # 1638 = already installed
Write-Log " winget exit code $wingetExit - checking if DCU is already present" -Level WARN
}
# Locate dcu-cli.exe (path is the same for x64 and Universal edition)
$dcuCandidates = @(
"C:\Program Files\Dell\CommandUpdate\dcu-cli.exe",
"C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe"
)
$dcuCli = $dcuCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $dcuCli) {
Write-Log " dcu-cli.exe not found - cannot run updates" -Level ERROR
return
}
Write-Log " dcu-cli.exe found: $dcuCli" -Level OK
# -----------------------------------------------------------------------
# Run all available updates (drivers, firmware, BIOS)
# -reboot=disable -> no mid-deployment reboot; BIOS/firmware staged for next restart
# -----------------------------------------------------------------------
Write-Log "Running Dell Command | Update (all categories, no auto-reboot)..." -Level STEP
Write-Log " This may take several minutes depending on available updates" -Level INFO
$dcuOutput = & $dcuCli /applyUpdates -silent -reboot=disable 2>&1
$exitCode = $LASTEXITCODE
$dcuOutput | ForEach-Object { Write-Log " [DCU] $_" -Level INFO }
Write-Log " DCU exit code: $exitCode" -Level INFO
# Dell Command | Update exit codes:
# 0 = completed, no updates required or updates applied (no reboot needed)
# 1 = updates applied, reboot required to finalize BIOS/firmware
# 5 = no applicable updates found for this system
# others = error or partial failure
switch ($exitCode) {
0 { Write-Log "Dell Command | Update: complete (no reboot required)" -Level OK }
1 { Write-Log "Dell Command | Update: updates staged - BIOS/firmware will finalize on restart" -Level OK }
5 { Write-Log "Dell Command | Update: no applicable updates for this model" -Level OK }
default { Write-Log "Dell Command | Update: exit code $exitCode - review DCU log in C:\ProgramData\Dell\UpdateService\Logs" -Level WARN }
}
Write-Log "Step 11 complete" -Level OK

View file

@ -146,5 +146,15 @@
"povolit-ping-icmp-firewall": "Enables \"File and Printer Sharing (Echo Request)\" firewall rules for ICMPv4 and ICMPv6. ICMP echo is disabled by default on clean Windows. Required for network diagnostics, monitoring tools, and basic connectivity verification.", "povolit-ping-icmp-firewall": "Enables \"File and Printer Sharing (Echo Request)\" firewall rules for ICMPv4 and ICMPv6. ICMP echo is disabled by default on clean Windows. Required for network diagnostics, monitoring tools, and basic connectivity verification.",
"zapnout-network-discovery": "Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines." "zapnout-network-discovery": "Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines."
} }
},
"11-dell-update": {
"synopsis": "Detects Dell hardware, installs Dell Command | Update, and applies all available updates.",
"description": "Checks Win32_ComputerSystem.Manufacturer - if not Dell, the step exits silently without\nerror so the same deployment script works on any hardware. On Dell machines, installs\nDell Command | Update (Universal) via winget and immediately runs /applyUpdates with\n-reboot=disable. This covers drivers, firmware, and BIOS. BIOS and firmware updates are\nstaged at this point and finalize automatically during the restart that closes the\ndeployment. The operator does not need to run a separate update pass after setup.",
"items": {
"detekce-dell-hw-win32-computersystem": "Reads Win32_ComputerSystem.Manufacturer. If the string does not contain \"Dell\", the entire step is skipped without error. The deployment continues normally on HP, Lenovo, or any other brand.",
"instalace-dell-command-update-via-winget": "Installs Dell.CommandUpdate.Universal silently via winget. This is the current DCU generation (v5+) that supports Latitude, OptiPlex, Precision, Vostro, and XPS on Win10 and Win11.",
"spusteni-vsech-aktualizaci-drivery-firmware-bios": "Runs dcu-cli.exe /applyUpdates -silent -reboot=disable. Covers driver, firmware, and BIOS categories in a single pass. The -reboot=disable flag prevents DCU from rebooting mid-deployment.",
"bios-firmware-staging-reboot": "BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed."
}
} }
} }

View file

@ -34,28 +34,10 @@
align-items: center; align-items: center;
gap: .75rem; gap: .75rem;
} }
.logo-text { .logo-text { font-size: 1.2rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
font-size: 1.2rem; .logo-sub { font-size: .8rem; color: var(--muted); margin-left: .2rem; }
font-weight: 700; header nav { margin-left: auto; display: flex; gap: 1.5rem; }
color: #fff; header nav a { color: var(--muted); text-decoration: none; font-size: .88rem; transition: color .15s; }
letter-spacing: -.02em;
}
.logo-sub {
font-size: .8rem;
color: var(--muted);
margin-left: .2rem;
}
header nav {
margin-left: auto;
display: flex;
gap: 1.5rem;
}
header nav a {
color: var(--muted);
text-decoration: none;
font-size: .88rem;
transition: color .15s;
}
header nav a:hover { color: var(--text); } header nav a:hover { color: var(--text); }
main { main {
@ -81,92 +63,64 @@
} }
h1 { h1 {
font-size: 2.8rem; font-size: 2.8rem; font-weight: 800; color: #fff;
font-weight: 800; letter-spacing: -.04em; line-height: 1.1;
color: #fff; margin-bottom: 1rem; max-width: 600px;
letter-spacing: -.04em;
line-height: 1.1;
margin-bottom: 1rem;
max-width: 600px;
} }
h1 span { color: var(--blue); } h1 span { color: var(--blue); }
.tagline { .tagline {
font-size: 1.1rem; font-size: 1.1rem; color: var(--muted); max-width: 480px;
color: var(--muted); line-height: 1.6; margin-bottom: 2.5rem;
max-width: 480px;
line-height: 1.6;
margin-bottom: 2.5rem;
} }
/* ---- ACTIONS ---- */
.actions { .actions {
display: flex; display: flex; gap: .75rem; flex-wrap: wrap;
gap: .75rem; justify-content: center; margin-bottom: 1.5rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 4rem;
} }
.btn-primary { .btn-primary {
padding: .6rem 1.4rem; padding: .6rem 1.4rem; background: var(--accent-bright); color: #fff;
background: var(--accent-bright); border: 1px solid transparent; border-radius: 8px;
color: #fff; font-size: .95rem; font-weight: 600; text-decoration: none; transition: opacity .15s;
border: 1px solid transparent;
border-radius: 8px;
font-size: .95rem;
font-weight: 600;
text-decoration: none;
transition: opacity .15s;
} }
.btn-primary:hover { opacity: .85; } .btn-primary:hover { opacity: .85; }
.btn-secondary { .btn-secondary {
padding: .6rem 1.4rem; padding: .6rem 1.4rem; background: transparent; color: var(--text);
background: transparent; border: 1px solid var(--border); border-radius: 8px;
color: var(--text); font-size: .95rem; text-decoration: none; transition: background .15s;
border: 1px solid var(--border);
border-radius: 8px;
font-size: .95rem;
text-decoration: none;
transition: background .15s;
} }
.btn-secondary:hover { background: var(--card); } .btn-secondary:hover { background: var(--card); }
/* ---- DOWNLOAD STRIP ---- */
.download-strip {
margin-bottom: 3.5rem;
display: flex; align-items: center; gap: .6rem;
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; padding: .6rem 1.1rem;
font-size: .85rem;
}
.download-strip a {
color: var(--blue); text-decoration: none; font-weight: 600;
display: flex; align-items: center; gap: .35rem;
}
.download-strip a:hover { text-decoration: underline; }
.download-strip .dl-meta { color: var(--muted); font-size: .78rem; }
.download-strip .dl-sep { color: var(--border); }
/* ---- CARDS ---- */
.cards { .cards {
display: grid; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; max-width: 720px; width: 100%;
gap: 1rem;
max-width: 720px;
width: 100%;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.2rem;
text-align: left;
}
.card-icon {
font-size: 1.3rem;
margin-bottom: .6rem;
display: block;
}
.card h3 {
font-size: .9rem;
font-weight: 600;
color: #fff;
margin-bottom: .3rem;
}
.card p {
font-size: .82rem;
color: var(--muted);
line-height: 1.4;
} }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 1.2rem; text-align: left; }
.card-icon { font-size: 1.3rem; margin-bottom: .6rem; display: block; }
.card h3 { font-size: .9rem; font-weight: 600; color: #fff; margin-bottom: .3rem; }
.card p { font-size: .82rem; color: var(--muted); line-height: 1.4; }
footer { footer {
border-top: 1px solid var(--border); border-top: 1px solid var(--border); padding: 1.2rem 2rem;
padding: 1.2rem 2rem; text-align: center; font-size: .8rem; color: var(--muted);
text-align: center;
font-size: .8rem;
color: var(--muted);
} }
footer a { color: var(--muted); text-decoration: none; } footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); } footer a:hover { color: var(--text); }
@ -201,6 +155,19 @@
<a href="https://git.xetup.x9.cz/x9/xetup" class="btn-secondary">Git repozitar</a> <a href="https://git.xetup.x9.cz/x9/xetup" class="btn-secondary">Git repozitar</a>
</div> </div>
<!-- Dynamic download strip filled by JS from Forgejo releases API -->
<div class="download-strip" id="dl-strip" style="display:none">
<a id="dl-link" href="#" download>
&#11015; xetup.exe
</a>
<span class="dl-sep">&middot;</span>
<span class="dl-meta" id="dl-meta">nacitam...</span>
<span class="dl-sep">&middot;</span>
<a href="https://git.xetup.x9.cz/x9/xetup/releases" style="color:var(--muted);font-size:.78rem;text-decoration:none">
vsechny verze
</a>
</div>
<div class="cards"> <div class="cards">
<div class="card"> <div class="card">
<span class="card-icon">&#9881;</span> <span class="card-icon">&#9881;</span>
@ -220,7 +187,7 @@
<div class="card"> <div class="card">
<span class="card-icon">&#128640;</span> <span class="card-icon">&#128640;</span>
<h3>Go TUI launcher</h3> <h3>Go TUI launcher</h3>
<p>xetup.exe &mdash; jednotny binarni spoustec. Zatim ve vyvoji.</p> <p>xetup.exe &mdash; jednotny binarni spoustec s TUI formularom a live logem.</p>
</div> </div>
</div> </div>
</main> </main>
@ -233,5 +200,34 @@
<a href="/spec/">Specifikace</a> <a href="/spec/">Specifikace</a>
</footer> </footer>
<script>
(function() {
const API = '/forgejo-api';
const REPO = 'x9/xetup';
const TOKEN = 'e67f674af71847c4349b79b51d2b66a1ea41d031';
const HEADS = { 'Authorization': 'token ' + TOKEN };
fetch(API + '/repos/' + REPO + '/releases?limit=1', { headers: HEADS })
.then(r => r.json())
.then(releases => {
if (!Array.isArray(releases) || !releases.length) return;
const rel = releases[0];
const asset = (rel.assets || []).find(a => a.name === 'xetup.exe');
if (!asset) return;
const strip = document.getElementById('dl-strip');
const link = document.getElementById('dl-link');
const meta = document.getElementById('dl-meta');
const sizeMB = (asset.size / 1048576).toFixed(1);
link.href = asset.browser_download_url;
link.textContent = '\u2b15 xetup.exe';
meta.textContent = rel.tag_name + ' \u00b7 ' + sizeMB + ' MB \u00b7 Windows x64';
strip.style.display = '';
})
.catch(() => {});
})();
</script>
</body> </body>
</html> </html>

View file

@ -508,6 +508,7 @@
<h4>Planovane kroky</h4> <h4>Planovane kroky</h4>
<a href="#step-pc">09 &ndash; PC identita + C:\X9</a> <a href="#step-pc">09 &ndash; PC identita + C:\X9</a>
<a href="#step-net">10 &ndash; Network discovery</a> <a href="#step-net">10 &ndash; Network discovery</a>
<a href="#step-dell">11 &ndash; Dell Command | Update</a>
<a href="#step-taskbar">Taskbar profily</a> <a href="#step-taskbar">Taskbar profily</a>
<hr class="sidebar-divider"> <hr class="sidebar-divider">
<h4>Architektura</h4> <h4>Architektura</h4>
@ -831,6 +832,32 @@
</div> </div>
</div> </div>
<!-- STEP 11 -->
<div class="step" id="step-dell">
<div class="step-header">
<span class="step-num">11</span>
<span class="step-title">Dell Command | Update</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Detekce Dell hardware (<code>Win32_ComputerSystem</code>)</td><td>Non-Dell stroj krok preskoci bez chyby &ndash; stejny skript pro vsechny HW</td></tr>
<tr class="flag-done"><td>Instalace Dell Command | Update via winget</td><td><code>Dell.CommandUpdate.Universal</code> &ndash; silent, Win10 + Win11</td></tr>
<tr class="flag-done"><td>Spusteni vsech aktualizaci: drivery, firmware, BIOS</td><td><code>dcu-cli.exe /applyUpdates -silent -reboot=disable</code></td></tr>
<tr class="flag-done"><td>BIOS/firmware se staging &ndash; dokonci se pri restartu</td><td>Restart po konci deploymenty (krok 10 rename) vse dokonci</td></tr>
</table>
<div class="note">
Non-Dell stroje: krok se preskoci automaticky, zadna chyba. Dell Latitude, OptiPlex,
Precision, Vostro, XPS &ndash; vsechny podporovane DCU Universal.<br><br>
<strong>Casova narocnost:</strong> 5&ndash;20 minut podle poctu dostupnych aktualizaci a rychlosti siteho pripojeni.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>11-dell-update.ps1</code></span>
<div class="comment-widget" data-issue="16"></div>
</div>
</div>
<!-- TASKBAR --> <!-- TASKBAR -->
<div class="step" id="step-taskbar"> <div class="step" id="step-taskbar">
<div class="step-header"> <div class="step-header">
@ -1102,9 +1129,10 @@
'step-05': '05-personalization', 'step-05': '05-personalization',
'step-06': '06-scheduled-tasks', 'step-06': '06-scheduled-tasks',
'step-07': '07-backinfo', 'step-07': '07-backinfo',
'step-pc': '09-pc-identity', 'step-pc': '09-pc-identity',
'step-net': '10-network', 'step-net': '10-network',
'step-08': '08-activation', 'step-08': '08-activation',
'step-dell': '11-dell-update',
}; };
function getItemDesc(stepId, slug) { function getItemDesc(stepId, slug) {