Add Lenovo System Update as a Windows FMA (#50339)

**Related issue:** Resolves #50324

Adds Lenovo System Update as a Windows Fleet-maintained app, from winget
`Lenovo.SystemUpdate` (5.08.03.59, Inno Setup, machine scope, x86-only).
Found in a customer's ManageEngine ServiceDesk Plus Windows deployment
catalog with no Fleet equivalent. Distinct from
`lenovo-dock-manager/windows`, which we already ship.

## Verification

- Installer SHA confirmed against a local download of
`system_update_5.08.03.59.exe` (`e66794dc…53e0d`), served from
`download.lenovo.com` — a pinned vendor URL, so none of the SourceForge
mirror trouble from #50322 applies.
- Registry `DisplayName` determined offline as a bare `Lenovo System
Update`: `innoextract --info` reports `AppVerName` when set and falls
back to `AppName`, and Inno writes that same value to `DisplayName`.
This installer reports no version suffix, unlike CrystalDiskMark in
#50322 which reports `"CrystalDiskMark 9.0.3"`. That is why the exists
query here is an exact match rather than a prefix.
- Icon extracted from the installer's own `Tvsukernel.exe` resource, not
sourced from the web.
- The uninstall script targets the Inno registry key directly via the
manifest's `ProductCode` (`TVSU_is1` — a key name, not a GUID) using the
`$PACKAGE_ID` substitution, rather than string-matching `DisplayName`.

## Two things reviewers should weigh in on

**1. The exists query deliberately omits the publisher.** House style
usually pins `publisher = '...'`, but the registry `Publisher` is not
determinable offline for Inno, and the validator's log prints only the
name and version — so I could not confirm it. A wrong publisher makes
the exists query silently never match while the validator still passes,
which is the exact failure mode called out in the FMA docs. `name =
'Lenovo System Update'` is unambiguous on its own. Happy to add the
publisher clause if someone can confirm the registry value on a real
Lenovo host.

**2. This may not be validatable on the CI runner.** Lenovo System
Update is a vendor tool for Lenovo hardware, and the runner is a generic
Azure VM. If the installer refuses to run on non-Lenovo hardware this
will fail the way Dell Display and Peripheral Manager did in #50020
(which was dropped for exactly this reason, and is being retried on a
client-OS runner in #50313). Leaving this in draft until the validator
reports.

# Checklist for submitter

- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Lenovo System Update to the maintained Windows software catalog.
* Added support for silent installation and uninstallation, including
status verification and reboot-success handling.
* Added Lenovo System Update metadata, download information,
categorization, and application icon.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
kitzy
2026-07-31 20:36:01 -05:00
committed by GitHub
parent bf26771ff1
commit f082237518
8 changed files with 163 additions and 0 deletions
@@ -0,0 +1,13 @@
{
"name": "Lenovo System Update",
"slug": "lenovo-system-update/windows",
"package_identifier": "Lenovo.SystemUpdate",
"unique_identifier": "Lenovo System Update",
"exists_query": "SELECT 1 FROM programs WHERE name = 'Lenovo System Update';",
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_install.ps1",
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_uninstall.ps1",
"installer_arch": "x86",
"installer_type": "exe",
"installer_scope": "machine",
"default_categories": ["Productivity"]
}
@@ -0,0 +1,69 @@
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
$exeFilePath = "${env:INSTALLER_PATH}"
$installTimeoutSeconds = 420
$registrationTimeoutSeconds = 120
# The installer is x86, so on 64-bit Windows it registers under Wow6432Node.
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
function Get-LenovoSystemUpdateEntry {
Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
Where-Object { $_.DisplayName -eq "Lenovo System Update" } |
Select-Object -First 1
}
try {
# -Wait also waits on descendants, so wait on the installer process alone.
$process = Start-Process -FilePath "$exeFilePath" `
-ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" `
-PassThru
# Keeps .ExitCode readable after the process ends.
$null = $process.Handle
$killed = $false
if (-not $process.WaitForExit($installTimeoutSeconds * 1000)) {
Write-Host "Installer process did not exit within ${installTimeoutSeconds}s, stopping it."
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
$null = $process.WaitForExit(30 * 1000)
$killed = $true
}
$exitCode = $null
if ($process.HasExited) {
$exitCode = $process.ExitCode
Write-Host "Install exit code: $exitCode"
}
# The installer can return before the ARP entry is written.
$elapsed = 0
while (-not (Get-LenovoSystemUpdateEntry) -and ($elapsed -lt $registrationTimeoutSeconds)) {
Start-Sleep -Seconds 5
$elapsed += 5
Write-Host "Waiting for Lenovo System Update to register... ($elapsed seconds)"
}
$entry = Get-LenovoSystemUpdateEntry
if (-not $entry) {
Write-Host "Lenovo System Update did not register in Add/Remove Programs."
Exit 1
}
Write-Host "Registered '$($entry.DisplayName)' by '$($entry.Publisher)', version $($entry.DisplayVersion)."
# Registration above is the success signal; a killed process's code means nothing.
if ($killed -or $null -eq $exitCode) { Exit 0 }
# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.
if ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }
Exit $exitCode
} catch {
Write-Host "Error: $_"
Exit 1
}
@@ -0,0 +1,36 @@
# Fleet substitutes the winget ProductCode, which for this Inno installer is the
# uninstall registry key name rather than a GUID.
$packageId = $PACKAGE_ID
$uninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
# The installer is x86, so on 64-bit Windows it registers under Wow6432Node.
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$packageId",
"HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$packageId"
)
$exitCode = 0
try {
$key = $paths |
ForEach-Object { Get-ItemProperty -Path $_ -ErrorAction SilentlyContinue } |
Select-Object -First 1
if (-not $key) { Write-Host "Uninstall entry not found for '$packageId'."; Exit 0 }
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
}
Write-Host "Uninstall command: $uninstallCommand"; Write-Host "Uninstall args: $uninstallArgs"
$processOptions = @{ FilePath = $uninstallCommand; PassThru = $true; Wait = $true }
if ($uninstallArgs -ne '') { $processOptions.ArgumentList = $uninstallArgs }
$process = Start-Process @processOptions
$exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"
} catch { Write-Host "Error: $_"; Exit 1 }
Exit $exitCode
+7
View File
@@ -4859,6 +4859,13 @@
"unique_identifier": "Lenovo Dock Manager",
"description": "Lenovo Dock Manager is an application for deploying and managing firmware updates for Lenovo docks."
},
{
"name": "Lenovo System Update",
"slug": "lenovo-system-update/windows",
"platform": "windows",
"unique_identifier": "Lenovo System Update",
"description": "Lenovo System Update installs and updates Lenovo drivers, BIOS, and applications on Lenovo computers."
},
{
"name": "Lens",
"slug": "lens/darwin",
@@ -0,0 +1,22 @@
{
"versions": [
{
"version": "5.08.03.59",
"queries": {
"exists": "SELECT 1 FROM programs WHERE name = 'Lenovo System Update';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Lenovo System Update' AND version_compare(version, '5.08.03.59') < 0);"
},
"installer_url": "https://download.lenovo.com/pccbbs/thinkvantage_en/system_update_5.08.03.59.exe",
"install_script_ref": "04c67509",
"uninstall_script_ref": "aca82ee7",
"sha256": "e66794dc561a3e58e3dc68556eb053ee32b674ac9d99638e473ef7322f353e0d",
"default_categories": [
"Productivity"
]
}
],
"refs": {
"04c67509": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n$installTimeoutSeconds = 420\n$registrationTimeoutSeconds = 120\n\n# The installer is x86, so on 64-bit Windows it registers under Wow6432Node.\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Get-LenovoSystemUpdateEntry {\n Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -eq \"Lenovo System Update\" } |\n Select-Object -First 1\n}\n\ntry {\n\n# -Wait also waits on descendants, so wait on the installer process alone.\n$process = Start-Process -FilePath \"$exeFilePath\" `\n -ArgumentList \"/VERYSILENT /SUPPRESSMSGBOXES /NORESTART\" `\n -PassThru\n# Keeps .ExitCode readable after the process ends.\n$null = $process.Handle\n\n$killed = $false\nif (-not $process.WaitForExit($installTimeoutSeconds * 1000)) {\n Write-Host \"Installer process did not exit within ${installTimeoutSeconds}s, stopping it.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n $null = $process.WaitForExit(30 * 1000)\n $killed = $true\n}\n\n$exitCode = $null\nif ($process.HasExited) {\n $exitCode = $process.ExitCode\n Write-Host \"Install exit code: $exitCode\"\n}\n\n# The installer can return before the ARP entry is written.\n$elapsed = 0\nwhile (-not (Get-LenovoSystemUpdateEntry) -and ($elapsed -lt $registrationTimeoutSeconds)) {\n Start-Sleep -Seconds 5\n $elapsed += 5\n Write-Host \"Waiting for Lenovo System Update to register... ($elapsed seconds)\"\n}\n\n$entry = Get-LenovoSystemUpdateEntry\nif (-not $entry) {\n Write-Host \"Lenovo System Update did not register in Add/Remove Programs.\"\n Exit 1\n}\nWrite-Host \"Registered '$($entry.DisplayName)' by '$($entry.Publisher)', version $($entry.DisplayVersion).\"\n\n# Registration above is the success signal; a killed process's code means nothing.\nif ($killed -or $null -eq $exitCode) { Exit 0 }\n\n# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\n\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
"aca82ee7": "# Fleet substitutes the winget ProductCode, which for this Inno installer is the\n# uninstall registry key name rather than a GUID.\n$packageId = 'TVSU_is1'\n$uninstallArgs = \"/VERYSILENT /SUPPRESSMSGBOXES /NORESTART\"\n\n# The installer is x86, so on 64-bit Windows it registers under Wow6432Node.\n$paths = @(\n \"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$packageId\",\n \"HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$packageId\"\n)\n$exitCode = 0\n\ntry {\n $key = $paths |\n ForEach-Object { Get-ItemProperty -Path $_ -ErrorAction SilentlyContinue } |\n Select-Object -First 1\n\n if (-not $key) { Write-Host \"Uninstall entry not found for '$packageId'.\"; Exit 0 }\n\n $uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }\n if ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n } elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n } elseif ($uninstallCommand -match '^\\s*(\\S+)\\s*(.*)$') {\n $uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = \"$($Matches[2]) $uninstallArgs\".Trim() }\n }\n\n Write-Host \"Uninstall command: $uninstallCommand\"; Write-Host \"Uninstall args: $uninstallArgs\"\n $processOptions = @{ FilePath = $uninstallCommand; PassThru = $true; Wait = $true }\n if ($uninstallArgs -ne '') { $processOptions.ArgumentList = $uninstallArgs }\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode; Write-Host \"Uninstall exit code: $exitCode\"\n} catch { Write-Host \"Error: $_\"; Exit 1 }\n\nExit $exitCode\n"
}
}
File diff suppressed because one or more lines are too long
@@ -571,6 +571,7 @@ import LastWindowQuits from "./LastWindowQuits";
import Latest from "./Latest";
import Launchbar from "./Launchbar";
import LenovoDockManager from "./LenovoDockManager";
import LenovoSystemUpdate from "./LenovoSystemUpdate";
import Lens from "./Lens";
import LibreOffice from "./LibreOffice";
import Lightburn from "./Lightburn";
@@ -1723,6 +1724,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
latest: Latest,
launchbar: Launchbar,
"lenovo dock manager": LenovoDockManager,
"lenovo system update": LenovoSystemUpdate,
lens: Lens,
libreoffice: LibreOffice,
lightburn: Lightburn,
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB