diff --git a/ee/maintained-apps/inputs/winget/lenovo-system-update.json b/ee/maintained-apps/inputs/winget/lenovo-system-update.json new file mode 100644 index 0000000000..27193818df --- /dev/null +++ b/ee/maintained-apps/inputs/winget/lenovo-system-update.json @@ -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"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_install.ps1 new file mode 100644 index 0000000000..5c4f9410b6 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_install.ps1 @@ -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 +} diff --git a/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_uninstall.ps1 b/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_uninstall.ps1 new file mode 100644 index 0000000000..0b7b922dce --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/lenovo_system_update_uninstall.ps1 @@ -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 diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index ddc87a1c6a..bae1958151 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -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", diff --git a/ee/maintained-apps/outputs/lenovo-system-update/windows.json b/ee/maintained-apps/outputs/lenovo-system-update/windows.json new file mode 100644 index 0000000000..b3e2efb3e8 --- /dev/null +++ b/ee/maintained-apps/outputs/lenovo-system-update/windows.json @@ -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" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/LenovoSystemUpdate.tsx b/frontend/pages/SoftwarePage/components/icons/LenovoSystemUpdate.tsx new file mode 100644 index 0000000000..ff9a916244 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/LenovoSystemUpdate.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const LenovoSystemUpdate = (props: SVGProps) => ( + + + +); +export default LenovoSystemUpdate; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index 442e31e641..bc4b9b593d 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -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, diff --git a/website/assets/images/app-icon-lenovo-system-update-60x60@2x.png b/website/assets/images/app-icon-lenovo-system-update-60x60@2x.png new file mode 100644 index 0000000000..0b78e46153 Binary files /dev/null and b/website/assets/images/app-icon-lenovo-system-update-60x60@2x.png differ