From 8440692e7a1cb950ec6fdeb2c8a112608a883080 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Sat, 21 Mar 2026 04:42:57 -0500 Subject: [PATCH] fix(upgrade): verify installed version after successful upgrade When upgrading, compare the version reported by Windows (ARP correlation or MSIX package family registration) to the manifest package version after the installer exits successfully. If the installed version is still lower, emit APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH. Skips verification when reboot is required to finish (version may not update until restart) or when either version is unknown. Does not cover installers that do not participate in ARP/MSIX correlation (e.g. some portable flows); those remain unchanged. Related: microsoft/winget-cli#4550 --- .../package-manager/winget/returnCodes.md | 2 + src/AppInstallerCLICore/Resources.h | 2 + .../Workflows/InstallFlow.cpp | 42 +++++++++++++++++++ .../Shared/Strings/en-us/winget.resw | 14 +++++++ src/AppInstallerCommonCore/Deployment.cpp | 24 +++++++++++ .../Public/AppInstallerDeployment.h | 6 +++ src/AppInstallerSharedLib/Errors.cpp | 2 + .../Public/AppInstallerErrors.h | 2 + .../Converters.h | 4 ++ 9 files changed, 98 insertions(+) diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index 1ec122dcef..8d7de20b4c 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -153,6 +153,8 @@ ms.localizationpriority: medium | 0x8A15008B | -1979335093 | APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED | Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. | | 0x8A15008C | -1979335092 | APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED | Font validation failed. | | 0x8A15008D | -1979335091 | APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED | Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. | +| 0x8A15008E | -1979335090 | APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH | An upgrade is available but uses a different install technology than the current installation | +| 0x8A15008F | -1979335089 | APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH | Installer reported success but the installed version is still older than the upgrade target | ## Install errors. diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index ef3db8b395..c62797e86f 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -796,6 +796,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstalledVersionMismatch); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index f702629c20..38e68f9b67 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include #include "InstallFlow.h" #include "DownloadFlow.h" #include "FontFlow.h" @@ -20,6 +21,7 @@ #include "SourceFlow.h" #include #include +#include #include #include #include @@ -59,6 +61,36 @@ namespace AppInstaller::CLI::Workflow } } + // After a successful installer exit code, confirms ARP/MSIX reports at least the manifest version (upgrade only). + void VerifyUpgradeInstalledVersion( + Execution::Context& context, + std::string_view installedVersionString, + const Manifest::Manifest& manifest) + { + if (!WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate)) + { + return; + } + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::RebootRequired)) + { + return; + } + + Version expectedVersion{ manifest.Version }; + Version installedVersion{ std::string{ installedVersionString } }; + if (expectedVersion.IsUnknown() || installedVersion.IsUnknown()) + { + return; + } + if (installedVersion < expectedVersion) + { + context.Reporter.Error() << Resource::String::UpgradeInstalledVersionMismatch( + Utility::LocIndString{ std::string{ manifest.Version } }, + Utility::LocIndString{ std::string{ installedVersionString } }) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH); + } + } + bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall) { switch (type) @@ -886,6 +918,11 @@ namespace AppInstaller::CLI::Workflow if (installer && !installer->PackageFamilyName.empty() && Deployment::IsRegistered(installer->PackageFamilyName)) { + const auto& manifest = context.Get(); + if (auto installedVersion = Deployment::GetInstalledVersionStringForFamilyName(installer->PackageFamilyName)) + { + VerifyUpgradeInstalledVersion(context, *installedVersion, manifest); + } return; } @@ -898,6 +935,11 @@ namespace AppInstaller::CLI::Workflow // Store the ARP entry found to match the package to record it in the tracking catalog later if (correlationResult.Package) { + VerifyUpgradeInstalledVersion( + context, + correlationResult.Package->GetProperty(PackageVersionProperty::Version).get(), + manifest); + std::vector entries; auto metadata = correlationResult.Package->GetMetadata(); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 6cfee598b7..d9a3c264fa 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1345,6 +1345,14 @@ Please specify one of them using the --source option to proceed. A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. + + {0} package(s) have upgrades blocked because newer versions use a different install technology than the current installation. Uninstall each package, then install the newer version. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + + + The installer reported success, but the installed version is still {1}. The upgrade target was {0}. + {Locked="{0}","{1}"} {0} is the expected package version from the manifest; {1} is the version reported by Windows after install. + The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. @@ -2265,6 +2273,12 @@ Please specify one of them using the --source option to proceed. No applicable update found + + An upgrade is available but uses a different install technology than the current installation + + + Installer reported success but the installed version is still older than the upgrade target + winget upgrade --all completed with failures diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp index 3824d21644..f5261d2945 100644 --- a/src/AppInstallerCommonCore/Deployment.cpp +++ b/src/AppInstallerCommonCore/Deployment.cpp @@ -385,6 +385,30 @@ namespace AppInstaller::Deployment return packages.begin() != packages.end(); } + std::optional GetInstalledVersionStringForFamilyName(std::string_view packageFamilyName) + { + try + { + std::wstring wideFamilyName = Utility::ConvertToUTF16(packageFamilyName); + + PackageManager packageManager; + auto packages = packageManager.FindPackagesForUser({}, wideFamilyName); + auto it = packages.begin(); + if (it == packages.end()) + { + return std::nullopt; + } + + const auto& v = it->Id().Version(); + std::ostringstream stream; + stream << v.Major << '.' << v.Minor << '.' << v.Build << '.' << v.Revision; + return stream.str(); + } + CATCH_LOG(); + + return std::nullopt; + } + void RegisterPackage( std::string_view packageFamilyName, IProgressCallback& callback) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h index 68f16b80e8..422efbe215 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -2,6 +2,9 @@ // Licensed under the MIT License. #pragma once #include +#include +#include +#include #include #include @@ -61,6 +64,9 @@ namespace AppInstaller::Deployment // Calls winrt::Windows::Management::Deployment::PackageManager::FindPackagesForUser bool IsRegistered(std::string_view packageFamilyName); + // Returns the version string (major.minor.build.revision) for the first package found for the family name, if any. + std::optional GetInstalledVersionStringForFamilyName(std::string_view packageFamilyName); + // Calls winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFamilyNameAsync void RegisterPackage( std::string_view packageFamilyName, diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index e3fdd13f1d..c6e27636cb 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -131,6 +131,8 @@ namespace AppInstaller WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE, "Manifest validation failed"), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, "Manifest is invalid"), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE, "No applicable update found"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH, "An upgrade is available but uses a different install technology than the current installation"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH, "Installer reported success but the installed version is still older than the upgrade target"), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE, "winget upgrade --all completed with failures"), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED, "Installer failed security check"), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, "Download size does not match expected content length"), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 2bb4ad097e..2ee49ea6a5 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -162,6 +162,8 @@ #define APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED ((HRESULT)0x8A15008B) #define APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED ((HRESULT)0x8A15008C) #define APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED ((HRESULT)0x8A15008D) +#define APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH ((HRESULT)0x8A15008E) +#define APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH ((HRESULT)0x8A15008F) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 460fe4e21c..4c3f724e8c 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -82,6 +82,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); break; case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH: case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); @@ -97,6 +98,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); break; + case APPINSTALLER_CLI_ERROR_UPGRADE_INSTALLED_VERSION_MISMATCH: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, InternalError, InternalError, InternalError); + break; case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); break;