Add VC++ Redistributable (x64) as a Windows FMA (#46935)

Add support for Microsoft Visual C++ Redistributable (x64) via winget:
new installer and uninstaller PowerShell scripts that handle the WiX
"burn" bootstrapper, a winget input manifest, an app entry in
outputs/apps.json, and a detailed outputs/vc-redist-x64/windows.json
with version metadata (14.51.36231.0), installer URL, SHA256 and
embedded script refs. The uninstall script targets the bundle
ProductCode (falls back to the Package Cache) and normalizes /uninstall,
/quiet and /norestart handling; the install script runs the bootstrapper
with /quiet /norestart and treats reboot codes (3010, 1641) as success.
This commit is contained in:
Allen Houchins
2026-06-05 14:03:47 -05:00
committed by GitHub
parent 794ef11fc3
commit 3c6b05f16d
11 changed files with 211 additions and 0 deletions
@@ -0,0 +1,38 @@
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
# The Visual C++ Redistributable ships as a WiX "burn" bootstrapper (.exe). It
# installs machine-wide and registers its own ARP entry. Silent switches come
# from the winget installer manifest (burn bundles use /quiet /norestart).
$exeFilePath = "${env:INSTALLER_PATH}"
try {
if (-not (Test-Path $exeFilePath)) {
Write-Host "Error: Installer file not found at: $exeFilePath"
Exit 1
}
$processOptions = @{
FilePath = "$exeFilePath"
ArgumentList = "/quiet /norestart"
PassThru = $true
Wait = $true
NoNewWindow = $true
}
$process = Start-Process @processOptions
$exitCode = $process.ExitCode
Write-Host "Install exit code: $exitCode"
# 0 = success, 3010 = success but reboot required, 1641 = reboot initiated
if ($exitCode -eq 3010 -or $exitCode -eq 1641) {
Exit 0
}
Exit $exitCode
} catch {
Write-Host "Error: $_"
Exit 1
}
@@ -0,0 +1,73 @@
# Uninstalls the Microsoft Visual C++ Redistributable WiX "burn" bundle.
#
# The redistributable installs as a burn bootstrapper that registers a *bundle*
# ARP entry (keyed by the bundle ProductCode) alongside several MSI component
# entries ("...X64 Minimum Runtime...", "...Additional Runtime..."). Only the
# bundle entry removes the whole redistributable, and it uninstalls by running
# its cached bootstrapper .exe with /uninstall -- never via msiexec. We target
# the bundle by its ProductCode (injected by the ingester) and fall back to the
# cached bootstrapper in the Package Cache.
$productCode = ${PACKAGE_ID}
function Invoke-Uninstaller {
param([string]$exe, [string]$exeArgs)
if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
$exeArgs = $exeArgs.Trim()
Write-Host "Uninstall command: $exe"
Write-Host "Uninstall args: $exeArgs"
$process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
return $process.ExitCode
}
$exitCode = $null
# 1) Preferred: the bundle ARP entry, looked up by the bundle ProductCode. Its
# UninstallString/QuietUninstallString points to the cached bootstrapper .exe.
$keys = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$productCode",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$productCode"
)
foreach ($key in $keys) {
if (-not (Test-Path $key)) { continue }
$entry = Get-ItemProperty $key -ErrorAction SilentlyContinue
if (-not $entry) { continue }
$raw = $entry.QuietUninstallString
if (-not $raw) { $raw = $entry.UninstallString }
if (-not $raw) { continue }
# Parse into executable + args, handling quoted/unquoted/bare shapes.
if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
$exe = $matches[1]; $exeArgs = $matches[2].Trim()
} elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
$exe = $matches[1]; $exeArgs = $matches[2].Trim()
} else {
$exe = $raw; $exeArgs = ""
}
$exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs
break
}
# 2) Fallback: run the cached bootstrapper directly from the Package Cache, which
# burn names after the bundle ProductCode.
if ($null -eq $exitCode) {
$cached = Get-ChildItem -Path "C:\ProgramData\Package Cache\$productCode" -Filter *.exe -ErrorAction SilentlyContinue | Select-Object -First 1
if ($cached) {
$exitCode = Invoke-Uninstaller -exe $cached.FullName -exeArgs ""
}
}
if ($null -eq $exitCode) {
Write-Host "Uninstall entry not found for product code: $productCode"
Exit 0
}
Write-Host "Uninstall exit code: $exitCode"
# 0 = success, 3010 = success but reboot required, 1641 = reboot initiated
if ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }
Exit $exitCode
@@ -0,0 +1,13 @@
{
"name": "Microsoft Visual C++ 2013 Redistributable (x64)",
"slug": "vc-redist-2013-x64/windows",
"package_identifier": "Microsoft.VCRedist.2013.x64",
"unique_identifier": "Microsoft Visual C++ 2013 Redistributable (x64)",
"exists_query": "SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2013 Redistributable (x64)%' AND publisher = 'Microsoft Corporation';",
"installer_arch": "x64",
"installer_type": "exe",
"installer_scope": "machine",
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/vc_redist_x64_install.ps1",
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/vc_redist_x64_uninstall.ps1",
"default_categories": ["Developer tools"]
}
@@ -0,0 +1,13 @@
{
"name": "Microsoft Visual C++ 2015-2022 Redistributable (x64)",
"slug": "vc-redist-x64/windows",
"package_identifier": "Microsoft.VCRedist.2015+.x64",
"unique_identifier": "Microsoft Visual C++ 2015-2022 Redistributable (x64)",
"exists_query": "SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2015-2022 Redistributable (x64)%' AND publisher = 'Microsoft Corporation';",
"installer_arch": "x64",
"installer_type": "exe",
"installer_scope": "machine",
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/vc_redist_x64_install.ps1",
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/vc_redist_x64_uninstall.ps1",
"default_categories": ["Developer tools"]
}
+14
View File
@@ -2577,6 +2577,20 @@
"unique_identifier": "com.utmapp.UTM",
"description": "UTM is a virtual machines UI using QEMU."
},
{
"name": "Microsoft Visual C++ 2013 Redistributable (x64)",
"slug": "vc-redist-2013-x64/windows",
"platform": "windows",
"unique_identifier": "Microsoft Visual C++ 2013 Redistributable (x64)",
"description": "Microsoft Visual C++ 2013 Redistributable (x64) installs the runtime components of the Visual C++ libraries required to run 64-bit applications built with Visual C++ 2013."
},
{
"name": "Microsoft Visual C++ 2015-2022 Redistributable (x64)",
"slug": "vc-redist-x64/windows",
"platform": "windows",
"unique_identifier": "Microsoft Visual C++ 2015-2022 Redistributable (x64)",
"description": "Microsoft Visual C++ 2015-2022 Redistributable (x64) installs the runtime components of the Visual C++ libraries required to run 64-bit applications built with Visual C++ 2015, 2017, 2019, and 2022."
},
{
"name": "VirtualBox",
"slug": "virtualbox/darwin",
@@ -0,0 +1,22 @@
{
"versions": [
{
"version": "12.0.40664.0",
"queries": {
"exists": "SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2013 Redistributable (x64)%' AND publisher = 'Microsoft Corporation';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2013 Redistributable (x64)%' AND publisher = 'Microsoft Corporation' AND version_compare(version, '12.0.40664.0') < 0);"
},
"installer_url": "https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe",
"install_script_ref": "65654cc8",
"uninstall_script_ref": "72bee0a9",
"sha256": "a4bba7701e355ae29c403431f871a537897c363e215cafe706615e270984f17c",
"default_categories": [
"Developer tools"
]
}
],
"refs": {
"65654cc8": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n#\n# The Visual C++ Redistributable ships as a WiX \"burn\" bootstrapper (.exe). It\n# installs machine-wide and registers its own ARP entry. Silent switches come\n# from the winget installer manifest (burn bundles use /quiet /norestart).\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\ntry {\n if (-not (Test-Path $exeFilePath)) {\n Write-Host \"Error: Installer file not found at: $exeFilePath\"\n Exit 1\n }\n\n $processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/quiet /norestart\"\n PassThru = $true\n Wait = $true\n NoNewWindow = $true\n }\n\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode\n Write-Host \"Install exit code: $exitCode\"\n\n # 0 = success, 3010 = success but reboot required, 1641 = reboot initiated\n if ($exitCode -eq 3010 -or $exitCode -eq 1641) {\n Exit 0\n }\n\n Exit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
"72bee0a9": "# Uninstalls the Microsoft Visual C++ Redistributable WiX \"burn\" bundle.\n#\n# The redistributable installs as a burn bootstrapper that registers a *bundle*\n# ARP entry (keyed by the bundle ProductCode) alongside several MSI component\n# entries (\"...X64 Minimum Runtime...\", \"...Additional Runtime...\"). Only the\n# bundle entry removes the whole redistributable, and it uninstalls by running\n# its cached bootstrapper .exe with /uninstall -- never via msiexec. We target\n# the bundle by its ProductCode (injected by the ingester) and fall back to the\n# cached bootstrapper in the Package Cache.\n\n$productCode = '{042d26ef-3dbe-4c25-95d3-4c1b11b235a7}'\n\nfunction Invoke-Uninstaller {\n param([string]$exe, [string]$exeArgs)\n if ($exeArgs -notmatch '/uninstall') { $exeArgs = \"/uninstall $exeArgs\" }\n if ($exeArgs -notmatch '/quiet') { $exeArgs = \"$exeArgs /quiet\" }\n if ($exeArgs -notmatch '/norestart') { $exeArgs = \"$exeArgs /norestart\" }\n $exeArgs = $exeArgs.Trim()\n Write-Host \"Uninstall command: $exe\"\n Write-Host \"Uninstall args: $exeArgs\"\n $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait\n return $process.ExitCode\n}\n\n$exitCode = $null\n\n# 1) Preferred: the bundle ARP entry, looked up by the bundle ProductCode. Its\n# UninstallString/QuietUninstallString points to the cached bootstrapper .exe.\n$keys = @(\n \"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$productCode\",\n \"HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$productCode\"\n)\n\nforeach ($key in $keys) {\n if (-not (Test-Path $key)) { continue }\n $entry = Get-ItemProperty $key -ErrorAction SilentlyContinue\n if (-not $entry) { continue }\n\n $raw = $entry.QuietUninstallString\n if (-not $raw) { $raw = $entry.UninstallString }\n if (-not $raw) { continue }\n\n # Parse into executable + args, handling quoted/unquoted/bare shapes.\n if ($raw -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exe = $matches[1]; $exeArgs = $matches[2].Trim()\n } elseif ($raw -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exe = $matches[1]; $exeArgs = $matches[2].Trim()\n } else {\n $exe = $raw; $exeArgs = \"\"\n }\n\n $exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs\n break\n}\n\n# 2) Fallback: run the cached bootstrapper directly from the Package Cache, which\n# burn names after the bundle ProductCode.\nif ($null -eq $exitCode) {\n $cached = Get-ChildItem -Path \"C:\\ProgramData\\Package Cache\\$productCode\" -Filter *.exe -ErrorAction SilentlyContinue | Select-Object -First 1\n if ($cached) {\n $exitCode = Invoke-Uninstaller -exe $cached.FullName -exeArgs \"\"\n }\n}\n\nif ($null -eq $exitCode) {\n Write-Host \"Uninstall entry not found for product code: $productCode\"\n Exit 0\n}\n\nWrite-Host \"Uninstall exit code: $exitCode\"\n# 0 = success, 3010 = success but reboot required, 1641 = reboot initiated\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\nExit $exitCode\n"
}
}
@@ -0,0 +1,22 @@
{
"versions": [
{
"version": "14.51.36231.0",
"queries": {
"exists": "SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2015-2022 Redistributable (x64)%' AND publisher = 'Microsoft Corporation';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Microsoft Visual C++ 2015-2022 Redistributable (x64)%' AND publisher = 'Microsoft Corporation' AND version_compare(version, '14.51.36231.0') < 0);"
},
"installer_url": "https://download.visualstudio.microsoft.com/download/pr/c1bd4f2c-3672-468e-8480-7ed419dbb641/90E48ADE404E4576D023ABFA374F323555F233982A8805EA9AC63DCA9491A16B/VC_redist.x64.exe",
"install_script_ref": "65654cc8",
"uninstall_script_ref": "5077ddcb",
"sha256": "90e48ade404e4576d023abfa374f323555f233982a8805ea9ac63dca9491a16b",
"default_categories": [
"Developer tools"
]
}
],
"refs": {
"5077ddcb": "# Uninstalls the Microsoft Visual C++ Redistributable WiX \"burn\" bundle.\n#\n# The redistributable installs as a burn bootstrapper that registers a *bundle*\n# ARP entry (keyed by the bundle ProductCode) alongside several MSI component\n# entries (\"...X64 Minimum Runtime...\", \"...Additional Runtime...\"). Only the\n# bundle entry removes the whole redistributable, and it uninstalls by running\n# its cached bootstrapper .exe with /uninstall -- never via msiexec. We target\n# the bundle by its ProductCode (injected by the ingester) and fall back to the\n# cached bootstrapper in the Package Cache.\n\n$productCode = '{c157798c-9519-4e56-98d9-f696f78f4a61}'\n\nfunction Invoke-Uninstaller {\n param([string]$exe, [string]$exeArgs)\n if ($exeArgs -notmatch '/uninstall') { $exeArgs = \"/uninstall $exeArgs\" }\n if ($exeArgs -notmatch '/quiet') { $exeArgs = \"$exeArgs /quiet\" }\n if ($exeArgs -notmatch '/norestart') { $exeArgs = \"$exeArgs /norestart\" }\n $exeArgs = $exeArgs.Trim()\n Write-Host \"Uninstall command: $exe\"\n Write-Host \"Uninstall args: $exeArgs\"\n $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait\n return $process.ExitCode\n}\n\n$exitCode = $null\n\n# 1) Preferred: the bundle ARP entry, looked up by the bundle ProductCode. Its\n# UninstallString/QuietUninstallString points to the cached bootstrapper .exe.\n$keys = @(\n \"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$productCode\",\n \"HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$productCode\"\n)\n\nforeach ($key in $keys) {\n if (-not (Test-Path $key)) { continue }\n $entry = Get-ItemProperty $key -ErrorAction SilentlyContinue\n if (-not $entry) { continue }\n\n $raw = $entry.QuietUninstallString\n if (-not $raw) { $raw = $entry.UninstallString }\n if (-not $raw) { continue }\n\n # Parse into executable + args, handling quoted/unquoted/bare shapes.\n if ($raw -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exe = $matches[1]; $exeArgs = $matches[2].Trim()\n } elseif ($raw -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exe = $matches[1]; $exeArgs = $matches[2].Trim()\n } else {\n $exe = $raw; $exeArgs = \"\"\n }\n\n $exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs\n break\n}\n\n# 2) Fallback: run the cached bootstrapper directly from the Package Cache, which\n# burn names after the bundle ProductCode.\nif ($null -eq $exitCode) {\n $cached = Get-ChildItem -Path \"C:\\ProgramData\\Package Cache\\$productCode\" -Filter *.exe -ErrorAction SilentlyContinue | Select-Object -First 1\n if ($cached) {\n $exitCode = Invoke-Uninstaller -exe $cached.FullName -exeArgs \"\"\n }\n}\n\nif ($null -eq $exitCode) {\n Write-Host \"Uninstall entry not found for product code: $productCode\"\n Exit 0\n}\n\nWrite-Host \"Uninstall exit code: $exitCode\"\n# 0 = success, 3010 = success but reboot required, 1641 = reboot initiated\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\nExit $exitCode\n",
"65654cc8": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n#\n# The Visual C++ Redistributable ships as a WiX \"burn\" bootstrapper (.exe). It\n# installs machine-wide and registers its own ARP entry. Silent switches come\n# from the winget installer manifest (burn bundles use /quiet /norestart).\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\ntry {\n if (-not (Test-Path $exeFilePath)) {\n Write-Host \"Error: Installer file not found at: $exeFilePath\"\n Exit 1\n }\n\n $processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/quiet /norestart\"\n PassThru = $true\n Wait = $true\n NoNewWindow = $true\n }\n\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode\n Write-Host \"Install exit code: $exitCode\"\n\n # 0 = success, 3010 = success but reboot required, 1641 = reboot initiated\n if ($exitCode -eq 3010 -or $exitCode -eq 1641) {\n Exit 0\n }\n\n Exit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n"
}
}
File diff suppressed because one or more lines are too long
@@ -268,6 +268,7 @@ import Transmit from "./Transmit";
import Tunnelblick from "./Tunnelblick";
import Twingate from "./Twingate";
import Utm from "./Utm";
import VcRedistX64 from "./VcRedistX64";
import VirtualBox from "./VirtualBox";
import VirtualBuddy from "./VirtualBuddy";
import Viscosity from "./Viscosity";
@@ -477,6 +478,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
"microsoft outlook": MicrosoftOutlook,
"microsoft powerpoint": MicrosoftPowerPoint,
"microsoft teams": Teams,
"microsoft visual c++": VcRedistX64,
"microsoft visual studio code": VisualStudioCode,
"microsoft word": Word,
miro: Miro,
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB