diff --git a/.github/scripts/partition-fma-apps.sh b/.github/scripts/partition-fma-apps.sh index c8f0cd9273..5696a89ff5 100755 --- a/.github/scripts/partition-fma-apps.sh +++ b/.github/scripts/partition-fma-apps.sh @@ -11,6 +11,13 @@ # ee/maintained-apps/inputs/winget/7-zip.json (.installer_arch). # Slugs whose input file or installer_arch is missing default to # the x64 runner. +# +# Exception: inputs with "requires_client_os": true always route +# to windows-11-arm regardless of installer_arch. The x64 runner +# image is Windows Server, and some installers (e.g. Dell Display +# and Peripheral Manager) refuse to install on Server SKUs; +# windows-11-arm is the only GitHub-hosted client-OS Windows +# runner, and it runs x64/x86 installers under Prism emulation. # darwin All apps run on macos-latest (arm64; x86-only casks run under # Rosetta 2, matching how customer Macs run them). No architecture # partitioning is needed. @@ -112,11 +119,20 @@ case "$PLATFORM" in name="${slug%/windows}" input_file="${WINGET_INPUTS_DIR}/${name}.json" arch="" + requires_client_os="false" if [ -f "$input_file" ]; then arch=$(jq -r '.installer_arch // empty' "$input_file" 2>/dev/null || echo "") + requires_client_os=$(jq -r '.requires_client_os // false' "$input_file" 2>/dev/null || echo "false") else echo "Warning: no winget input file for '$slug' at $input_file, assuming x64" >&2 fi + if [ "$requires_client_os" == "true" ]; then + # The app won't install on Windows Server (the x64 runner + # image), so validate it on the client-OS arm64 runner. + arm64_slugs+=("$slug") + echo " - $slug -> windows-11-arm (requires_client_os)" + continue + fi case "$arch" in arm64) arm64_slugs+=("$slug") diff --git a/.github/workflows/test-fma-windows-pr-only.yml b/.github/workflows/test-fma-windows-pr-only.yml index 7ffcc73169..b4666c7673 100644 --- a/.github/workflows/test-fma-windows-pr-only.yml +++ b/.github/workflows/test-fma-windows-pr-only.yml @@ -33,8 +33,9 @@ jobs: # cheap Linux runner. The (much more expensive) Windows runners below only # spin up when there are Windows apps to validate, and each app is routed to # a runner whose native architecture matches its installer: arm64 apps to - # windows-11-arm, x64/x86/neutral apps to the x64 runner. Large PRs are split - # into shards that validate in parallel. + # windows-11-arm, x64/x86/neutral apps to the x64 runner. Apps marked + # requires_client_os always go to windows-11-arm (the x64 runner is Windows + # Server). Large PRs are split into shards that validate in parallel. detect-changed-apps: runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/test-fma-windows.yml b/.github/workflows/test-fma-windows.yml index dcd26ddaf3..fb36f30631 100644 --- a/.github/workflows/test-fma-windows.yml +++ b/.github/workflows/test-fma-windows.yml @@ -28,8 +28,9 @@ jobs: # Partition every Windows app in apps.json by installer architecture on a # cheap Linux runner, then fan out to Windows runners whose native # architecture matches each app's installer: arm64 apps to windows-11-arm, - # x64/x86/neutral apps to the x64 runner. Each architecture bucket is split - # into shards that validate in parallel. + # x64/x86/neutral apps to the x64 runner. Apps marked requires_client_os + # always go to windows-11-arm (the x64 runner is Windows Server). Each + # architecture bucket is split into shards that validate in parallel. partition: runs-on: ubuntu-latest outputs: diff --git a/ee/maintained-apps/README.md b/ee/maintained-apps/README.md index 6b4046d1f0..d7b933910a 100644 --- a/ee/maintained-apps/README.md +++ b/ee/maintained-apps/README.md @@ -124,6 +124,7 @@ go run cmd/maintained-apps/main.go --slug="box-drive/windows" --debug | `install_script_path` | string | Filepath to a custom install script (`.ps1`). Overrides the generated install script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates install scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide PowerShell scripts that run the installer file directly. Fleet stores the installer and sends it to the host at install time; your script must execute it using the `INSTALLER_PATH` environment variable. | | `uninstall_script_path` | string | Filepath to a custom uninstall script (`.ps1`). Overrides the generated uninstall script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates uninstall scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide a script to uninstall the app. Scripts for `.exe` apps are vendor-specific. Use the vendor’s documented silent uninstall switch or the registered UninstallString (if available), ensuring the script runs silently and returns the installer’s exit code. | | `fuzzy_match_name` | boolean | If the `unique_identifier` doesn't match the `DisplayName`, use `fuzzy_match_name` to specify that Fleet uses "fuzzy matching" to match the Fleet-maintained app and the inventoried software. For example, for Pritunl, the `unique_identifier` is "Pritunl" and the inventories software's `DisplayName` is "Pritunl Client". With `fuzzy_match_name` set to true, Pritunl app will be matched to the inventories software. | +| `requires_client_os` | boolean | Set to `true` when the installer refuses to run on Windows Server SKUs (e.g., Dell Display and Peripheral Manager). Fleet's ingestion ignores this field; CI reads it to route validation to the `windows-11-arm` runner (the only GitHub-hosted client-OS Windows runner) instead of the default Windows Server x64 runner. | #### Windows troubleshooting diff --git a/ee/maintained-apps/ingesters/winget/ingester.go b/ee/maintained-apps/ingesters/winget/ingester.go index b766a5821f..9c17f410ec 100644 --- a/ee/maintained-apps/ingesters/winget/ingester.go +++ b/ee/maintained-apps/ingesters/winget/ingester.go @@ -652,6 +652,11 @@ type inputApp struct { DefaultCategories []string `json:"default_categories"` Frozen bool `json:"frozen"` PatchPolicyPath string `json:"patch_policy_path"` + // RequiresClientOS marks installers that refuse to run on Windows Server + // SKUs (e.g. Dell Display and Peripheral Manager). The ingester ignores it; + // CI (.github/scripts/partition-fma-apps.sh) reads it to route validation to + // the windows-11-arm runner, the only GitHub-hosted client-OS Windows runner. + RequiresClientOS bool `json:"requires_client_os"` } type installerManifest struct { diff --git a/ee/maintained-apps/inputs/winget/dell-display-and-peripheral-manager.json b/ee/maintained-apps/inputs/winget/dell-display-and-peripheral-manager.json new file mode 100644 index 0000000000..5c944a334e --- /dev/null +++ b/ee/maintained-apps/inputs/winget/dell-display-and-peripheral-manager.json @@ -0,0 +1,14 @@ +{ + "name": "Dell Display and Peripheral Manager", + "slug": "dell-display-and-peripheral-manager/windows", + "package_identifier": "Dell.DisplayAndPeripheralManager", + "unique_identifier": "Dell Display and Peripheral Manager", + "program_publisher": "Dell Technologies", + "installer_arch": "x64", + "installer_type": "exe", + "installer_scope": "machine", + "requires_client_os": true, + "install_script_path": "ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_install.ps1", + "uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_uninstall.ps1", + "default_categories": ["Productivity"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_install.ps1 new file mode 100644 index 0000000000..03a1f9e30f --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_install.ps1 @@ -0,0 +1,34 @@ +# Learn more about .exe install scripts: +# http://fleetdm.com/learn-more-about/exe-install-scripts + +$exeFilePath = "${env:INSTALLER_PATH}" + +try { + +# DDPM is a Windows 10/11 client app: the InstallShield setup's OS check aborts +# with exit 0x80042000 on Windows Server SKUs (per its /CreateDebugLog output), +# so it only installs on client editions. Dell's documented managed-deployment +# switches: +# /Silent - no UI +# /HeadlessMode=true - required for SYSTEM/session-0 (no desktop) installs +# /TelemetryConsent=false - decline telemetry (no consent prompt) +# /TurnOffCA - disable the app's own auto-update (Fleet manages updates) +$processOptions = @{ + FilePath = "$exeFilePath" + ArgumentList = "/Silent /HeadlessMode=true /TelemetryConsent=false /TurnOffCA" + PassThru = $true + Wait = $true +} + +# Start process and track exit code +$process = Start-Process @processOptions +$exitCode = $process.ExitCode + +# Prints the exit code +Write-Host "Install exit code: $exitCode" +Exit $exitCode + +} catch { + Write-Host "Error: $_" + Exit 1 +} diff --git a/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_uninstall.ps1 b/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_uninstall.ps1 new file mode 100644 index 0000000000..88ea7ad179 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/dell-display-and-peripheral-manager_uninstall.ps1 @@ -0,0 +1,121 @@ +# Uninstalls Dell Display and Peripheral Manager. DDPM is an InstallShield +# InstallScript (non-MSI) product: its uninstall registry key is named like an +# MSI ProductCode GUID, but no MSI product is registered, so msiexec /x fails +# with 1605. Its UninstallString ("\Installer\setup.exe +# -runfromtemp -removeonly") hangs under Dell's /Silent wrapper switch because +# in -removeonly mode the InstallScript engine drives the dialogs and only +# honors its own /s silent switch with a response file. The installer ships +# exactly this removal response file embedded in its overlay (SdWelcomeMaint +# Result=303 / MessageBox Result=6 / SdFinishReboot Result=1); we recreate it +# with the GUID from the registry key and pass it via /f1. Success is gated on +# the uninstall registry entry disappearing, not the launcher exit code, since +# -runfromtemp relaunches from %TEMP% and can return early. + +$softwareName = "Dell Display and Peripheral Manager" + +$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' +$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + +function Get-InstalledEntry { + Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue | + ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } | + Where-Object { $_.DisplayName -eq $softwareName } | + Select-Object -First 1 +} + +try { + +# DDPM can auto-launch after install; a running instance can block the +# uninstaller. +Get-Process -Name "DDPM*" -ErrorAction SilentlyContinue | + Stop-Process -Force -ErrorAction SilentlyContinue + +$key = Get-InstalledEntry +if (-not $key) { + Write-Host "Uninstall entry not found for '$softwareName'." + Exit 1 +} + +$u = $key.UninstallString +if (-not $u) { + Write-Host "No UninstallString registered for '$softwareName'." + Exit 1 +} + +# Parse defensively: quoted path, unquoted path with spaces, bare token. +if ($u -match '^\s*"([^"]+)"\s*(.*)$') { + $exe = $Matches[1]; $uninstallArgs = $Matches[2] +} elseif ($u -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { + $exe = $Matches[1]; $uninstallArgs = $Matches[2] +} else { + Write-Host "Unrecognized UninstallString format: $u" + Exit 1 +} + +if ($exe -match '(?i)msiexec') { + # Defensive: if a future release registers an MSI-style uninstall. + $uninstallArgs = "$uninstallArgs /qn /norestart".Trim() + $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs ` + -NoNewWindow -PassThru -Wait + Write-Host "msiexec exit code: $($process.ExitCode)" +} else { + # The registry key name is the InstallScript product GUID; the response + # file's section names must use it. + $guid = $key.PSChildName + $issPath = Join-Path $env:TEMP "ddpm-uninstall.iss" + $logPath = Join-Path $env:TEMP "ddpm-uninstall.log" + Remove-Item -Path $logPath -Force -ErrorAction SilentlyContinue + @" +[InstallShield Silent] +Version=v7.00 +File=Response File +[File Transfer] +OverwrittenReadOnly=NoToAll +[$guid-DlgOrder] +Dlg0=$guid-SdWelcomeMaint-0 +Count=3 +Dlg1=$guid-MessageBox-0 +Dlg2=$guid-SdFinishReboot-0 +[$guid-SdWelcomeMaint-0] +Result=303 +[$guid-MessageBox-0] +Result=6 +[$guid-SdFinishReboot-0] +Result=1 +BootOption=0 +"@ | Set-Content -Path $issPath -Encoding ASCII + + $uninstallArgs = "$uninstallArgs /s /f1`"$issPath`" /f2`"$logPath`"".Trim() + Write-Host "Uninstalling via: `"$exe`" $uninstallArgs" + $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs -PassThru + if (-not $process.WaitForExit(180000)) { + Write-Host "Uninstaller still running after 180s; killing it." + Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue + } else { + Write-Host "Launcher exit code: $($process.ExitCode)" + } + if (Test-Path $logPath) { + Write-Host "--- InstallShield uninstall log ---" + Get-Content $logPath | Write-Host + Write-Host "-----------------------------------" + } +} + +# The launcher can return before the %TEMP% copy finishes removal; poll the +# registry entry to decide success. +$deadline = (Get-Date).AddSeconds(120) +while ((Get-Date) -lt $deadline) { + if (-not (Get-InstalledEntry)) { + Write-Host "'$softwareName' uninstalled." + Exit 0 + } + Start-Sleep -Seconds 5 +} + +Write-Host "'$softwareName' is still registered after uninstall." +Exit 1 + +} catch { + Write-Host "Error: $_" + Exit 1 +} diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index b61026ff88..610fa62cd9 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -2423,6 +2423,13 @@ "unique_identifier": "Dell Command", "description": "Dell Command Update is a standalone application that delivers BIOS, firmware, driver, and application updates for Dell client hardware." }, + { + "name": "Dell Display and Peripheral Manager", + "slug": "dell-display-and-peripheral-manager/windows", + "platform": "windows", + "unique_identifier": "Dell Display and Peripheral Manager", + "description": "Dell Display and Peripheral Manager sets up and configures Dell monitors and peripherals such as keyboards, mice, webcams, audio devices, and active pens." + }, { "name": "Descript", "slug": "descript/darwin", diff --git a/ee/maintained-apps/outputs/dell-display-and-peripheral-manager/windows.json b/ee/maintained-apps/outputs/dell-display-and-peripheral-manager/windows.json new file mode 100644 index 0000000000..cc303c8d99 --- /dev/null +++ b/ee/maintained-apps/outputs/dell-display-and-peripheral-manager/windows.json @@ -0,0 +1,22 @@ +{ + "versions": [ + { + "version": "2.2.2.8", + "queries": { + "exists": "SELECT 1 FROM programs WHERE name = 'Dell Display and Peripheral Manager' AND publisher = 'Dell Technologies';", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Dell Display and Peripheral Manager' AND publisher = 'Dell Technologies' AND version_compare(version, '2.2.2.8') < 0);" + }, + "installer_url": "https://dl.dell.com/FOLDER14474164M/1/DDPM-Setup_2.2.2.8.exe", + "install_script_ref": "56cc0c50", + "uninstall_script_ref": "575cb5c4", + "sha256": "d8ef308817fd729bdc9ec7ecaa0fc125fad9eed7f6ed35f4c9e282570cfca56e", + "default_categories": [ + "Productivity" + ] + } + ], + "refs": { + "56cc0c50": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\ntry {\n\n# DDPM is a Windows 10/11 client app: the InstallShield setup's OS check aborts\n# with exit 0x80042000 on Windows Server SKUs (per its /CreateDebugLog output),\n# so it only installs on client editions. Dell's documented managed-deployment\n# switches:\n# /Silent - no UI\n# /HeadlessMode=true - required for SYSTEM/session-0 (no desktop) installs\n# /TelemetryConsent=false - decline telemetry (no consent prompt)\n# /TurnOffCA - disable the app's own auto-update (Fleet manages updates)\n$processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/Silent /HeadlessMode=true /TelemetryConsent=false /TurnOffCA\"\n PassThru = $true\n Wait = $true\n}\n\n# Start process and track exit code\n$process = Start-Process @processOptions\n$exitCode = $process.ExitCode\n\n# Prints the exit code\nWrite-Host \"Install exit code: $exitCode\"\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n", + "575cb5c4": "# Uninstalls Dell Display and Peripheral Manager. DDPM is an InstallShield\n# InstallScript (non-MSI) product: its uninstall registry key is named like an\n# MSI ProductCode GUID, but no MSI product is registered, so msiexec /x fails\n# with 1605. Its UninstallString (\"\\Installer\\setup.exe\n# -runfromtemp -removeonly\") hangs under Dell's /Silent wrapper switch because\n# in -removeonly mode the InstallScript engine drives the dialogs and only\n# honors its own /s silent switch with a response file. The installer ships\n# exactly this removal response file embedded in its overlay (SdWelcomeMaint\n# Result=303 / MessageBox Result=6 / SdFinishReboot Result=1); we recreate it\n# with the GUID from the registry key and pass it via /f1. Success is gated on\n# the uninstall registry entry disappearing, not the launcher exit code, since\n# -runfromtemp relaunches from %TEMP% and can return early.\n\n$softwareName = \"Dell Display and Peripheral Manager\"\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Get-InstalledEntry {\n Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |\n Where-Object { $_.DisplayName -eq $softwareName } |\n Select-Object -First 1\n}\n\ntry {\n\n# DDPM can auto-launch after install; a running instance can block the\n# uninstaller.\nGet-Process -Name \"DDPM*\" -ErrorAction SilentlyContinue |\n Stop-Process -Force -ErrorAction SilentlyContinue\n\n$key = Get-InstalledEntry\nif (-not $key) {\n Write-Host \"Uninstall entry not found for '$softwareName'.\"\n Exit 1\n}\n\n$u = $key.UninstallString\nif (-not $u) {\n Write-Host \"No UninstallString registered for '$softwareName'.\"\n Exit 1\n}\n\n# Parse defensively: quoted path, unquoted path with spaces, bare token.\nif ($u -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exe = $Matches[1]; $uninstallArgs = $Matches[2]\n} elseif ($u -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exe = $Matches[1]; $uninstallArgs = $Matches[2]\n} else {\n Write-Host \"Unrecognized UninstallString format: $u\"\n Exit 1\n}\n\nif ($exe -match '(?i)msiexec') {\n # Defensive: if a future release registers an MSI-style uninstall.\n $uninstallArgs = \"$uninstallArgs /qn /norestart\".Trim()\n $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs `\n -NoNewWindow -PassThru -Wait\n Write-Host \"msiexec exit code: $($process.ExitCode)\"\n} else {\n # The registry key name is the InstallScript product GUID; the response\n # file's section names must use it.\n $guid = $key.PSChildName\n $issPath = Join-Path $env:TEMP \"ddpm-uninstall.iss\"\n $logPath = Join-Path $env:TEMP \"ddpm-uninstall.log\"\n Remove-Item -Path $logPath -Force -ErrorAction SilentlyContinue\n @\"\n[InstallShield Silent]\nVersion=v7.00\nFile=Response File\n[File Transfer]\nOverwrittenReadOnly=NoToAll\n[$guid-DlgOrder]\nDlg0=$guid-SdWelcomeMaint-0\nCount=3\nDlg1=$guid-MessageBox-0\nDlg2=$guid-SdFinishReboot-0\n[$guid-SdWelcomeMaint-0]\nResult=303\n[$guid-MessageBox-0]\nResult=6\n[$guid-SdFinishReboot-0]\nResult=1\nBootOption=0\n\"@ | Set-Content -Path $issPath -Encoding ASCII\n\n $uninstallArgs = \"$uninstallArgs /s /f1`\"$issPath`\" /f2`\"$logPath`\"\".Trim()\n Write-Host \"Uninstalling via: `\"$exe`\" $uninstallArgs\"\n $process = Start-Process -FilePath $exe -ArgumentList $uninstallArgs -PassThru\n if (-not $process.WaitForExit(180000)) {\n Write-Host \"Uninstaller still running after 180s; killing it.\"\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n } else {\n Write-Host \"Launcher exit code: $($process.ExitCode)\"\n }\n if (Test-Path $logPath) {\n Write-Host \"--- InstallShield uninstall log ---\"\n Get-Content $logPath | Write-Host\n Write-Host \"-----------------------------------\"\n }\n}\n\n# The launcher can return before the %TEMP% copy finishes removal; poll the\n# registry entry to decide success.\n$deadline = (Get-Date).AddSeconds(120)\nwhile ((Get-Date) -lt $deadline) {\n if (-not (Get-InstalledEntry)) {\n Write-Host \"'$softwareName' uninstalled.\"\n Exit 0\n }\n Start-Sleep -Seconds 5\n}\n\nWrite-Host \"'$softwareName' is still registered after uninstall.\"\nExit 1\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/DellDisplayAndPeripheralManager.tsx b/frontend/pages/SoftwarePage/components/icons/DellDisplayAndPeripheralManager.tsx new file mode 100644 index 0000000000..a22f6ca0bd --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/DellDisplayAndPeripheralManager.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const DellDisplayAndPeripheralManager = (props: SVGProps) => ( + + + +); +export default DellDisplayAndPeripheralManager; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index df398f60b4..d464131566 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -275,6 +275,7 @@ import Deezer from "./Deezer"; import DefaultFolderX from "./DefaultFolderX"; import DelineaConnectionManager from "./DelineaConnectionManager"; import DellCommandUpdate from "./DellCommandUpdate"; +import DellDisplayAndPeripheralManager from "./DellDisplayAndPeripheralManager"; import Descript from "./Descript"; import Deskpad from "./Deskpad"; import Desktime from "./Desktime"; @@ -1434,6 +1435,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = { "default folder x": DefaultFolderX, "delinea connection manager": DelineaConnectionManager, "dell command update": DellCommandUpdate, + "dell display and peripheral manager": DellDisplayAndPeripheralManager, descript: Descript, deskpad: Deskpad, desktime: Desktime, diff --git a/website/assets/images/app-icon-dell-display-and-peripheral-manager-60x60@2x.png b/website/assets/images/app-icon-dell-display-and-peripheral-manager-60x60@2x.png new file mode 100644 index 0000000000..e0bdb96ce1 Binary files /dev/null and b/website/assets/images/app-icon-dell-display-and-peripheral-manager-60x60@2x.png differ