commit 4950d3361b7ad32886ac158fb4c85641a55c231f
parent d0ff81ed62ca582a5def366f561668909fc4838d
Author: JohnMcPMS <johnmcp@microsoft.com>
Date: Wed, 3 Aug 2022 16:50:59 -0700
Add the ability to specify "scope or unknown" via COM (#2402)
Adds two new scope options for COM; `UserOrUnknown` and `SystemOrUnknown`. These will allow the installer selection to consider both the specific scope, as well as an unknown scope.
Diffstat:
8 files changed, 135 insertions(+), 13 deletions(-)
diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h
@@ -54,6 +54,7 @@ namespace AppInstaller::CLI::Execution
DependencySource,
AllowedArchitectures,
PortableARPEntry,
+ AllowUnknownScope,
Max
};
@@ -222,5 +223,11 @@ namespace AppInstaller::CLI::Execution
{
using value_t = Registry::Portable::PortableARPEntry;
};
+
+ template <>
+ struct DataMapping<Data::AllowUnknownScope>
+ {
+ using value_t = bool;
+ };
}
}
diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp
@@ -310,10 +310,10 @@ namespace AppInstaller::CLI::Workflow
struct ScopeComparator : public details::ComparisonField
{
- ScopeComparator(Manifest::ScopeEnum preference, Manifest::ScopeEnum requirement) :
- details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement) {}
+ ScopeComparator(Manifest::ScopeEnum preference, Manifest::ScopeEnum requirement, bool allowUnknownInAdditionToRequired) :
+ details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement), m_allowUnknownInAdditionToRequired(allowUnknownInAdditionToRequired) {}
- static std::unique_ptr<ScopeComparator> Create(const Execution::Args& args)
+ static std::unique_ptr<ScopeComparator> Create(const Execution::Context& context)
{
// Preference will always come from settings
Manifest::ScopeEnum preference = ConvertScope(Settings::User().Get<Settings::Setting::InstallScopePreference>());
@@ -321,6 +321,7 @@ namespace AppInstaller::CLI::Workflow
// Requirement may come from args or settings; args overrides settings.
Manifest::ScopeEnum requirement = Manifest::ScopeEnum::Unknown;
+ const auto& args = context.Args;
if (args.Contains(Execution::Args::Type::InstallScope))
{
requirement = Manifest::ConvertToScopeEnum(args.GetArg(Execution::Args::Type::InstallScope));
@@ -330,9 +331,21 @@ namespace AppInstaller::CLI::Workflow
requirement = ConvertScope(Settings::User().Get<Settings::Setting::InstallScopeRequirement>());
}
+ bool allowUnknownInAdditionToRequired = false;
+ if (context.Contains(Execution::Data::AllowUnknownScope))
+ {
+ allowUnknownInAdditionToRequired = context.Get<Execution::Data::AllowUnknownScope>();
+
+ // Force the required type to be preferred over Unknown
+ if (requirement != Manifest::ScopeEnum::Unknown)
+ {
+ preference = requirement;
+ }
+ }
+
if (preference != Manifest::ScopeEnum::Unknown || requirement != Manifest::ScopeEnum::Unknown)
{
- return std::make_unique<ScopeComparator>(preference, requirement);
+ return std::make_unique<ScopeComparator>(preference, requirement, allowUnknownInAdditionToRequired);
}
else
{
@@ -342,7 +355,15 @@ namespace AppInstaller::CLI::Workflow
InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override
{
- if (m_requirement == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerIgnoreScopeFromManifest(installer))
+ // Applicable if one of:
+ // 1. No requirement (aka is Unknown)
+ // 2. Requirement met
+ // 3. Installer scope is Unknown and this has been explicitly allowed
+ // 4. The installer type is scope agnostic (we can control it)
+ if (m_requirement == Manifest::ScopeEnum::Unknown ||
+ installer.Scope == m_requirement ||
+ (installer.Scope == Manifest::ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) ||
+ DoesInstallerIgnoreScopeFromManifest(installer))
{
return InapplicabilityFlags::None;
}
@@ -379,6 +400,7 @@ namespace AppInstaller::CLI::Workflow
Manifest::ScopeEnum m_preference;
Manifest::ScopeEnum m_requirement;
+ bool m_allowUnknownInAdditionToRequired;
};
struct InstalledLocaleComparator : public details::ComparisonField
@@ -607,7 +629,7 @@ namespace AppInstaller::CLI::Workflow
AddComparator(LocaleComparator::Create(context.Args));
}
- AddComparator(ScopeComparator::Create(context.Args));
+ AddComparator(ScopeComparator::Create(context));
AddComparator(MachineArchitectureComparator::Create(context, installationMetadata));
}
diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs
@@ -346,6 +346,46 @@ namespace AppInstallerCLIE2ETests.Interop
Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status);
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false);
Directory.Delete(conflictDirectory, true);
+ }
+
+
+ [Test]
+ public async Task InstallRequireUserScope()
+ {
+ // Find package
+ var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller");
+
+ // Configure installation
+ var installOptions = TestFactory.CreateInstallOptions();
+ installOptions.PackageInstallMode = PackageInstallMode.Silent;
+ installOptions.PreferredInstallLocation = installDir;
+ installOptions.PackageInstallScope = PackageInstallScope.User;
+
+ // Install
+ var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions);
+
+ // Assert
+ Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status);
+ }
+
+
+ [Test]
+ public async Task InstallRequireUserScopeAndUnknown()
+ {
+ // Find package
+ var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller");
+
+ // Configure installation
+ var installOptions = TestFactory.CreateInstallOptions();
+ installOptions.PackageInstallMode = PackageInstallMode.Silent;
+ installOptions.PreferredInstallLocation = installDir;
+ installOptions.PackageInstallScope = PackageInstallScope.UserOrUnknown;
+
+ // Install
+ var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions);
+
+ // Assert
+ Assert.AreEqual(InstallResultStatus.Ok, installResult.Status);
}
}
}
\ No newline at end of file
diff --git a/src/AppInstallerCLITests/ManifestComparator.cpp b/src/AppInstallerCLITests/ManifestComparator.cpp
@@ -640,3 +640,31 @@ TEST_CASE("ManifestComparator_MarketFilter", "[manifest_comparator]")
RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Market});
}
}
+
+TEST_CASE("ManifestComparator_Scope_AllowUnknown", "[manifest_comparator]")
+{
+ Manifest manifest;
+ ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown);
+
+ ManifestComparatorTestContext testContext;
+ testContext.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User));
+
+ SECTION("Default")
+ {
+ ManifestComparator mc(testContext, {});
+ auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest);
+
+ REQUIRE(!result);
+ RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope });
+ }
+ SECTION("Allow Unknown")
+ {
+ testContext.Add<Data::AllowUnknownScope>(true);
+
+ ManifestComparator mc(testContext, {});
+ auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest);
+
+ RequireInstaller(result, expected);
+ REQUIRE(inapplicabilities.size() == 0);
+ }
+}
diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp
@@ -242,4 +242,23 @@ namespace winrt::Microsoft::Management::Deployment::implementation
return {};
}
+
+ std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope)
+ {
+ switch (scope)
+ {
+ case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any:
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false);
+ case winrt::Microsoft::Management::Deployment::PackageInstallScope::User:
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, false);
+ case winrt::Microsoft::Management::Deployment::PackageInstallScope::System:
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, false);
+ case winrt::Microsoft::Management::Deployment::PackageInstallScope::UserOrUnknown:
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, true);
+ case winrt::Microsoft::Management::Deployment::PackageInstallScope::SystemOrUnknown:
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, true);
+ }
+
+ return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false);
+ }
}
diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h
@@ -17,6 +17,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation
winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult);
std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture);
std::optional<winrt::Windows::System::ProcessorArchitecture> GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture);
+ std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope);
#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_) \
if constexpr (std::is_same_v<TStatus, winrt::Microsoft::Management::Deployment::InstallResultStatus>) \
diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp
@@ -351,13 +351,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation
}
// If the PackageInstallScope is anything other than ::Any then set it as a requirement.
- if (options.PackageInstallScope() == PackageInstallScope::System)
+ auto manifestScope = GetManifestScope(options.PackageInstallScope());
+ if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown)
{
- context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::Machine));
- }
- else if (options.PackageInstallScope() == PackageInstallScope::User)
- {
- context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::User));
+ context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first));
+ context->Add<Execution::Data::AllowUnknownScope>(manifestScope.second);
}
if (options.PackageInstallMode() == PackageInstallMode::Interactive)
diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
namespace Microsoft.Management.Deployment
{
- [contractversion(4)]
+ [contractversion(5)]
apicontract WindowsPackageManagerContract{};
/// State of the install
@@ -551,6 +551,13 @@ namespace Microsoft.Management.Deployment
User,
/// Only System installers will be valid
System,
+ [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)]
+ {
+ /// Both User and Unknown install scope installers are valid
+ UserOrUnknown,
+ /// Both System and Unknown install scope installers are valid
+ SystemOrUnknown,
+ }
};
[contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)]