From 86f4619faf66b8f0ca9c76d977bb7cef785090cb Mon Sep 17 00:00:00 2001 From: kitzy Date: Mon, 3 Aug 2026 01:30:07 -0400 Subject: [PATCH] Add Microsoft ODBC Driver 17 for SQL Server as a Windows FMA (#50342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Related issue:** Resolves #50326 Adds Microsoft ODBC Driver 17 for SQL Server as a Windows Fleet-maintained app, from winget `Microsoft.msodbcsql.17` (17.11.1.1, MSI, machine scope, x64, en-US). Found in a customer's ManageEngine ServiceDesk Plus Windows deployment catalog with no Fleet equivalent. ## The install would fail without a custom script The MSI refuses to install unless `IACCEPTMSODBCSQLLICENSETERMS=YES` is passed. This is not just a winget convention — the MSI carries the condition and the matching error string: ``` IACCEPTMSODBCSQLLICENSETERMS ~= IACCEPTMSODBCSQLLICENSETERMS=YES command-line parameter is missing. ``` Fleet's default MSI install script passes only `/quiet /norestart /i`, so without the small custom script in this PR every install would fail. Uninstall is still the auto-generated upgrade-code script, which is correct as-is. ## Verification Identity read from the MSI Property table: ``` ProductName Microsoft ODBC Driver 17 for SQL Server Manufacturer Microsoft Corporation ProductCode {ACBA503E-64A9-4A8A-A6CC-63656F395618} UpgradeCode {0123A210-9B73-46E7-B5CE-7F33630300E7} ALLUSERS 1 ``` - Installer SHA confirmed against a local download (`0f642870…bd4e`). - `ALLUSERS=1` means it always installs per-machine. - The generated uninstall script correctly picked up the **x64** UpgradeCode — note the x86 build uses a different one, so an arch mix-up here would silently fail to uninstall. **No version pinning is needed in the exists query.** When I scoped this I expected to need `AND version LIKE '17.%'` the way the Amazon Corretto FMAs do, but the `ProductName` already carries the major version, so `Microsoft ODBC Driver 17 for SQL Server` and `...18...` are naturally distinct entries. Simple equality is correct and unambiguous. ## Two notes for reviewers **Icon quality.** The MSI's `ARPPRODUCTICON` stream tops out at 32×32, so the 128×128 asset here is an upscale and looks soft. It is the authentic Add/Remove Programs icon, but if we would rather fall back to the generic software icon than ship a blurry one, drop the icon files and I will regenerate without them. **Icon map key needed a manual fix.** The generator derives its key from the slug and produced `"microsoft odbc driver 17"`, but lookups use the lowercased catalog name — `"microsoft odbc driver 17 for sql server"`. Corrected by hand. Same generator gap hit Paint.NET in #50340. **Dependency.** The manifest declares `Microsoft.VCRedist.2015+.x64`. The ingester ignores winget `Dependencies`, but unlike HandBrake (#50323) we do ship a matching FMA (`vc-redist-x64/windows`), and the redistributable is present on most Windows hosts already. Noting it rather than treating it as a blocker. # Checklist for submitter - [x] QA'd all new/changed functionality manually ## Summary by CodeRabbit * **New Features** * Added Microsoft ODBC Driver 17 for SQL Server to the maintained software catalog. * Added Windows installation and upgrade support for version 17.11.1.1. * Added the software’s icon and catalog display details. * Included silent installation, logging, license acceptance, and reboot handling. --- .../winget/microsoft-odbc-driver-17.json | 11 +++++++++ .../microsoft_odbc_driver_17_install.ps1 | 22 +++++++++++++++++ ee/maintained-apps/outputs/apps.json | 7 ++++++ .../microsoft-odbc-driver-17/windows.json | 23 ++++++++++++++++++ .../icons/MicrosoftOdbcDriver17.tsx | 14 +++++++++++ .../SoftwarePage/components/icons/index.ts | 2 ++ ...icon-microsoft-odbc-driver-17-60x60@2x.png | Bin 0 -> 9169 bytes 7 files changed, 79 insertions(+) create mode 100644 ee/maintained-apps/inputs/winget/microsoft-odbc-driver-17.json create mode 100644 ee/maintained-apps/inputs/winget/scripts/microsoft_odbc_driver_17_install.ps1 create mode 100644 ee/maintained-apps/outputs/microsoft-odbc-driver-17/windows.json create mode 100644 frontend/pages/SoftwarePage/components/icons/MicrosoftOdbcDriver17.tsx create mode 100644 website/assets/images/app-icon-microsoft-odbc-driver-17-60x60@2x.png diff --git a/ee/maintained-apps/inputs/winget/microsoft-odbc-driver-17.json b/ee/maintained-apps/inputs/winget/microsoft-odbc-driver-17.json new file mode 100644 index 0000000000..e40ddec1ce --- /dev/null +++ b/ee/maintained-apps/inputs/winget/microsoft-odbc-driver-17.json @@ -0,0 +1,11 @@ +{ + "name": "Microsoft ODBC Driver 17 for SQL Server", + "slug": "microsoft-odbc-driver-17/windows", + "package_identifier": "Microsoft.msodbcsql.17", + "unique_identifier": "Microsoft ODBC Driver 17 for SQL Server", + "install_script_path": "ee/maintained-apps/inputs/winget/scripts/microsoft_odbc_driver_17_install.ps1", + "installer_arch": "x64", + "installer_type": "msi", + "installer_scope": "machine", + "default_categories": ["Developer tools"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/microsoft_odbc_driver_17_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/microsoft_odbc_driver_17_install.ps1 new file mode 100644 index 0000000000..8bac7607b1 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/microsoft_odbc_driver_17_install.ps1 @@ -0,0 +1,22 @@ +# The MSI refuses to install without IACCEPTMSODBCSQLLICENSETERMS=YES, which the +# default MSI install script does not pass. + +$logFile = "${env:TEMP}/fleet-install-software.log" + +try { + +$installProcess = Start-Process msiexec.exe ` + -ArgumentList "/quiet /norestart /lv ${logFile} /i `"${env:INSTALLER_PATH}`" IACCEPTMSODBCSQLLICENSETERMS=YES" ` + -PassThru -Verb RunAs -Wait + +Get-Content $logFile -Tail 500 + +# 3010 (reboot required) and 1641 (reboot initiated) are successful installs. +if ($installProcess.ExitCode -eq 3010 -or $installProcess.ExitCode -eq 1641) { Exit 0 } + +Exit $installProcess.ExitCode + +} catch { + Write-Host "Error: $_" + Exit 1 +} diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index 34e56fbd66..9ca9453068 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -5447,6 +5447,13 @@ "unique_identifier": "com.microsoft.Excel", "description": "Microsoft Excel is the industry-standard spreadsheet software, perfect for data analysis, reporting, and visualization." }, + { + "name": "Microsoft ODBC Driver 17 for SQL Server", + "slug": "microsoft-odbc-driver-17/windows", + "platform": "windows", + "unique_identifier": "Microsoft ODBC Driver 17 for SQL Server", + "description": "Microsoft ODBC Driver 17 for SQL Server lets applications connect to SQL Server and Azure SQL Database through the ODBC interface." + }, { "name": "Microsoft Office", "slug": "microsoft-office/windows", diff --git a/ee/maintained-apps/outputs/microsoft-odbc-driver-17/windows.json b/ee/maintained-apps/outputs/microsoft-odbc-driver-17/windows.json new file mode 100644 index 0000000000..a1c36a9caa --- /dev/null +++ b/ee/maintained-apps/outputs/microsoft-odbc-driver-17/windows.json @@ -0,0 +1,23 @@ +{ + "versions": [ + { + "version": "17.11.1.1", + "queries": { + "exists": "SELECT 1 FROM programs WHERE name = 'Microsoft ODBC Driver 17 for SQL Server' AND publisher = 'Microsoft Corporation';", + "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Microsoft ODBC Driver 17 for SQL Server' AND publisher = 'Microsoft Corporation' AND version_compare(version, '17.11.1.1') < 0);" + }, + "installer_url": "https://download.microsoft.com/download/d99fcd4f-548f-46e6-83d5-b9eb62b373d5/amd64/1033/msodbcsql.msi", + "install_script_ref": "5a2f9a9f", + "uninstall_script_ref": "e1c68d7e", + "sha256": "0f6428706ac1fa4863d10e515135b587de88011813131c0a43f70cd70dc0bd4e", + "default_categories": [ + "Developer tools" + ], + "upgrade_code": "{0123A210-9B73-46E7-B5CE-7F33630300E7}" + } + ], + "refs": { + "5a2f9a9f": "# The MSI refuses to install without IACCEPTMSODBCSQLLICENSETERMS=YES, which the\n# default MSI install script does not pass.\n\n$logFile = \"${env:TEMP}/fleet-install-software.log\"\n\ntry {\n\n$installProcess = Start-Process msiexec.exe `\n -ArgumentList \"/quiet /norestart /lv ${logFile} /i `\"${env:INSTALLER_PATH}`\" IACCEPTMSODBCSQLLICENSETERMS=YES\" `\n -PassThru -Verb RunAs -Wait\n\nGet-Content $logFile -Tail 500\n\n# 3010 (reboot required) and 1641 (reboot initiated) are successful installs.\nif ($installProcess.ExitCode -eq 3010 -or $installProcess.ExitCode -eq 1641) { Exit 0 }\n\nExit $installProcess.ExitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n", + "e1c68d7e": "# Fleet uninstalls app by finding all related product codes for the specified upgrade code\n$inst = New-Object -ComObject \"WindowsInstaller.Installer\"\n$timeoutSeconds = 300 # 5 minute timeout per product\n\n# MSI exit codes that indicate success. 3010 = ERROR_SUCCESS_REBOOT_REQUIRED,\n# 1641 = ERROR_SUCCESS_REBOOT_INITIATED. Treat these as success rather than failure.\n$successCodes = @(0, 3010, 1641)\n\nforeach ($product_code in $inst.RelatedProducts('{0123A210-9B73-46E7-B5CE-7F33630300E7}')) {\n $process = Start-Process msiexec -ArgumentList @(\"/quiet\", \"/x\", $product_code, \"/norestart\") -PassThru\n\n # Wait for process with timeout\n $completed = $process.WaitForExit($timeoutSeconds * 1000)\n\n if (-not $completed) {\n Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue\n Exit 1603 # ERROR_UNINSTALL_FAILURE\n }\n\n # If the uninstall failed, bail\n if ($successCodes -notcontains $process.ExitCode) {\n Write-Output \"Uninstall for $($product_code) exited $($process.ExitCode)\"\n Exit $process.ExitCode\n }\n}\n\n# All uninstalls succeeded; exit success\nExit 0\n" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/MicrosoftOdbcDriver17.tsx b/frontend/pages/SoftwarePage/components/icons/MicrosoftOdbcDriver17.tsx new file mode 100644 index 0000000000..c53fe89818 --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/MicrosoftOdbcDriver17.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const MicrosoftOdbcDriver17 = (props: SVGProps) => ( + + + +); +export default MicrosoftOdbcDriver17; diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts index 4aa9b0f9cc..a8ea6fbce8 100644 --- a/frontend/pages/SoftwarePage/components/icons/index.ts +++ b/frontend/pages/SoftwarePage/components/icons/index.ts @@ -644,6 +644,7 @@ import MicrosoftAutoUpdate from "./MicrosoftAutoUpdate"; import MicrosoftAzureStorageExplorer from "./MicrosoftAzureStorageExplorer"; import MicrosoftDotnetRuntime from "./MicrosoftDotnetRuntime"; import MicrosoftEdge from "./MicrosoftEdge"; +import MicrosoftOdbcDriver17 from "./MicrosoftOdbcDriver17"; import MicrosoftOffice from "./MicrosoftOffice"; import MicrosoftOneNote from "./MicrosoftOneNote"; import MicrosoftOutlook from "./MicrosoftOutlook"; @@ -1800,6 +1801,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = { "microsoft azure storage explorer": MicrosoftAzureStorageExplorer, "microsoft edge": Edge, "microsoft excel": Excel, + "microsoft odbc driver 17 for sql server": MicrosoftOdbcDriver17, "microsoft office": MicrosoftOffice, "microsoft onenote": MicrosoftOneNote, "microsoft outlook": MicrosoftOutlook, diff --git a/website/assets/images/app-icon-microsoft-odbc-driver-17-60x60@2x.png b/website/assets/images/app-icon-microsoft-odbc-driver-17-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..530593b73293ffb34b9636fa4d0ba3370a83e6a8 GIT binary patch literal 9169 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