From a900148c2d7761e0f1d4872ed9e8f3d7a0ec4e1a Mon Sep 17 00:00:00 2001 From: "Slai.life" Date: Fri, 30 Oct 2026 13:09:15 +0800 Subject: [PATCH] Report skipped upgrades that are not applicable --- .../1.31/6516-upgrade-all-skip-reason.md | 5 +++++ src/AppInstallerCLICore/Resources.h | 1 + .../Workflows/UpdateFlow.cpp | 8 ++++++++ .../Shared/Strings/en-us/winget.resw | 4 ++++ .../AppInstallerCLITests.vcxproj | 3 +++ .../AppInstallerCLITests.vcxproj.filters | 3 +++ .../UpdateFlowTest_Exe_MachineScope.yaml | 19 +++++++++++++++++++ src/AppInstallerCLITests/UpdateFlow.cpp | 17 +++++++++++++++++ src/AppInstallerCLITests/WorkflowCommon.cpp | 16 ++++++++++++++++ src/AppInstallerCLITests/WorkflowCommon.h | 1 + 10 files changed, 77 insertions(+) create mode 100644 doc/release-notes/1.31/6516-upgrade-all-skip-reason.md create mode 100644 src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_MachineScope.yaml diff --git a/doc/release-notes/1.31/6516-upgrade-all-skip-reason.md b/doc/release-notes/1.31/6516-upgrade-all-skip-reason.md new file mode 100644 index 0000000000..5f512d848b --- /dev/null +++ b/doc/release-notes/1.31/6516-upgrade-all-skip-reason.md @@ -0,0 +1,5 @@ +## Bug Fixes + +### Minor Bug Fixes + +* `winget upgrade --all` now reports how many available upgrades were skipped because they do not apply to the selected scope or other requirements. \ No newline at end of file diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 4b574f5155..d7e0f4fdd9 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -821,6 +821,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeNotApplicableCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index a2c243673e..46df0e6c76 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -217,6 +217,7 @@ namespace AppInstaller::CLI::Workflow int packagesWithUnknownVersionSkipped = 0; int packagesThatRequireExplicitSkipped = 0; int packagesSkippedInstallTechnologyMismatch = 0; + int packagesSkippedNotApplicable = 0; for (const auto& match : matches) { @@ -253,6 +254,7 @@ namespace AppInstaller::CLI::Workflow if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) { + ++packagesSkippedNotApplicable; continue; } @@ -314,6 +316,12 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << packagesSkippedInstallTechnologyMismatch << " package(s) skipped due to install technology mismatch"); context.Reporter.Info() << Resource::String::UpgradeInstallTechnologyMismatchCount(packagesSkippedInstallTechnologyMismatch) << std::endl; } + + if (packagesSkippedNotApplicable > 0) + { + AICLI_LOG(CLI, Info, << packagesSkippedNotApplicable << " package(s) skipped because the available upgrade was not applicable"); + context.Reporter.Info() << Resource::String::UpgradeNotApplicableCount(packagesSkippedNotApplicable) << std::endl; + } } void SelectSinglePackageVersionForInstallOrUpgrade::operator()(Execution::Context& context) const diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index af04b9f319..5f7330c69f 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1429,6 +1429,10 @@ Please specify one of them using the --source option to proceed. {0} package(s) are pinned and need to be explicitly upgraded. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. + + {0} package(s) were skipped because an available upgrade does not apply to the selected scope or other requirements. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + The arguments provided can only be used with a query. diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 07f7dca337..e98f7dec92 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -760,6 +760,9 @@ true + + true + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 0311271140..0c3e5139f1 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -822,6 +822,9 @@ TestData + + TestData + TestData diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_MachineScope.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_MachineScope.yaml new file mode 100644 index 0000000000..2c3f6d6f25 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_MachineScope.yaml @@ -0,0 +1,19 @@ +# Update manifest with only a machine-scope installer. +Id: AppInstallerCliTest.TestExeInstaller +Version: 2.0.0.0 +Name: AppInstaller Test Installer Machine Scope +Publisher: Microsoft Corporation +AppMoniker: AICLITestExeMachineScope +License: Test +Switches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Scope: machine + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestVersion: 0.1.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/UpdateFlow.cpp b/src/AppInstallerCLITests/UpdateFlow.cpp index 9d70283c78..a4bb811cda 100644 --- a/src/AppInstallerCLITests/UpdateFlow.cpp +++ b/src/AppInstallerCLITests/UpdateFlow.cpp @@ -540,6 +540,7 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") OverrideForMSStore(context, true); OverrideForPortableInstall(context); context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); UpgradeCommand update({}); update.Execute(context); @@ -556,6 +557,22 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } +TEST_CASE("UpdateFlow_UpdateAll_ReportsNotApplicable", "[UpdateFlow][workflow]") +{ + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_NotApplicable })); + context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(updateOutput.str().find(Resource::String::UpgradeNotApplicableCount(1)) != std::string::npos); +} + TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); diff --git a/src/AppInstallerCLITests/WorkflowCommon.cpp b/src/AppInstallerCLITests/WorkflowCommon.cpp index 93a42282a4..b80beca475 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.cpp +++ b/src/AppInstallerCLITests/WorkflowCommon.cpp @@ -276,6 +276,22 @@ namespace TestCommon PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); }); + const TestSourceResult TestInstaller_Exe_NotApplicable( + "TestExeInstallerNotApplicable"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_MachineScope.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + const TestSourceResult TestInstaller_Exe_DifferentInstallerType( "TestExeInstallerWithDifferentInstalledType"sv, [](std::vector& matches, std::weak_ptr source) { diff --git a/src/AppInstallerCLITests/WorkflowCommon.h b/src/AppInstallerCLITests/WorkflowCommon.h index e4f6333780..3c4507d03e 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.h +++ b/src/AppInstallerCLITests/WorkflowCommon.h @@ -33,6 +33,7 @@ namespace TestCommon const extern TestSourceResult TestQuery_ReturnOne; const extern TestSourceResult TestQuery_ReturnTwo; const extern TestSourceResult TestInstaller_Exe; + const extern TestSourceResult TestInstaller_Exe_NotApplicable; const extern TestSourceResult TestInstaller_Exe_Dependencies; const extern TestSourceResult TestInstaller_Exe_DifferentInstallerType; const extern TestSourceResult TestInstaller_Exe_ExpectedReturnCodes;