Skip to content

Commit

Permalink
Add the ability to specify "scope or unknown" via COM (#2402)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JohnMcPMS authored Aug 3, 2022
1 parent d0ff81e commit 4950d33
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 13 deletions.
7 changes: 7 additions & 0 deletions src/AppInstallerCLICore/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace AppInstaller::CLI::Execution
DependencySource,
AllowedArchitectures,
PortableARPEntry,
AllowUnknownScope,
Max
};

Expand Down Expand Up @@ -222,5 +223,11 @@ namespace AppInstaller::CLI::Execution
{
using value_t = Registry::Portable::PortableARPEntry;
};

template <>
struct DataMapping<Data::AllowUnknownScope>
{
using value_t = bool;
};
}
}
34 changes: 28 additions & 6 deletions src/AppInstallerCLICore/Workflows/ManifestComparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,17 +310,18 @@ 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>());

// 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));
Expand All @@ -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
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -379,6 +400,7 @@ namespace AppInstaller::CLI::Workflow

Manifest::ScopeEnum m_preference;
Manifest::ScopeEnum m_requirement;
bool m_allowUnknownInAdditionToRequired;
};

struct InstalledLocaleComparator : public details::ComparisonField
Expand Down Expand Up @@ -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));
}

Expand Down
40 changes: 40 additions & 0 deletions src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,46 @@ public async Task InstallPortableFailsWithCleanup()
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);
}
}
}
28 changes: 28 additions & 0 deletions src/AppInstallerCLITests/ManifestComparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
19 changes: 19 additions & 0 deletions src/Microsoft.Management.Deployment/Converters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
1 change: 1 addition & 0 deletions src/Microsoft.Management.Deployment/Converters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>) \
Expand Down
10 changes: 4 additions & 6 deletions src/Microsoft.Management.Deployment/PackageManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion src/Microsoft.Management.Deployment/PackageManager.idl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.
namespace Microsoft.Management.Deployment
{
[contractversion(4)]
[contractversion(5)]
apicontract WindowsPackageManagerContract{};

/// State of the install
Expand Down Expand Up @@ -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)]
Expand Down

0 comments on commit 4950d33

Please sign in to comment.