diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 64d31bf255..1d5ee359ef 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -305,6 +305,7 @@ + @@ -369,7 +370,6 @@ - @@ -391,6 +391,7 @@ + @@ -456,7 +457,6 @@ - diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index a2407b19ff..0004d827b1 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -47,9 +47,6 @@ Workflows - - Workflows - Workflows @@ -284,6 +281,9 @@ Commands\Configuration + + Commands\Configuration + @@ -301,9 +301,6 @@ Source Files - - Workflows - Workflows @@ -535,6 +532,9 @@ Commands\Configuration + + Commands\Configuration + diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 4101d20e47..1380568ff0 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -289,6 +289,8 @@ namespace AppInstaller::CLI return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; case Execution::Args::Type::OutputFile: return { type, "output"_liv, 'o' }; + case Execution::Args::Type::Correlation: + return { type, "correlation"_liv }; case Execution::Args::Type::DependencySource: return { type, "dependency-source"_liv, ArgTypeCategory::ExtendedSource }; @@ -468,6 +470,8 @@ namespace AppInstaller::CLI return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; case Args::Type::Family: return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::Correlation: + return Argument{ type, Resource::String::CorrelationArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; default: THROW_HR(E_UNEXPECTED); } @@ -486,6 +490,7 @@ namespace AppInstaller::CLI args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); args.push_back(ForType(Args::Type::Proxy)); args.push_back(ForType(Args::Type::NoProxy)); + args.push_back(ForType(Args::Type::Correlation)); } std::string Argument::GetUsageString() const diff --git a/src/AppInstallerCLICore/Commands/DscCommand.cpp b/src/AppInstallerCLICore/Commands/DscCommand.cpp index a31016447a..d3a57b7b44 100644 --- a/src/AppInstallerCLICore/Commands/DscCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommand.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "DscCommand.h" +#include "DscPackageResource.h" #ifndef AICLI_DISABLE_TEST_HOOKS #include "DscTestFileResource.h" @@ -13,6 +14,7 @@ namespace AppInstaller::CLI std::vector> DscCommand::GetCommands() const { return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), #ifndef AICLI_DISABLE_TEST_HOOKS std::make_unique(FullName()), std::make_unique(FullName()), diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index 05b860fc5c..6cd2dd6773 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -4,6 +4,7 @@ #include "DscCommandBase.h" #include "DscCommand.h" #include +#include #define WINGET_DSC_FUNCTION_FOREACH(_macro_) \ _macro_(Get); \ @@ -201,8 +202,7 @@ namespace AppInstaller::CLI void DscCommandBase::ExecuteInternal(Execution::Context& context) const { context.Reporter.SetChannel(Execution::Reporter::Channel::Json); - - // TODO: Consider adding a stderr logger + Logging::StdErrLogger::Add(); #define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \ if (context.Args.Contains(Execution::Args::Type::DscResourceFunction ## _function_)) \ @@ -266,18 +266,33 @@ namespace AppInstaller::CLI #undef WINGET_DSC_FUNCTION_METHOD - std::optional DscCommandBase::GetJsonFromInput(Execution::Context& context) const + std::optional DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const { - Json::Value result; - Json::CharReaderBuilder builder; - Json::String errors; - if (!Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors)) + // Don't attempt to read from an interactive stream as this will just block + if (!context.Reporter.InputStreamIsInteractive()) { + AICLI_LOG(CLI, Verbose, << "Reading Json from input stream..."); + + Json::Value result; + Json::CharReaderBuilder builder; + Json::String errors; + if (Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors)) + { + AICLI_LOG(CLI, Info, << "Json from input stream:\n" << Json::writeString(Json::StreamWriterBuilder{}, result)); + return result; + } + AICLI_LOG(CLI, Error, << "Failed to read input JSON: " << errors); - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, std::nullopt); } - return result; + if (terminateContextOnError) + { + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, std::nullopt); + } + else + { + return std::nullopt; + } } void DscCommandBase::WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.h b/src/AppInstallerCLICore/Commands/DscCommandBase.h index 538ef6b498..a89f1019e0 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.h +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.h @@ -94,7 +94,7 @@ namespace AppInstaller::CLI virtual void ResourceFunctionManifest(Execution::Context& context) const; // Parses a JSON object from stdin. - std::optional GetJsonFromInput(Execution::Context& context) const; + std::optional GetJsonFromInput(Execution::Context& context, bool terminateContextOnError = true) const; // Writes the value to the context output. void WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const; diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.cpp b/src/AppInstallerCLICore/Commands/DscComposableObject.cpp index 82bf4f5be3..cb6be0898e 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.cpp +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.cpp @@ -5,6 +5,32 @@ namespace AppInstaller::CLI { + namespace + { + std::string GetTypeString(Json::ValueType type) + { + switch (type) + { + case Json::nullValue: + return "null"; + case Json::intValue: + case Json::uintValue: + case Json::realValue: + return "number"; + case Json::stringValue: + return "string"; + case Json::booleanValue: + return "boolean"; + case Json::arrayValue: + return "array"; + case Json::objectValue: + return "object"; + default: + THROW_HR(E_UNEXPECTED); + } + } + } + namespace details { const Json::Value* GetProperty(const Json::Value& object, std::string_view name) @@ -32,7 +58,14 @@ namespace AppInstaller::CLI return result; } - void AddPropertySchema(Json::Value& object, std::string_view name, DscComposablePropertyFlag flags, std::string_view type, std::string_view description) + void AddPropertySchema( + Json::Value& object, + std::string_view name, + DscComposablePropertyFlag flags, + Json::ValueType type, + std::string_view description, + const std::vector& enumValues, + const std::optional& defaultValue) { Json::Value& propertiesObject = object["properties"]; @@ -45,13 +78,30 @@ namespace AppInstaller::CLI Json::Value property{ Json::ValueType::objectValue }; - if (!type.empty()) + if (type != Json::ValueType::objectValue) { - property["type"] = std::string{ type }; + property["type"] = GetTypeString(type); } property["description"] = std::string{ description }; + if (!enumValues.empty()) + { + Json::Value enumArray{ Json::ValueType::arrayValue }; + + for (const std::string& enumValue : enumValues) + { + enumArray.append(enumValue); + } + + property["enum"] = std::move(enumArray); + } + + if (defaultValue) + { + property["default"] = defaultValue.value(); + } + propertiesObject[nameString] = std::move(property); if (WI_IsFlagSet(flags, DscComposablePropertyFlag::Required)) diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 16fa2dc344..ec7f13faa0 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -4,10 +4,15 @@ #include #include #include +#include +#include "Resources.h" #include #include +#include +#include using namespace std::string_view_literals; +using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { @@ -33,7 +38,14 @@ namespace AppInstaller::CLI Json::Value GetBaseSchema(const std::string& title); // Adds a property to the schema object. - void AddPropertySchema(Json::Value& object, std::string_view name, DscComposablePropertyFlag flags, std::string_view type, std::string_view description); + void AddPropertySchema( + Json::Value& object, + std::string_view name, + DscComposablePropertyFlag flags, + Json::ValueType type, + std::string_view description, + const std::vector& enumValues, + const std::optional& defaultValue); } template @@ -50,9 +62,9 @@ namespace AppInstaller::CLI return value.asBool(); } - static std::string_view SchemaTypeName() + static Json::ValueType SchemaType() { - return "boolean"sv; + return Json::ValueType::booleanValue; } }; @@ -64,9 +76,9 @@ namespace AppInstaller::CLI return value.asString(); } - static std::string_view SchemaTypeName() + static Json::ValueType SchemaType() { - return "string"sv; + return Json::ValueType::stringValue; } }; @@ -78,10 +90,9 @@ namespace AppInstaller::CLI return value; } - static std::string_view SchemaTypeName() + static Json::ValueType SchemaType() { - // Indicates that the schema should not set a type - return {}; + return Json::ValueType::objectValue; } }; @@ -103,16 +114,19 @@ namespace AppInstaller::CLI { DscComposableObject() = default; - DscComposableObject(const std::optional& input) + DscComposableObject(const std::optional& input, bool ignoreFieldRequirements = false) { - THROW_HR_IF(E_POINTER, !input); - FromJson(input.value()); + THROW_HR_IF(E_POINTER, !input && !ignoreFieldRequirements); + if (input) + { + FromJson(input.value(), ignoreFieldRequirements); + } } // Read values for each property - void FromJson(const Json::Value& input) + void FromJson(const Json::Value& input, bool ignoreFieldRequirements = false) { - (FoldHelper{}, ..., Property::FromJson(this, details::GetProperty(input, Property::Name()))); + (FoldHelper{}, ..., Property::FromJson(this, details::GetProperty(input, Property::Name()), ignoreFieldRequirements)); } // Populate JSON object with properties. @@ -124,7 +138,7 @@ namespace AppInstaller::CLI } // Copies the appropriate values to a new object for output. - DscComposableObject CopyForOutput() + DscComposableObject CopyForOutput() const { DscComposableObject result; (FoldHelper{}, ..., Property::CopyForOutput(this, &result)); @@ -135,7 +149,7 @@ namespace AppInstaller::CLI static Json::Value Schema(const std::string& title) { Json::Value result = details::GetBaseSchema(title); - (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaTypeName(), Property::Description())); + (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default())); return result; } }; @@ -146,7 +160,7 @@ namespace AppInstaller::CLI using Type = PropertyType; static constexpr DscComposablePropertyFlag Flags = PropertyFlags; - static void FromJson(Derived* self, const Json::Value* value) + static void FromJson(Derived* self, const Json::Value* value, bool ignoreFieldRequirements) { if (value) { @@ -154,7 +168,7 @@ namespace AppInstaller::CLI } else { - if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) + if (!ignoreFieldRequirements && WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) { THROW_HR_MSG(WINGET_CONFIG_ERROR_MISSING_FIELD, "Required property `%hs` not provided.", Derived::Name().data()); } @@ -190,25 +204,39 @@ namespace AppInstaller::CLI std::optional m_value; }; -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ struct _property_type_ : public DscComposableProperty<_property_type_, _value_type_, _flags_> \ { \ static std::string_view Name() { return _json_name_; } \ - static std::string_view Description() { return _description_; } \ + static Resource::LocString Description() { return _description_; } \ + static std::vector EnumValues() { return std::vector _enum_vals_; } \ + static std::optional Default() { return _default_; } \ std::optional& _property_name_() { return m_value; } \ const std::optional& _property_name_() const { return m_value; } \ void _property_name_(std::optional value) { m_value = std::move(value); } \ -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ }; -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(_property_type_, _value_type_, _property_name_, _json_name_, _description_) WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_) -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(_property_type_, _value_type_, _property_name_, _json_name_, _description_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, {}) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, {}, {}) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, _default_) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, _enum_vals_, _default_) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, "Indicates whether an instance should/does exist.") - bool ShouldExist() { return m_value.value_or(true); } + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) + bool ShouldExist() const { return m_value.value_or(true); } }; - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(StandardInDesiredStateProperty, bool, InDesiredState, "_inDesiredState", "Indicates whether an instance is in the desired state."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(StandardInDesiredStateProperty, bool, InDesiredState, "_inDesiredState", Resource::String::DscResourcePropertyDescriptionInDesiredState); } diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp new file mode 100644 index 0000000000..d3e480d3db --- /dev/null +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -0,0 +1,527 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscPackageResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/UninstallFlow.h" +#include "Workflows/UpdateFlow.h" +#include + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageId); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageSource); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", Resource::String::DscResourcePropertyDescriptionPackageVersion); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", Resource::String::DscResourcePropertyDescriptionPackageMatchOption, ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(UseLatestProperty, bool, UseLatest, "useLatest", Resource::String::DscResourcePropertyDescriptionPackageUseLatest, "false"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(InstallModeProperty, std::string, InstallMode, "installMode", Resource::String::DscResourcePropertyDescriptionPackageInstallMode, ({ "default", "silent", "interactive" }), "silent"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionPackageAcceptAgreements); + + // TODO: To support Scope on this resource: + // 1. Change the installed source to pull in all package info for both scopes by default + // 2. Change the installed source open in workflows to always open for everything, regardless of scope + // 3. Improve correlation handling if needed for cross-scope package installations + // 4. Update the test EXE installer to handle being installed for both scopes + using PackageResourceObject = DscComposableObject; + + std::optional ToMatchType(const std::optional& value) + { + if (!value) + { + return std::nullopt; + } + + std::string lowerValue = Utility::ToLower(value.value()); + + if (lowerValue == "equals") + { + return MatchType::Exact; + } + else if (lowerValue == "equals""case""insensitive") + { + return MatchType::CaseInsensitive; + } + else if (lowerValue == "starts""with""case""insensitive") + { + return MatchType::StartsWith; + } + else if (lowerValue == "contains""case""insensitive") + { + return MatchType::Substring; + } + + THROW_HR(E_INVALIDARG); + } + + struct PackageFunctionData + { + PackageFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : + Input(json, ignoreFieldRequirements), + ParentContext(context) + { + Reset(); + } + + const PackageResourceObject Input; + PackageResourceObject Output; + Execution::Context& ParentContext; + std::unique_ptr SubContext; + + // Reset the state that is modified by Get + void Reset() + { + Output = Input.CopyForOutput(); + + SubContext = ParentContext.CreateSubContext(); + SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); + + if (Input.AcceptAgreements().value_or(false)) + { + SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); + SubContext->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + } + + void PrepareSubContextInputs() + { + if (Input.Source()) + { + SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); + } + } + + // Fills the Output object with the current state + bool Get() + { + PrepareSubContextInputs(); + + *SubContext << + Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext), false, CompositeSearchBehavior::AllPackages); + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return false; + } + + // Do a manual search of the now opened source + Source& source = SubContext->Get(); + MatchType matchType = ToMatchType(Input.MatchOption()).value_or(MatchType::CaseInsensitive); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, Input.Identifier().value())); + + SearchResult result = source.Search(request); + SubContext->Add(result); + + if (result.Matches.empty()) + { + Output.Exist(false); + } + else if (result.Matches.size() > 1) + { + AICLI_LOG(Config, Warning, << "Found " << result.Matches.size() << " matches when searching for '" << Input.Identifier().value() << "'"); + Output.Exist(false); + } + else + { + auto& package = result.Matches.front().Package; + SubContext->Add(package); + + auto installedPackage = package->GetInstalled(); + + // Fill Output and SubContext + Output.Exist(static_cast(installedPackage)); + Output.Identifier(package->GetProperty(PackageProperty::Id)); + + if (installedPackage) + { + auto versionKeys = installedPackage->GetVersionKeys(); + AICLI_LOG(CLI, Verbose, << "Package::Get found " << versionKeys.size() << " installed versions"); + + std::shared_ptr installedVersion; + + // Find the specific version provided if possible + if (Input.Version()) + { + Utility::Version inputVersion{ Input.Version().value() }; + + for (const auto& key : versionKeys) + { + if (inputVersion == Utility::Version{ key.Version }) + { + installedVersion = installedPackage->GetVersion(key); + break; + } + } + } + + if (!installedVersion) + { + installedVersion = installedPackage->GetLatestVersion(); + } + + if (installedVersion) + { + Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); + } + + auto data = Repository::GetLatestApplicableVersion(package); + Output.UseLatest(!data.UpdateAvailable); + } + } + + AICLI_LOG(CLI, Verbose, << "Package::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); + return true; + } + + void Uninstall() + { + AICLI_LOG(CLI, Verbose, << "Package::Uninstall invoked"); + + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::TargetVersion, Input.Version().value()); + } + else + { + SubContext->Args.AddArg(Execution::Args::Type::AllVersions); + } + + *SubContext << + Workflow::UninstallSinglePackage; + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + Output.Exist(false); + Output.Version(std::nullopt); + Output.UseLatest(std::nullopt); + } + + void Install(bool allowDowngrade = false) + { + AICLI_LOG(CLI, Verbose, << "Package::Install invoked"); + + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::Version, Input.Version().value()); + } + + *SubContext << + Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, allowDowngrade) << + Workflow::InstallSinglePackage; + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + Output.Exist(true); + + Output.Version(std::nullopt); + if (SubContext->Contains(Execution::Data::PackageVersion)) + { + const auto& packageVersion = SubContext->Get(); + if (packageVersion) + { + Output.Version(packageVersion->GetProperty(Repository::PackageVersionProperty::Version)); + } + } + + Output.UseLatest(std::nullopt); + } + + void Reinstall() + { + AICLI_LOG(CLI, Verbose, << "Package::Reinstall invoked"); + + SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); + + Install(true); + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + if (Input.ShouldExist()) + { + if (Output.Exist().value()) + { + AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Latest(" << TestLatest() << ")"); + return TestVersion() && TestLatest(); + } + else + { + AICLI_LOG(CLI, Verbose, << "Package::Test was false because the package was not installed"); + return false; + } + } + else + { + AICLI_LOG(CLI, Verbose, << "Package::Test desired the package to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); + return !Output.Exist().value(); + } + } + + Json::Value DiffJson() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + Json::Value result{ Json::ValueType::arrayValue }; + + if (Input.ShouldExist() != Output.Exist().value()) + { + result.append(std::string{ StandardExistProperty::Name() }); + } + else + { + if (!TestVersion()) + { + result.append(std::string{ VersionProperty::Name() }); + } + + if (!TestLatest()) + { + result.append(std::string{ UseLatestProperty::Name() }); + } + } + + return result; + } + + bool TestVersion() + { + if (Input.Version()) + { + if (Output.Version()) + { + return Utility::Version{ Input.Version().value() } == Utility::Version{ Output.Version().value() }; + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestLatest() + { + if (Input.UseLatest() && Input.UseLatest().value()) + { + if (Output.UseLatest()) + { + return Output.UseLatest().value(); + } + else + { + return false; + } + } + else + { + return true; + } + } + }; + } + + DscPackageResource::DscPackageResource(std::string_view parent) : + DscCommandBase(parent, "package", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscPackageResource::ShortDescription() const + { + return Resource::String::DscPackageResourceShortDescription; + } + + Resource::LocString DscPackageResource::LongDescription() const + { + return Resource::String::DscPackageResourceLongDescription; + } + + std::string DscPackageResource::ResourceType() const + { + return "Package"; + } + + void DscPackageResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscPackageResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + if (!data.Test()) + { + if (data.Input.ShouldExist()) + { + if (data.Output.Exist().value()) + { + if (!data.TestLatest()) + { + // Install will swap to update flow + AICLI_LOG(CLI, Info, << "Installing package to update to latest"); + data.Install(); + } + else // (!data.TestVersion()) + { + Utility::Version inputVersion{ data.Input.Version().value() }; + Utility::Version outputVersion{ data.Output.Version().value() }; + + if (outputVersion < inputVersion) + { + // Install will swap to update flow + AICLI_LOG(CLI, Info, << "Installing package to update to desired version"); + data.Install(); + } + else + { + AICLI_LOG(CLI, Info, << "Reinstalling package to downgrade to desired version"); + data.Reinstall(); + } + } + } + else + { + AICLI_LOG(CLI, Info, << "Installing package as it was not found"); + data.Install(); + } + } + else + { + AICLI_LOG(CLI, Info, << "Uninstalling package as desired"); + data.Uninstall(); + } + + if (data.SubContext->IsTerminated()) + { + return; + } + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscPackageResource::ResourceFunctionExport(Execution::Context& context) const + { + auto json = GetJsonFromInput(context, false); + PackageFunctionData data{ context, json, true }; + + data.PrepareSubContextInputs(); + + if (!data.Input.UseLatest().value_or(true)) + { + data.SubContext->Args.AddArg(Execution::Args::Type::IncludeVersions); + } + + data.SubContext->Args.AddArg(Execution::Args::Type::ConfigurationExportAll); + + *data.SubContext << + Workflow::SearchSourceForPackageExport; + + if (data.SubContext->IsTerminated()) + { + context.Terminate(data.SubContext->GetTerminationHR()); + return; + } + + const auto& packageCollection = data.SubContext->Get(); + + for (const auto& source : packageCollection.Sources) + { + for (const auto& package : source.Packages) + { + PackageResourceObject output; + + output.Identifier(package.Id); + output.Source(source.Details.Name); + + if (!package.VersionAndChannel.GetVersion().IsEmpty()) + { + output.Version(package.VersionAndChannel.GetVersion().ToString()); + } + + // TODO: Exporting scope requires one or more of the following: + // 1. Support for "OrUnknown" scope variants during Set (and workflows) + // 2. Tracking scope intent as we do for some other installer properties + // 3. Checking for the availability of the current scope in the package + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + + void DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, PackageResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.h b/src/AppInstallerCLICore/Commands/DscPackageResource.h new file mode 100644 index 0000000000..3bc0787096 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing package state. + struct DscPackageResource : public DscCommandBase + { + DscPackageResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp index eafc03f48f..63a59ea04f 100644 --- a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp @@ -8,10 +8,10 @@ using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { - namespace anon + namespace { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PathProperty, std::string, Path, "path", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The absolute path to a file."); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ContentProperty, std::string, Content, "content", "The content of the file."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PathProperty, std::string, Path, "path", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The absolute path to a file."_lis); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ContentProperty, std::string, Content, "content", "The content of the file."_lis); using TestFileObject = DscComposableObject; @@ -127,7 +127,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + TestFileFunctionData data{ json }; data.Get(); @@ -139,7 +139,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + TestFileFunctionData data{ json }; data.Get(); @@ -186,7 +186,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + TestFileFunctionData data{ json }; data.Get(); data.Output.InDesiredState(data.Test()); @@ -200,7 +200,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + TestFileFunctionData data{ json }; if (std::filesystem::exists(data.Path)) { @@ -215,7 +215,7 @@ namespace AppInstaller::CLI { if (std::filesystem::is_regular_file(file)) { - anon::TestFileObject output; + TestFileObject output; output.Path(file.path().u8string()); std::ifstream stream{ file.path(), std::ios::binary}; @@ -231,6 +231,6 @@ namespace AppInstaller::CLI void DscTestFileResource::ResourceFunctionSchema(Execution::Context& context) const { - WriteJsonOutputLine(context, anon::TestFileObject::Schema(ResourceType())); + WriteJsonOutputLine(context, TestFileObject::Schema(ResourceType())); } } diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp index 198c38fe12..595ffb704b 100644 --- a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp @@ -9,10 +9,10 @@ using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { - namespace anon + namespace { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PropertyProperty, std::string, Property, "property", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The JSON property name."); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ValueProperty, Json::Value, Value, "value", "The value for the JSON property."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PropertyProperty, std::string, Property, "property", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The JSON property name."_lis); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ValueProperty, Json::Value, Value, "value", "The value for the JSON property."_lis); using TestJsonObject = DscComposableObject; @@ -119,7 +119,7 @@ namespace AppInstaller::CLI { if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionDelete)) { - std::filesystem::remove_all(anon::TestJsonFunctionData::GetFilePath()); + std::filesystem::remove_all(TestJsonFunctionData::GetFilePath()); return; } @@ -135,7 +135,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestJsonFunctionData data{ json }; + TestJsonFunctionData data{ json }; data.Get(); @@ -147,7 +147,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestJsonFunctionData data{ json }; + TestJsonFunctionData data{ json }; data.Get(); @@ -181,7 +181,7 @@ namespace AppInstaller::CLI void DscTestJsonResource::ResourceFunctionExport(Execution::Context& context) const { - anon::TestJsonFunctionData data; + TestJsonFunctionData data; if (data.RootValue.isObject()) { @@ -191,7 +191,7 @@ namespace AppInstaller::CLI if (memberValue) { - anon::TestJsonObject output; + TestJsonObject output; output.Property(member); output.Value(*memberValue); @@ -203,6 +203,6 @@ namespace AppInstaller::CLI void DscTestJsonResource::ResourceFunctionSchema(Execution::Context& context) const { - WriteJsonOutputLine(context, anon::TestJsonObject::Schema(ResourceType())); + WriteJsonOutputLine(context, TestJsonObject::Schema(ResourceType())); } } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index 3dbc308831..3f548354b8 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -83,7 +83,7 @@ namespace AppInstaller::CLI Logging::UseGlobalTelemetryLoggerActivityIdOnly(); - Execution::Context context{ std::cout, std::cin }; + Execution::Context context; auto previousThreadGlobals = context.SetForCurrentThread(); // Set up debug string logging during initialization diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 2742b45b1a..f656ede107 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -166,6 +166,7 @@ namespace AppInstaller::CLI::Execution OpenLogs, // Opens the default logs directory after executing the command Force, // Forces the execution of the workflow with non security related issues OutputFile, + Correlation, DependencySource, // Index source to be queried against for finding dependencies CustomHeader, // Optional Rest source header diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index 5223fdd972..81bf6a4d02 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -93,6 +93,7 @@ namespace AppInstaller::CLI::Execution // arguments via Execution::Args. struct Context : EnumBasedVariantMap { + Context() = default; Context(std::ostream& out, std::istream& in) : Reporter(out, in) {} // Constructor for creating a sub-context. diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 8515673bf4..3d894b06e1 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -23,6 +23,29 @@ namespace AppInstaller::CLI::Execution const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow; + namespace + { + DWORD GetStdHandleType(DWORD stdHandle) + { + DWORD result = FILE_TYPE_UNKNOWN; + + HANDLE handle = GetStdHandle(stdHandle); + if (handle != INVALID_HANDLE_VALUE && handle != NULL) + { + result = GetFileType(handle); + } + + return result; + } + } + + Reporter::Reporter() : + Reporter(std::cout, std::cin) + { + m_outStreamFileType = GetStdHandleType(STD_OUTPUT_HANDLE); + m_inStreamFileType = GetStdHandleType(STD_INPUT_HANDLE); + } + Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : Reporter(std::make_shared(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream) { @@ -48,6 +71,11 @@ namespace AppInstaller::CLI::Execution Reporter::Reporter(const Reporter& other, clone_t) : Reporter(other.m_out, other.m_in) { + m_outStreamFileType = other.m_outStreamFileType; + m_inStreamFileType = other.m_inStreamFileType; + + SetChannel(other.m_channel); + if (other.m_style.has_value()) { SetStyle(*other.m_style); @@ -136,6 +164,12 @@ namespace AppInstaller::CLI::Execution return m_in; } + bool Reporter::InputStreamIsInteractive() const + { + AICLI_LOG(CLI, Verbose, << "Reporter::m_inStreamFileType is " << m_inStreamFileType); + return m_inStreamFileType == FILE_TYPE_CHAR; + } + bool Reporter::PromptForBoolResponse(Resource::LocString message, Level level, bool resultIfDisabled) { auto out = GetOutputStream(level); diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index d48547e11c..20babda255 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -60,6 +60,7 @@ namespace AppInstaller::CLI::Execution All = Verbose | Info | Warning | Error, }; + Reporter(); Reporter(std::ostream& outStream, std::istream& inStream); Reporter(const Reporter&) = delete; Reporter& operator=(const Reporter&) = delete; @@ -109,6 +110,9 @@ namespace AppInstaller::CLI::Execution // Get the raw input stream. std::istream& RawInputStream(); + // Check if the input stream is interactive or not. + bool InputStreamIsInteractive() const; + // Prompts the user, return true if they consented. bool PromptForBoolResponse(Resource::LocString message, Level level = Level::Info, bool resultIfDisabled = false); @@ -204,6 +208,8 @@ namespace AppInstaller::CLI::Execution wil::srwlock m_progressCallbackLock; std::atomic m_progressCallback; std::atomic m_progressSink; + DWORD m_outStreamFileType = FILE_TYPE_UNKNOWN; + DWORD m_inStreamFileType = FILE_TYPE_UNKNOWN; // Enable all levels by default Level m_enabledLevels = Level::All; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 43d42b9403..8b885c45bb 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -184,6 +184,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); + WINGET_DEFINE_RESOURCE_STRINGID(CorrelationArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); WINGET_DEFINE_RESOURCE_STRINGID(CustomSwitchesArgumentDescription); @@ -212,6 +213,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(Downloading); WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSet); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionWhatIf); @@ -223,6 +226,16 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionAdapter); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSchema); WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageScope); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageAcceptAgreements); WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 4457ec2b2b..948be1585b 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "DependenciesFlow.h" -#include "ManifestComparator.h" +#include #include "InstallFlow.h" #include "winget\DependenciesGraph.h" #include "DependencyNodeProcessor.h" diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index 93a52f1733..ad24e298f5 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "DependencyNodeProcessor.h" -#include "ManifestComparator.h" +#include "WorkflowBase.h" #include #include @@ -96,7 +96,7 @@ namespace AppInstaller::CLI::Workflow installationMetadata = m_nodePackageInstalledVersion->GetMetadata(); } - ManifestComparator manifestComparator(m_context, installationMetadata); + ManifestComparator manifestComparator(GetManifestComparatorOptions(m_context, installationMetadata)); auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(m_nodeManifest); if (!installer.has_value()) @@ -112,4 +112,4 @@ namespace AppInstaller::CLI::Workflow m_dependenciesList = m_installer.Dependencies; return DependencyNodeProcessorResult::Success; } -} \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp index 4bfc1d4a23..77e0b29abd 100644 --- a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp @@ -13,7 +13,7 @@ #include "AppInstallerMsixInfo.h" #include "AppInstallerSynchronization.h" #include "MSStoreInstallerHandler.h" -#include "ManifestComparator.h" +#include #include using namespace AppInstaller::Manifest; diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 2793c51e6e..ed4fa83261 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "ShowFlow.h" -#include "ManifestComparator.h" +#include #include "TableOutput.h" using namespace AppInstaller::Repository; diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 14b354d326..a873e15b3b 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -5,7 +5,7 @@ #include "DependenciesFlow.h" #include "InstallFlow.h" #include "UpdateFlow.h" -#include "ManifestComparator.h" +#include #include #include @@ -51,7 +51,7 @@ namespace AppInstaller::CLI::Workflow installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); } - ManifestComparator manifestComparator(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{}); + Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{})); bool versionFound = false; bool installedTypeInapplicable = false; bool packagePinned = false; @@ -116,7 +116,7 @@ namespace AppInstaller::CLI::Workflow if (!installer.has_value()) { // If there is at least one installer whose only reason is InstalledType. - auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), InapplicabilityFlags::InstalledType); + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); if (onlyInstalledType != inapplicabilities.end()) { installedTypeInapplicable = true; @@ -330,7 +330,7 @@ namespace AppInstaller::CLI::Workflow // If version specified, use the version and verify applicability context << GetManifestFromPackage(/* considerPins */ true); - if (m_operationType == OperationType::Upgrade) + if (m_operationType == OperationType::Upgrade && !m_allowDowngrade) { context << EnsureUpdateVersionApplicable; } diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.h b/src/AppInstallerCLICore/Workflows/UpdateFlow.h index 686c983aca..28a294f954 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.h +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.h @@ -39,13 +39,14 @@ namespace AppInstaller::CLI::Workflow // Outputs: None struct SelectSinglePackageVersionForInstallOrUpgrade : public WorkflowTask { - SelectSinglePackageVersionForInstallOrUpgrade(OperationType operation) : - WorkflowTask("SelectSinglePackageVersionForInstallOrUpgrade"), m_operationType(operation) {} + SelectSinglePackageVersionForInstallOrUpgrade(OperationType operation, bool allowDowngrade = false) : + WorkflowTask("SelectSinglePackageVersionForInstallOrUpgrade"), m_operationType(operation), m_allowDowngrade(allowDowngrade) {} void operator()(Execution::Context& context) const override; private: mutable OperationType m_operationType; + bool m_allowDowngrade; }; // Install or upgrade a single package @@ -62,4 +63,4 @@ namespace AppInstaller::CLI::Workflow private: mutable OperationType m_operationType; }; -} \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 44aa57650d..4f97bbf402 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "WorkflowBase.h" #include "ExecutionContext.h" -#include "ManifestComparator.h" +#include #include "PromptFlow.h" #include "Sixel.h" #include "TableOutput.h" @@ -460,6 +460,57 @@ namespace AppInstaller::CLI::Workflow return HandleException(&context, exception); } + AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const IPackageVersion::Metadata& metadata) + { + AppInstaller::Manifest::ManifestComparator::Options options; + bool getAllowedArchitecturesFromMetadata = false; + + if (context.Contains(Execution::Data::AllowedArchitectures)) + { + // Com caller can directly set allowed architectures + options.AllowedArchitectures = context.Get(); + } + else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) + { + // Arguments provided in command line + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))); + } + else if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) + { + // Arguments provided in command line. Also skips applicability check. + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture))); + options.SkipApplicabilityCheck = true; + } + else + { + getAllowedArchitecturesFromMetadata = true; + } + + if (context.Args.Contains(Execution::Args::Type::InstallerType)) + { + options.RequestedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallerType))); + } + + if (context.Args.Contains(Execution::Args::Type::InstallScope)) + { + options.RequestedInstallerScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + } + + if (context.Contains(Execution::Data::AllowUnknownScope)) + { + options.AllowUnknownScope = context.Get(); + } + + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + options.RequestedInstallerLocale = context.Args.GetArg(Execution::Args::Type::Locale); + } + + Repository::GetManifestComparatorOptionsFromMetadata(options, metadata, getAllowedArchitecturesFromMetadata); + + return options; + } + void OpenSource::operator()(Execution::Context& context) const { std::string_view sourceName; @@ -1374,12 +1425,12 @@ namespace AppInstaller::CLI::Workflow installationMetadata = context.Get()->GetMetadata(); } - ManifestComparator manifestComparator(context, installationMetadata); + Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, installationMetadata)); auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get()); if (!installer.has_value()) { - auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), InapplicabilityFlags::InstalledType); + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); if (onlyInstalledType != inapplicabilities.end()) { if (isRepair) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 4a41c325d0..f08cd39c7a 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -91,6 +92,9 @@ namespace AppInstaller::CLI::Workflow // Helper to report exceptions and return the HRESULT. HRESULT HandleException(Execution::Context& context, std::exception_ptr exception); + // Fills the options from the given context and metadata. + AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const Repository::IPackageVersion::Metadata& metadata); + // Creates the source object. // Required Args: None // Inputs: None diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs new file mode 100644 index 0000000000..b8831c1709 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -0,0 +1,677 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.Json; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public class DSCv3ResourceCommands + { + private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId; + private const string DefaultPackageLowVersion = "1.0.0.0"; + private const string DefaultPackageMidVersion = "1.1.0.0"; + private const string DefaultPackageHighVersion = "2.0.0.0"; + private const string PackageResource = "package"; + private const string GetFunction = "get"; + private const string TestFunction = "test"; + private const string SetFunction = "set"; + private const string ExportFunction = "export"; + private const string ExistPropertyName = "_exist"; + private const string VersionPropertyName = "version"; + private const string UseLatestPropertyName = "useLatest"; + + /// + /// Ensures that the test resources manifests are present. + /// + public static void EnsureTestResourcePresence() + { + string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps"); + Assert.IsNotEmpty(outputDirectory); + + var result = TestCommon.RunAICLICommand("dscv3 package", $"--manifest -o {outputDirectory}\\microsoft.winget.package.dsc.resource.json"); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + TestCommon.SetupTestSource(); + WinGetSettingsHelper.ConfigureFeature("dsc3", true); + WinGetSettingsHelper.ConfigureLoggingLevel("verbose"); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + RemoveTestPackage(); + WinGetSettingsHelper.ConfigureLoggingLevel(null); + WinGetSettingsHelper.ConfigureFeature("dsc3", false); + TestCommon.TearDownTestSource(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Try clean up TestExeInstaller for failure cases where cleanup is not successful + RemoveTestPackage(); + } + + /// + /// Calls `get` on the `package` resource with the value not present. + /// + [Test] + public void Package_Get_NotPresent() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + } + + /// + /// Calls `get` on the `package` resource with the package not existing. + /// + [Test] + public void Package_Get_UnknownIdentifier() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = "Not.An.Existing.Identifier.123456789.ABCDEFG" }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + } + + /// + /// Calls `get` on the `package` resource with the value present. + /// + [Test] + public void Package_Get_Present() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + } + + /// + /// Calls `get` on the `package` resource with the value present and supplying most inputs. + /// + [Test] + public void Package_Get_MuchInput() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Source = Constants.TestSourceName, + MatchOption = "equals", + }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + } + + /// + /// Calls `test` on the `package` resource with the value not present. + /// + [Test] + public void Package_Test_NotPresent() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + Assert.False(output.InDesiredState); + + Assert.IsNotNull(diff); + Assert.AreEqual(1, diff.Count); + Assert.AreEqual(ExistPropertyName, diff[0]); + } + + /// + /// Calls `test` on the `package` resource with the value present. + /// + [Test] + public void Package_Test_SimplePresent() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that matches. + /// + [Test] + public void Package_Test_VersionMatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageLowVersion, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that does not match. + /// + [Test] + public void Package_Test_VersionMismatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ VersionPropertyName ]); + } + + /// + /// Calls `test` on the `package` resource with a version that is the latest. + /// + [Test] + public void Package_Test_Latest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that is not the latest. + /// + [Test] + public void Package_Test_NotLatest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ UseLatestPropertyName ]); + } + + /// + /// Calls `set` on the `package` resource when the package is not present, and again afterward. + /// + [Test] + public void Package_Set_SimpleRepeated() + { + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Set again should be a no-op + result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `package` resource to ensure that it is not present. + /// + [Test] + public void Package_Set_Remove() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Exist = false, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceDataForGet.Identifier, output.Identifier); + } + + /// + /// Calls `set` on the `package` resource to request the latest version when a lower version is installed. + /// + [Test] + public void Package_Set_Latest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); + + AssertDiffState(diff, [ UseLatestPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + } + + /// + /// Calls `set` on the `package` resource to request a specific version when a lower version is installed. + /// + [Test] + public void Package_Set_SpecificVersionUpgrade() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); + + AssertDiffState(diff, [ VersionPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + } + + /// + /// Calls `set` on the `package` resource to request a specific version when a higher version is installed. + /// + [Test] + public void Package_Set_SpecificVersionDowngrade() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); + + AssertDiffState(diff, [ VersionPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + } + + /// + /// Calls `export` on the `package` resource without providing any input. + /// + [Test] + public void Package_Export_NoInput() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + var result = RunDSCv3Command(PackageResource, ExportFunction, " "); + AssertSuccessfulResourceRun(ref result); + + List output = GetOutputLinesAs(result.StdOut); + + bool foundDefaultPackage = false; + foreach (PackageResourceData item in output) + { + if (item.Identifier == DefaultPackageIdentifier) + { + foundDefaultPackage = true; + Assert.IsNull(item.Version); + break; + } + } + + Assert.IsTrue(foundDefaultPackage); + } + + /// + /// Calls `export` on the `package` resource providing input to request that versions be included. + /// + [Test] + public void Package_Export_RequestVersions() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + UseLatest = false, + }; + + var result = RunDSCv3Command(PackageResource, ExportFunction, packageResourceData, 300000); + AssertSuccessfulResourceRun(ref result); + + List output = GetOutputLinesAs(result.StdOut); + + bool foundDefaultPackage = false; + foreach (PackageResourceData item in output) + { + if (item.Identifier == DefaultPackageIdentifier) + { + foundDefaultPackage = true; + Assert.AreEqual(DefaultPackageLowVersion, item.Version); + } + else + { + Assert.IsNotNull(item.Version); + Assert.IsNotEmpty(item.Version); + } + } + + Assert.IsTrue(foundDefaultPackage); + } + + private static void RemoveTestPackage() + { + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Exist = false, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + } + + private static TestCommon.RunCommandResult RunDSCv3Command(string resource, string function, object input, int timeOut = 60000, bool throwOnTimeout = true) + { + return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout); + } + + private static void AssertSuccessfulResourceRun(ref TestCommon.RunCommandResult result) + { + Assert.AreEqual(0, result.ExitCode); + Assert.IsNotEmpty(result.StdOut); + } + + private static JsonSerializerOptions GetDefaultJsonOptions() + { + return new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(), + }, + }; + } + + private static string ConvertToJSON(object value) => value switch + { + string s => s, + null => null, + _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()), + }; + + private static string[] GetOutputLines(string output) + { + return output.TrimEnd().Split(Environment.NewLine); + } + + private static T GetSingleOutputLineAs(string output) + { + string[] lines = GetOutputLines(output); + Assert.AreEqual(1, lines.Length); + + return JsonSerializer.Deserialize(lines[0], GetDefaultJsonOptions()); + } + + private static (T, List) GetSingleOutputLineAndDiffAs(string output) + { + string[] lines = GetOutputLines(output); + Assert.AreEqual(2, lines.Length); + + var options = GetDefaultJsonOptions(); + return (JsonSerializer.Deserialize(lines[0], options), JsonSerializer.Deserialize>(lines[1], options)); + } + + private static List GetOutputLinesAs(string output) + { + List result = new List(); + string[] lines = GetOutputLines(output); + var options = GetDefaultJsonOptions(); + + foreach (string line in lines) + { + result.Add(JsonSerializer.Deserialize(line, options)); + } + + return result; + } + + private static void AssertExistingPackageResourceData(PackageResourceData output, string version, bool ignoreLatest = false) + { + Assert.IsNotNull(output); + Assert.True(output.Exist); + Assert.AreEqual(DefaultPackageIdentifier, output.Identifier); + Assert.AreEqual(version, output.Version); + + if (!ignoreLatest) + { + if (version == DefaultPackageHighVersion) + { + Assert.True(output.UseLatest); + } + else + { + Assert.False(output.UseLatest); + } + } + } + + private static void AssertDiffState(List diff, IList expected) + { + Assert.IsNotNull(diff); + Assert.AreEqual(expected.Count, diff.Count); + + foreach (string item in expected) + { + Assert.Contains(item, diff); + } + } + + private class PackageResourceData + { + [JsonPropertyName(ExistPropertyName)] + public bool? Exist { get; set; } + + [JsonPropertyName("_inDesiredState")] + public bool? InDesiredState { get; set; } + + [JsonPropertyName("id")] + public string Identifier { get; set; } + + public string Source { get; set; } + + public string Version { get; set; } + + public string MatchOption { get; set; } + + public bool? UseLatest { get; set; } + + public string InstallMode { get; set; } + + public bool? AcceptAgreements { get; set; } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/DownloadCommand.cs b/src/AppInstallerCLIE2ETests/DownloadCommand.cs index e18964a515..ba40685cc3 100644 --- a/src/AppInstallerCLIE2ETests/DownloadCommand.cs +++ b/src/AppInstallerCLIE2ETests/DownloadCommand.cs @@ -55,7 +55,7 @@ public void DownloadToDefaultDirectory() var result = TestCommon.RunAICLICommand("download", $"{Constants.ExeInstallerPackageId} --version {packageVersion}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ExeInstallerPackageId}_{packageVersion}"); - TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.User, PackageInstallerType.Exe); } /// @@ -67,7 +67,7 @@ public void DownloadToDirectory() var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"{Constants.ExeInstallerPackageId} --download-directory {downloadDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.User, PackageInstallerType.Exe); } /// diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 5e8946b82a..4afe1b2ceb 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -115,16 +115,27 @@ public static bool IsCIEnvironment /// The result of the command. public static RunCommandResult RunAICLICommand(string command, string parameters, string stdIn = null, int timeOut = 60000, bool throwOnTimeout = true) { + string correlationParameter = " --correlation " + Guid.NewGuid().ToString(); + + // Don't include correlation when the call has an option ending `--` value. + foreach (string part in parameters.Split(' ', StringSplitOptions.TrimEntries)) + { + if (part == "--") + { + correlationParameter = string.Empty; + } + } + string inputMsg = "AICLI path: " + TestSetup.Parameters.AICLIPath + " Command: " + command + - " Parameters: " + parameters + + " Parameters: " + parameters + correlationParameter + (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + " Timeout: " + timeOut; TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); - return RunAICLICommandViaDirectProcess(command, parameters, stdIn, timeOut, throwOnTimeout); + return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout); } /// diff --git a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs index 703d177b5e..6ad0d78f70 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs @@ -230,6 +230,26 @@ public static void InitializeAllFeatures(bool status) SetWingetSettings(settingsJson); } + /// + /// Configure the logging level for the settings. + /// + /// The logging level to set; null removes the value. + public static void ConfigureLoggingLevel(string level) + { + JObject settingsJson = GetJsonSettingsObject("logging"); + + if (level == null) + { + settingsJson["logging"]["level"]?.Parent?.Remove(); + } + else + { + settingsJson["logging"]["level"] = new JValue(level); + } + + SetWingetSettings(settingsJson); + } + /// /// Configure experimental features. /// diff --git a/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs b/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs index 31cc247aa5..0bd28b6ff4 100644 --- a/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/DownloadInterop.cs @@ -118,7 +118,7 @@ public async Task DownloadToDefaultDirectory() var packageVersion = "2.0.0.0"; Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ExeInstallerPackageId}_{packageVersion}"); - TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.User, PackageInstallerType.Exe); } /// @@ -142,7 +142,7 @@ public async Task DownloadToDirectory() // Assert Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe); + TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.User, PackageInstallerType.Exe); } /// diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs index 72eea928ef..03c350fb0a 100644 --- a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs @@ -482,7 +482,7 @@ public async Task InstallPortableFailsWithCleanup() public async Task InstallRequireUserScope() { // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); // Configure installation var installOptions = this.TestFactory.CreateInstallOptions(); @@ -505,7 +505,7 @@ public async Task InstallRequireUserScope() public async Task InstallRequireUserScopeAndUnknown() { // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); // Configure installation var installOptions = this.TestFactory.CreateInstallOptions(); @@ -725,4 +725,4 @@ public async Task InstallMSIX_VerifyInstalledCatalog() Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } } -} \ No newline at end of file +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml index 073e3c5c71..0e5dd4c9e0 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml @@ -5,6 +5,7 @@ Publisher: AppInstallerTest License: Test Installers: - Arch: x86 + Scope: user Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe Sha256: InstallerType: exe @@ -17,4 +18,18 @@ Installers: Language: /exeenus Log: /exelog InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.0.1.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml index 2cf660aa43..2e52222f20 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml @@ -5,6 +5,7 @@ Publisher: AppInstallerTest License: Test Installers: - Arch: x86 + Scope: user Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe Sha256: InstallerType: exe @@ -17,4 +18,18 @@ Installers: Language: /exeenus Log: /exelog InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.1.0.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml index f2d6af8541..7e44697901 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml @@ -5,6 +5,7 @@ Publisher: AppInstallerTest License: Test Installers: - Arch: x86 + Scope: user Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe Sha256: InstallerType: exe @@ -17,4 +18,18 @@ Installers: Language: /exeenus Log: /exelog InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 2.0.0.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml index 85e3be9e8e..9e29ba0c4e 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml @@ -5,6 +5,7 @@ Publisher: AppInstallerTest License: Test Installers: - Arch: x86 + Scope: user Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe Sha256: InstallerType: exe @@ -17,4 +18,18 @@ Installers: Language: /exeenus Log: /LogFile InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml new file mode 100644 index 0000000000..92ada3f407 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml @@ -0,0 +1,20 @@ +Id: AppInstallerTest.TestExeInstallerNoScope +Name: TestExeInstallerNoScope +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 5ec1036985..65c787d8c3 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3247,4 +3247,43 @@ Please specify one of them using the --source option to proceed. Get the resource manifest + + Manage package state + + + Allows management of package state via the DSC v3 command line interface protocol. See the help link for details. + + + Indicates whether an instance should or does exist. + + + Indicates whether an instance is in the desired state. + + + The identifier of the package. + + + The source of the package. + + + The version of the package. + + + The method for matching the identifier with a package. + + + Indicate that the latest available version of the package should be installed. + + + The install mode to use if needed. + + + The target scope of the package. + + + Indicates whether to accept agreements for package installs. + + + Value is logged for correlation + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 180c5800d5..bf57c8d039 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -149,9 +149,6 @@ Source Files\Common - - Source Files\CLI - Source Files\CLI @@ -377,6 +374,9 @@ Source Files\Repository + + Source Files\Common + @@ -1081,4 +1081,4 @@ TestData - + \ No newline at end of file diff --git a/src/AppInstallerCLITests/ManifestComparator.cpp b/src/AppInstallerCLITests/ManifestComparator.cpp index 2bd0110236..9a7f08db01 100644 --- a/src/AppInstallerCLITests/ManifestComparator.cpp +++ b/src/AppInstallerCLITests/ManifestComparator.cpp @@ -4,8 +4,9 @@ #include "TestCommon.h" #include #include -#include +#include #include +#include using namespace std::string_literals; using namespace std::string_view_literals; @@ -91,7 +92,7 @@ TEST_CASE("ManifestComparator_OSFilter_Low", "[manifest_comparator]") Manifest manifest; AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.99999.0"); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -103,7 +104,7 @@ TEST_CASE("ManifestComparator_OSFilter_High", "[manifest_comparator]") Manifest manifest; ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.0.0"); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, expected); @@ -117,7 +118,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Unknown", "[manifest_comparat SECTION("Nothing Installed") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -129,7 +130,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Unknown", "[manifest_comparat IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); @@ -140,7 +141,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter_Unknown", "[manifest_comparat IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); @@ -156,7 +157,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") SECTION("Nothing Installed") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -168,7 +169,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); @@ -179,7 +180,7 @@ TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); @@ -195,7 +196,7 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") SECTION("Nothing Installed") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -207,7 +208,7 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msi); @@ -218,7 +219,7 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msix); @@ -234,7 +235,7 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") SECTION("Nothing Installed") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -246,7 +247,7 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Exe); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, burn); @@ -257,7 +258,7 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Inno); - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, burn); @@ -273,7 +274,7 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") SECTION("Nothing Required") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -285,7 +286,7 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); @@ -296,7 +297,7 @@ TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); @@ -312,7 +313,7 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") SECTION("No Preference") { - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // The default preference is user @@ -324,7 +325,7 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") TestUserSettings settings; settings.Set(ScopeEnum::User); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, user); @@ -335,7 +336,7 @@ TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") TestUserSettings settings; settings.Set(ScopeEnum::Machine); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, machine); @@ -354,7 +355,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed_WithUnknown", "[manifes TestUserSettings settings; settings.Set({ "en-US" }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -366,7 +367,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed_WithUnknown", "[manifes IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); @@ -377,7 +378,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed_WithUnknown", "[manifes IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); @@ -396,7 +397,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator TestUserSettings settings; settings.Set({ "en-US" }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); // Only because it is first @@ -408,7 +409,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); @@ -419,7 +420,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -431,7 +432,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; metadata[PackageVersionMetadata::UserIntentLocale] = "fr-FR"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, frFR); @@ -443,7 +444,7 @@ TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; metadata[PackageVersionMetadata::UserIntentLocale] = "zh-CN"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -463,7 +464,7 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::Locale, "en-GB"s); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); @@ -474,7 +475,7 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::Locale, "zh-CN"s); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -485,7 +486,7 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") TestUserSettings settings; settings.Set({ "en-US" }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, enGB); @@ -496,7 +497,7 @@ TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") TestUserSettings settings; settings.Set({ "zh-CN" }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, unknown); @@ -512,7 +513,7 @@ TEST_CASE("ManifestComparator_AllowedArchitecture_x64_only", "[manifest_comparat ManifestComparatorTestContext context; context.Add({ Architecture::X64 }); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -532,7 +533,7 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") ManifestComparatorTestContext context; context.Add({ Architecture::X86, Architecture::X64 }); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x86); @@ -543,7 +544,7 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") ManifestComparatorTestContext context; context.Add({ Architecture::Unknown }); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(result); @@ -555,7 +556,7 @@ TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") ManifestComparatorTestContext context; context.Add({ Architecture::X86, Architecture::Unknown }); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x86); @@ -574,7 +575,7 @@ TEST_CASE("ManifestComparator_Architectures_WithUserIntent", "[manifest_comparat IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x86); @@ -586,7 +587,7 @@ TEST_CASE("ManifestComparator_Architectures_WithUserIntent", "[manifest_comparat metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, x64); @@ -601,7 +602,7 @@ TEST_CASE("ManifestComparator_Architectures_WithUserIntent", "[manifest_comparat metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; - ManifestComparator mc(ManifestComparatorTestContext{}, metadata); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(x86OnlyManifest); REQUIRE(!result); @@ -631,7 +632,7 @@ TEST_CASE("ManifestComparator_UnsupportedOSArchitecture", "[manifest_comparator] { ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { systemArchitecture }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -641,7 +642,7 @@ TEST_CASE("ManifestComparator_UnsupportedOSArchitecture", "[manifest_comparator] { ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { applicableArchitecture }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, installer); @@ -662,7 +663,7 @@ TEST_CASE("ManifestComparator_Inapplicabilities", "[manifest_comparator]") IPackageVersion::Metadata metadata; metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); - ManifestComparator mc(context, metadata); + ManifestComparator mc(GetManifestComparatorOptions(context, metadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -692,7 +693,7 @@ TEST_CASE("ManifestComparator_MarketFilter", "[manifest_comparator]") } ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, installer); @@ -712,7 +713,7 @@ TEST_CASE("ManifestComparator_MarketFilter", "[manifest_comparator]") } ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -730,7 +731,7 @@ TEST_CASE("ManifestComparator_Scope_AllowUnknown", "[manifest_comparator]") SECTION("Default") { - ManifestComparator mc(testContext, {}); + ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -740,7 +741,7 @@ TEST_CASE("ManifestComparator_Scope_AllowUnknown", "[manifest_comparator]") { testContext.Add(true); - ManifestComparator mc(testContext, {}); + ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, expected); @@ -760,7 +761,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::InstallerType, "msi"s); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msi); @@ -771,7 +772,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::InstallerType, "msix"s); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msix); @@ -782,7 +783,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") ManifestComparatorTestContext context; context.Args.AddArg(Args::Type::InstallerType, "portable"s); - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -793,7 +794,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Exe }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, exe); @@ -804,7 +805,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Portable }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msi); @@ -815,7 +816,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, exe); @@ -826,7 +827,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, msix); @@ -837,7 +838,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Exe }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, exe); @@ -848,7 +849,7 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") TestUserSettings settings; settings.Set({ InstallerTypeEnum::Inno }); - ManifestComparator mc(ManifestComparatorTestContext{}, {}); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); REQUIRE(!result); @@ -864,7 +865,7 @@ TEST_CASE("ManifestComparator_MachineArchitecture_Strong_Scope_Weak", "[manifest ManifestComparatorTestContext context; - ManifestComparator mc(context, {}); + ManifestComparator mc(GetManifestComparatorOptions(context, {})); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, system); @@ -881,7 +882,7 @@ TEST_CASE("ManifestComparator_InstallerCompatibilitySet_Weaker_Than_Architecture IPackageVersion::Metadata installationMetadata; installationMetadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(foil.EffectiveInstallerType()); - ManifestComparator mc(context, installationMetadata); + ManifestComparator mc(GetManifestComparatorOptions(context, installationMetadata)); auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); RequireInstaller(result, target); diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 42165b1778..7fbe528939 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -348,6 +348,7 @@ + @@ -402,6 +403,7 @@ + @@ -431,6 +433,7 @@ + @@ -466,6 +469,7 @@ + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 2c5d80bc70..1bc3db76bd 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -210,6 +210,12 @@ Public\winget + + Public\winget + + + Public\winget + @@ -380,6 +386,12 @@ Source Files + + Manifest + + + Source Files + diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp similarity index 72% rename from src/AppInstallerCLICore/Workflows/ManifestComparator.cpp rename to src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp index 96d617b524..92bd27f0aa 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp @@ -1,19 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "ManifestComparator.h" -#include "WorkflowBase.h" +#include #include #include #include #include -using namespace AppInstaller::CLI; using namespace AppInstaller::Manifest; namespace AppInstaller::Manifest { - std::ostream& operator<<(std::ostream& out, const AppInstaller::Manifest::ManifestInstaller& installer) + std::ostream& operator<<(std::ostream& out, const ManifestInstaller& installer) { return out << '[' << AppInstaller::Utility::ToString(installer.Arch) << ',' << @@ -23,7 +21,7 @@ namespace AppInstaller::Manifest } } -namespace AppInstaller::CLI::Workflow +namespace AppInstaller::Manifest { namespace { @@ -31,7 +29,7 @@ namespace AppInstaller::CLI::Workflow { PortableInstallFilter() : details::FilterField("Portable Install") {} - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { // Unvirtualized resources restricted capability is only supported for >= 10.0.18362 // TODO: Add support for OS versions that don't support virtualization. @@ -43,7 +41,7 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::None; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller&) override + std::string ExplainInapplicable(const ManifestInstaller&) override { std::string result = "Current OS is lower than supported MinOSVersion (10.0.18362) for Portable install"; return result; @@ -54,7 +52,7 @@ namespace AppInstaller::CLI::Workflow { OSVersionFilter() : details::FilterField("OS Version") {} - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { if (installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) { @@ -64,7 +62,7 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::OSVersion; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "Current OS is lower than MinOSVersion "; result += installer.MinOSVersion; @@ -82,67 +80,9 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Verbose, << "Architecture Comparator created with allowed architectures: " << Utility::ConvertContainerToString(m_allowedArchitectures, Utility::ToString)); } - static std::unique_ptr Create(const Execution::Context& context, const Repository::IPackageVersion::Metadata& metadata) + static std::unique_ptr Create(const ManifestComparator::Options& options) { - std::vector allowedArchitectures; - bool skipApplicabilityCheck = false; - - if (context.Contains(Execution::Data::AllowedArchitectures)) - { - // Com caller can directly set allowed architectures - allowedArchitectures = context.Get(); - } - else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) - { - // Arguments provided in command line - allowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))); - } - else if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) - { - // Arguments provided in command line. Also skips applicability check. - allowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture))); - skipApplicabilityCheck = true; - } - else - { - auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); - auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledArchitecture); - if (userIntentItr != metadata.end()) - { - // For upgrade, user intent from previous install is considered requirement - allowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(userIntentItr->second)); - } - else - { - if (installedItr != metadata.end()) - { - // For upgrade, previous installed architecture should be considered first preference and is always allowed. - // Then check settings requirements and preferences. - allowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(installedItr->second)); - } - - std::vector requiredArchitectures = Settings::User().Get(); - std::vector optionalArchitectures = Settings::User().Get(); - - if (!requiredArchitectures.empty()) - { - // Required architecture list from settings if applicable - allowedArchitectures.insert(allowedArchitectures.end(), requiredArchitectures.begin(), requiredArchitectures.end()); - } - else - { - // Preferred architecture list from settings if applicable, add Unknown to indicate allowing remaining applicable - if (!optionalArchitectures.empty()) - { - allowedArchitectures.insert(allowedArchitectures.end(), optionalArchitectures.begin(), optionalArchitectures.end()); - } - - allowedArchitectures.emplace_back(Utility::Architecture::Unknown); - } - } - } - - if (!allowedArchitectures.empty()) + if (!options.AllowedArchitectures.empty()) { // If the incoming data contains elements, we will use them to construct a final allowed list. // The algorithm is to take elements until we find Unknown, which indicates that any architecture is @@ -150,7 +90,7 @@ namespace AppInstaller::CLI::Workflow std::vector result; bool addRemainingApplicableArchitectures = false; - for (Utility::Architecture architecture : allowedArchitectures) + for (Utility::Architecture architecture : options.AllowedArchitectures) { if (architecture == Utility::Architecture::Unknown) { @@ -159,7 +99,7 @@ namespace AppInstaller::CLI::Workflow } // If the architecture is applicable and not already in our result set... - if ((skipApplicabilityCheck || Utility::IsApplicableArchitecture(architecture) != Utility::InapplicableArchitecture) && + if ((options.SkipApplicabilityCheck || Utility::IsApplicableArchitecture(architecture) != Utility::InapplicableArchitecture) && Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) { result.push_back(architecture); @@ -185,7 +125,7 @@ namespace AppInstaller::CLI::Workflow } } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { if (CheckAllowedArchitecture(installer.Arch) == Utility::InapplicableArchitecture || IsSystemArchitectureUnsupportedByInstaller(installer)) @@ -196,7 +136,7 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::None; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result; if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) @@ -217,7 +157,7 @@ namespace AppInstaller::CLI::Workflow return result; } - details::ComparisonResult IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) override + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override { auto arch1 = CheckAllowedArchitecture(first.Arch); auto arch2 = CheckAllowedArchitecture(second.Arch); @@ -275,14 +215,14 @@ namespace AppInstaller::CLI::Workflow << " , Preferred InstallerTypes: " << m_preferenceAsString); } - static std::unique_ptr Create(const Execution::Args& args) + static std::unique_ptr Create(const ManifestComparator::Options& options) { std::vector preference; std::vector requirement; - if (args.Contains(Execution::Args::Type::InstallerType)) + if (options.RequestedInstallerType) { - requirement.emplace_back(Manifest::ConvertToInstallerTypeEnum(std::string(args.GetArg(Execution::Args::Type::InstallerType)))); + requirement.emplace_back(options.RequestedInstallerType.value()); } else { @@ -300,7 +240,7 @@ namespace AppInstaller::CLI::Workflow } } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "InstallerType ["; result += InstallerTypeToString(installer.EffectiveInstallerType()); @@ -309,7 +249,7 @@ namespace AppInstaller::CLI::Workflow return result; } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { if (!m_requirement.empty()) { @@ -328,14 +268,14 @@ namespace AppInstaller::CLI::Workflow } } - details::ComparisonResult IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) override + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override { if (m_preference.empty()) { return details::ComparisonResult::Negative; } - for (Manifest::InstallerTypeEnum installerTypePreference : m_preference) + for (InstallerTypeEnum installerTypePreference : m_preference) { bool isFirstInstallerTypePreferred = first.EffectiveInstallerType() == installerTypePreference || @@ -373,16 +313,15 @@ namespace AppInstaller::CLI::Workflow struct InstalledTypeFilter : public details::FilterField { - InstalledTypeFilter(Manifest::InstallerTypeEnum installedType) : + InstalledTypeFilter(InstallerTypeEnum installedType) : details::FilterField("Installed Type"), m_installedType(installedType) {} - static std::unique_ptr Create(const Repository::IPackageVersion::Metadata& installationMetadata) + static std::unique_ptr Create(const ManifestComparator::Options& options) { - auto installerTypeItr = installationMetadata.find(Repository::PackageVersionMetadata::InstalledType); - if (installerTypeItr != installationMetadata.end()) + if (options.CurrentlyInstalledType) { - Manifest::InstallerTypeEnum installedType = Manifest::ConvertToInstallerTypeEnum(installerTypeItr->second); - if (installedType != Manifest::InstallerTypeEnum::Unknown) + InstallerTypeEnum installedType = options.CurrentlyInstalledType.value(); + if (installedType != InstallerTypeEnum::Unknown) { return std::make_unique(installedType); } @@ -391,20 +330,20 @@ namespace AppInstaller::CLI::Workflow return {}; } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { return IsInstallerCompatibleWith(installer, m_installedType) ? InapplicabilityFlags::None : InapplicabilityFlags::InstalledType; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { - std::string result = "Installed package type '" + std::string{ Manifest::InstallerTypeToString(m_installedType) } + - "' is not compatible with installer type " + std::string{ Manifest::InstallerTypeToString(installer.EffectiveInstallerType()) }; + std::string result = "Installed package type '" + std::string{ InstallerTypeToString(m_installedType) } + + "' is not compatible with installer type " + std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }; std::string arpInstallerTypes; for (const auto& entry : installer.AppsAndFeaturesEntries) { - arpInstallerTypes += " " + std::string{ Manifest::InstallerTypeToString(entry.InstallerType) }; + arpInstallerTypes += " " + std::string{ InstallerTypeToString(entry.InstallerType) }; } if (!arpInstallerTypes.empty()) @@ -417,9 +356,9 @@ namespace AppInstaller::CLI::Workflow private: // The installer is compatible if it's type or any of its ARP entries' type matches the installed type - static bool IsInstallerCompatibleWith(const Manifest::ManifestInstaller& installer, Manifest::InstallerTypeEnum type) + static bool IsInstallerCompatibleWith(const ManifestInstaller& installer, InstallerTypeEnum type) { - if (Manifest::IsInstallerTypeCompatible(installer.EffectiveInstallerType(), type)) + if (IsInstallerTypeCompatible(installer.EffectiveInstallerType(), type)) { return true; } @@ -427,7 +366,7 @@ namespace AppInstaller::CLI::Workflow auto itr = std::find_if( installer.AppsAndFeaturesEntries.begin(), installer.AppsAndFeaturesEntries.end(), - [=](AppsAndFeaturesEntry arpEntry) { return Manifest::IsInstallerTypeCompatible(arpEntry.InstallerType, type); }); + [=](AppsAndFeaturesEntry arpEntry) { return IsInstallerTypeCompatible(arpEntry.InstallerType, type); }); if (itr != installer.AppsAndFeaturesEntries.end()) { return true; @@ -436,22 +375,21 @@ namespace AppInstaller::CLI::Workflow return false; } - Manifest::InstallerTypeEnum m_installedType; + InstallerTypeEnum m_installedType; }; struct InstalledScopeFilter : public details::FilterField { - InstalledScopeFilter(Manifest::ScopeEnum requirement) : + InstalledScopeFilter(ScopeEnum requirement) : details::FilterField("Installed Scope"), m_requirement(requirement) {} - static std::unique_ptr Create(const Repository::IPackageVersion::Metadata& installationMetadata) + static std::unique_ptr Create(const ManifestComparator::Options& options) { // Check for an existing install and require a matching scope. - auto installerScopeItr = installationMetadata.find(Repository::PackageVersionMetadata::InstalledScope); - if (installerScopeItr != installationMetadata.end()) + if (options.CurrentlyInstalledScope) { - Manifest::ScopeEnum installedScope = Manifest::ConvertToScopeEnum(installerScopeItr->second); - if (installedScope != Manifest::ScopeEnum::Unknown) + ScopeEnum installedScope = options.CurrentlyInstalledScope.value(); + if (installedScope != ScopeEnum::Unknown) { return std::make_unique(installedScope); } @@ -460,10 +398,10 @@ namespace AppInstaller::CLI::Workflow return {}; } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. - if (installer.Scope == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) + if (installer.Scope == ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) { return InapplicabilityFlags::None; } @@ -471,36 +409,35 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::InstalledScope; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "Installer scope does not match currently installed scope: "; - result += Manifest::ScopeToString(installer.Scope); + result += ScopeToString(installer.Scope); result += " != "; - result += Manifest::ScopeToString(m_requirement); + result += ScopeToString(m_requirement); return result; } private: - Manifest::ScopeEnum m_requirement; + ScopeEnum m_requirement; }; struct ScopeComparator : public details::ComparisonField { - ScopeComparator(Manifest::ScopeEnum preference, Manifest::ScopeEnum requirement, bool allowUnknownInAdditionToRequired) : + ScopeComparator(ScopeEnum preference, ScopeEnum requirement, bool allowUnknownInAdditionToRequired) : details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement), m_allowUnknownInAdditionToRequired(allowUnknownInAdditionToRequired) {} - static std::unique_ptr Create(const Execution::Context& context) + static std::unique_ptr Create(const ManifestComparator::Options& options) { // Preference will always come from settings - Manifest::ScopeEnum preference = Settings::User().Get(); + ScopeEnum preference = Settings::User().Get(); // Requirement may come from args or settings; args overrides settings. - Manifest::ScopeEnum requirement = Manifest::ScopeEnum::Unknown; + ScopeEnum requirement = ScopeEnum::Unknown; - const auto& args = context.Args; - if (args.Contains(Execution::Args::Type::InstallScope)) + if (options.RequestedInstallerScope) { - requirement = Manifest::ConvertToScopeEnum(args.GetArg(Execution::Args::Type::InstallScope)); + requirement = options.RequestedInstallerScope.value(); } else { @@ -508,18 +445,18 @@ namespace AppInstaller::CLI::Workflow } bool allowUnknownInAdditionToRequired = false; - if (context.Contains(Execution::Data::AllowUnknownScope)) + if (options.AllowUnknownScope) { - allowUnknownInAdditionToRequired = context.Get(); + allowUnknownInAdditionToRequired = options.AllowUnknownScope.value(); // Force the required type to be preferred over Unknown - if (requirement != Manifest::ScopeEnum::Unknown) + if (requirement != ScopeEnum::Unknown) { preference = requirement; } } - if (preference != Manifest::ScopeEnum::Unknown || requirement != Manifest::ScopeEnum::Unknown) + if (preference != ScopeEnum::Unknown || requirement != ScopeEnum::Unknown) { return std::make_unique(preference, requirement, allowUnknownInAdditionToRequired); } @@ -529,16 +466,16 @@ namespace AppInstaller::CLI::Workflow } } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { // 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 || + if (m_requirement == ScopeEnum::Unknown || installer.Scope == m_requirement || - (installer.Scope == Manifest::ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) || + (installer.Scope == ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) { return InapplicabilityFlags::None; @@ -547,29 +484,29 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::Scope; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "Installer scope does not match required scope: "; - result += Manifest::ScopeToString(installer.Scope); + result += ScopeToString(installer.Scope); result += " != "; - result += Manifest::ScopeToString(m_requirement); + result += ScopeToString(m_requirement); return result; } - details::ComparisonResult IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) override + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override { - if (m_preference != Manifest::ScopeEnum::Unknown && first.Scope == m_preference && second.Scope != m_preference) + if (m_preference != ScopeEnum::Unknown && first.Scope == m_preference && second.Scope != m_preference) { // When the second input is unknown, this is a weak result. If it is not (and therefore the opposite of the preference), this is strong. - return (second.Scope == Manifest::ScopeEnum::Unknown ? details::ComparisonResult::WeakPositive : details::ComparisonResult::StrongPositive); + return (second.Scope == ScopeEnum::Unknown ? details::ComparisonResult::WeakPositive : details::ComparisonResult::StrongPositive); } return details::ComparisonResult::Negative; } private: - Manifest::ScopeEnum m_preference; - Manifest::ScopeEnum m_requirement; + ScopeEnum m_preference; + ScopeEnum m_requirement; bool m_allowUnknownInAdditionToRequired; }; @@ -586,28 +523,26 @@ namespace AppInstaller::CLI::Workflow << " , IsInstalledLocale: " << m_isInstalledLocale); } - static std::unique_ptr Create(const Execution::Args& args, const Repository::IPackageVersion::Metadata& metadata) + static std::unique_ptr Create(const ManifestComparator::Options& options) { std::vector preference; std::vector requirement; // This is for installed locale case, where the locale is a preference but requires at least compatible match. bool isInstalledLocale = false; - auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentLocale); - auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledLocale); // Requirement may come from args, previous user intent or settings; args overrides previous user intent then settings. - if (args.Contains(Execution::Args::Type::Locale)) + if (options.RequestedInstallerLocale) { - requirement.emplace_back(args.GetArg(Execution::Args::Type::Locale)); + requirement.emplace_back(options.RequestedInstallerLocale.value()); } - else if (userIntentItr != metadata.end()) + else if (options.PreviousUserIntentLocale) { - requirement.emplace_back(userIntentItr->second); + requirement.emplace_back(options.PreviousUserIntentLocale.value()); isInstalledLocale = true; } else { - if (installedItr == metadata.end()) + if (!options.CurrentlyInstalledLocale) { // If it's an upgrade of previous package, no need to set requirements from settings // as previous installed locale will be used later. @@ -618,9 +553,9 @@ namespace AppInstaller::CLI::Workflow // Preference will come from previous installed locale, winget settings or Preferred Languages settings. // Previous installed locale goes first, then winget settings, then Preferred Languages settings. // Previous installed locale also requires at least compatible locale match. - if (installedItr != metadata.end()) + if (options.CurrentlyInstalledLocale) { - preference.emplace_back(installedItr->second); + preference.emplace_back(options.CurrentlyInstalledLocale.value()); isInstalledLocale = true; } else @@ -642,7 +577,7 @@ namespace AppInstaller::CLI::Workflow } } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { InapplicabilityFlags inapplicableFlag = m_isInstalledLocale ? InapplicabilityFlags::InstalledLocale : InapplicabilityFlags::Locale; @@ -680,7 +615,7 @@ namespace AppInstaller::CLI::Workflow } } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "Installer locale does not match required locale: "; result += installer.Locale; @@ -691,7 +626,7 @@ namespace AppInstaller::CLI::Workflow return result; } - details::ComparisonResult IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) override + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override { if (m_preference.empty()) { @@ -735,7 +670,7 @@ namespace AppInstaller::CLI::Workflow return std::make_unique(Runtime::GetOSRegion()); } - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { // If both allowed and excluded lists are provided, we only need to check the allowed markets. if (!installer.Markets.AllowedMarkets.empty()) @@ -758,7 +693,7 @@ namespace AppInstaller::CLI::Workflow return InapplicabilityFlags::None; } - std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) override + std::string ExplainInapplicable(const ManifestInstaller& installer) override { std::string result = "Current market '" + m_market + "' does not match installer markets." + " Allowed markets: " + Utility::ConvertContainerToString(installer.Markets.AllowedMarkets) + @@ -779,18 +714,18 @@ namespace AppInstaller::CLI::Workflow }; } - ManifestComparator::ManifestComparator(const Execution::Context& context, const Repository::IPackageVersion::Metadata& installationMetadata) + ManifestComparator::ManifestComparator(const Options& options) { // Filters based on installer's MinOSVersion AddFilter(std::make_unique()); // Filters out portable installers if they are not supported by the system AddFilter(std::make_unique()); // Filters based on the scope of a currently installed package - AddFilter(InstalledScopeFilter::Create(installationMetadata)); + AddFilter(InstalledScopeFilter::Create(options)); // Filters based on the market region of the system AddFilter(MarketFilter::Create()); // Filters based on the installer type compatability, including with AppsAndFeaturesEntry declarations - AddFilter(InstalledTypeFilter::Create(installationMetadata)); + AddFilter(InstalledTypeFilter::Create(options)); // Filter order is not important, but comparison order determines priority. // Note that all comparators are also filters and their comparison function will only be called on @@ -809,27 +744,27 @@ namespace AppInstaller::CLI::Workflow // Only applies when preference exists: // Strong if first is compatible and better match than second // Weak if first is unknown and second is not - AddComparator(LocaleComparator::Create(context.Args, installationMetadata)); + AddComparator(LocaleComparator::Create(options)); // Filters only if a requirement is present and it cannot be satisfied by the installer (including installer types that we can control scope in code) // Only applies when preference exists: // Strong if first matches preference and second does not and is not Unknown // Weak if first matches preference and second is Unknown - AddComparator(ScopeComparator::Create(context)); + AddComparator(ScopeComparator::Create(options)); // Filters architectures out that are not supported or are not in the preferences/requirements/inputs. // Strong if first equals the earliest architecture in the allowed list and second does not [default means the system architecture] // Weak if first is better match for system architecture than second - AddComparator(MachineArchitectureComparator::Create(context, installationMetadata)); + AddComparator(MachineArchitectureComparator::Create(options)); // Filters installer types out that are not in preferences or requirements. // Only applies when preference exists: // Weak if first is in preference list and second is not - AddComparator(InstallerTypeComparator::Create(context.Args)); + AddComparator(InstallerTypeComparator::Create(options)); } - InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest::Manifest& manifest) + InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest& manifest) { AICLI_LOG(CLI, Verbose, << "Starting installer selection."); - const Manifest::ManifestInstaller* result = nullptr; + const ManifestInstaller* result = nullptr; std::vector inapplicabilitiesInstallers; for (const auto& installer : manifest.Installers) @@ -857,7 +792,7 @@ namespace AppInstaller::CLI::Workflow return { *result, std::move(inapplicabilitiesInstallers) }; } - InapplicabilityFlags ManifestComparator::IsApplicable(const Manifest::ManifestInstaller& installer) + InapplicabilityFlags ManifestComparator::IsApplicable(const ManifestInstaller& installer) { InapplicabilityFlags inapplicabilityResult = InapplicabilityFlags::None; @@ -875,8 +810,8 @@ namespace AppInstaller::CLI::Workflow } bool ManifestComparator::IsFirstBetter( - const Manifest::ManifestInstaller& first, - const Manifest::ManifestInstaller& second) + const ManifestInstaller& first, + const ManifestInstaller& second) { // The priority will still be used as a tie-break between weak results. std::optional firstWeakComparator; diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h similarity index 57% rename from src/AppInstallerCLICore/Workflows/ManifestComparator.h rename to src/AppInstallerCommonCore/Public/winget/ManifestComparator.h index 68ad24c4bd..96f199efcc 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h @@ -1,18 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "ExecutionContext.h" -#include "ExecutionArgs.h" #include -#include - #include +#include #include #include +#include #include -namespace AppInstaller::CLI::Workflow +namespace AppInstaller::Manifest { // Flags to indicate why an installer was not applicable enum class InapplicabilityFlags : int @@ -43,11 +41,11 @@ namespace AppInstaller::CLI::Workflow std::string_view Name() const { return m_name; } // Determines if the installer is applicable based on this field alone. - virtual InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) = 0; + virtual InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; // Explains why the filter regarded this installer as inapplicable. // Will only be called when IsApplicable returns false. - virtual std::string ExplainInapplicable(const Manifest::ManifestInstaller& installer) = 0; + virtual std::string ExplainInapplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; private: std::string_view m_name; @@ -73,31 +71,59 @@ namespace AppInstaller::CLI::Workflow virtual ~ComparisonField() = default; // Determines if the first installer is a better choice based on this field alone. - virtual ComparisonResult IsFirstBetter(const Manifest::ManifestInstaller& first, const Manifest::ManifestInstaller& second) = 0; + virtual ComparisonResult IsFirstBetter(const AppInstaller::Manifest::ManifestInstaller& first, const AppInstaller::Manifest::ManifestInstaller& second) = 0; }; } struct InstallerAndInapplicabilities { - std::optional installer; + std::optional installer; std::vector inapplicabilitiesInstaller; }; // Class in charge of comparing manifest entries struct ManifestComparator { - ManifestComparator(const Execution::Context& context, const Repository::IPackageVersion::Metadata& installationMetadata); + // Options that affect the comparisons. + struct Options + { + // The allowed architectures and a value indicating whether to perform applicability checks. + std::vector AllowedArchitectures; + bool SkipApplicabilityCheck = false; + + // The requested installer type. + std::optional RequestedInstallerType; + + // The currently installed type. + std::optional CurrentlyInstalledType; + + // The requested installer scope and a value indicating whether and unknown scope is acceptable. + std::optional RequestedInstallerScope; + std::optional AllowUnknownScope; + + // The currently installed scope. + std::optional CurrentlyInstalledScope; + + // The requested installer locale. + std::optional RequestedInstallerLocale; + + // Get the currently installed locale intent and value. + std::optional PreviousUserIntentLocale; + std::optional CurrentlyInstalledLocale; + }; + + ManifestComparator(const Options& options); // Gets the best installer from the manifest, if at least one is applicable. - InstallerAndInapplicabilities GetPreferredInstaller(const Manifest::Manifest& manifest); + InstallerAndInapplicabilities GetPreferredInstaller(const AppInstaller::Manifest::Manifest& manifest); // Determines if an installer is applicable. - InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer); + InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer); // Determines if the first installer is a better choice. bool IsFirstBetter( - const Manifest::ManifestInstaller& first, - const Manifest::ManifestInstaller& second); + const AppInstaller::Manifest::ManifestInstaller& first, + const AppInstaller::Manifest::ManifestInstaller& second); private: void AddFilter(std::unique_ptr&& filter); @@ -108,4 +134,4 @@ namespace AppInstaller::CLI::Workflow std::vector m_comparators; }; -} \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h b/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h new file mode 100644 index 0000000000..2208590778 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Logging +{ + // Sends logs to the stderr stream. + struct StdErrLogger : ILogger + { + StdErrLogger() = default; + + ~StdErrLogger() = default; + + // ILogger + std::string GetName() const override; + + void Write(Channel channel, Level level, std::string_view message) noexcept override; + + void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; + + // Adds OutputDebugStringLogger to the current Log + static void Add(); + + // Removes OutputDebugStringLogger from the current Log + static void Remove(); + + private: + Level m_level = Level::Error; + }; +} diff --git a/src/AppInstallerCommonCore/StdErrLogger.cpp b/src/AppInstallerCommonCore/StdErrLogger.cpp new file mode 100644 index 0000000000..9a65ef6d3e --- /dev/null +++ b/src/AppInstallerCommonCore/StdErrLogger.cpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/StdErrLogger.h" + +namespace AppInstaller::Logging +{ + namespace + { + static constexpr std::string_view s_StdErrLoggerName = "StdErrLogger"; + } + + std::string StdErrLogger::GetName() const + { + return std::string{ s_StdErrLoggerName }; + } + + void StdErrLogger::Write(Channel channel, Level level, std::string_view message) noexcept try + { + if (level >= m_level) + { + std::cerr << "[" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; + } + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void StdErrLogger::WriteDirect(Channel, Level level, std::string_view message) noexcept try + { + if (level >= m_level) + { + std::cerr.write(message.data(), static_cast(message.size())); + } + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void StdErrLogger::Add() + { + Log().AddLogger(std::make_unique()); + } + + void StdErrLogger::Remove() + { + Log().RemoveLogger(std::string{ s_StdErrLoggerName }); + } +} diff --git a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp index 7ecd8e17ea..77cd3c7ac7 100644 --- a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp +++ b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Public/winget/PackageVersionSelection.h" #include "Public/winget/RepositorySource.h" +#include "Public/winget/PinningData.h" namespace AppInstaller::Repository @@ -154,4 +155,125 @@ namespace AppInstaller::Repository { return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); } + + LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite) + { + using namespace AppInstaller::Pinning; + + LatestApplicableVersionData result; + + auto installedVersion = AppInstaller::Repository::GetInstalledVersion(composite); + auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(composite, installedVersion); + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); + + AppInstaller::Manifest::ManifestComparator::Options options; + if (installedVersion) + { + GetManifestComparatorOptionsFromMetadata(options, installedVersion->GetMetadata()); + } + AppInstaller::Manifest::ManifestComparator manifestComparator{ options }; + + auto availableVersionKeys = availableVersions->GetVersionKeys(); + for (const auto& availableVersionKey : availableVersionKeys) + { + auto availableVersion = availableVersions->GetVersion(availableVersionKey); + + if (installedVersion && !evaluator.IsUpdate(availableVersion)) + { + // Version too low or different channel for upgrade + continue; + } + + if (evaluator.EvaluatePinType(availableVersion) != AppInstaller::Pinning::PinType::Unknown) + { + // Pinned + continue; + } + + auto manifestComparatorResult = manifestComparator.GetPreferredInstaller(availableVersion->GetManifest()); + if (!manifestComparatorResult.installer.has_value()) + { + // No applicable installer + continue; + } + + result.LatestApplicableVersion = availableVersion; + if (installedVersion) + { + result.UpdateAvailable = true; + } + + break; + } + + return result; + } + + void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures) + { + auto installedTypeItr = metadata.find(Repository::PackageVersionMetadata::InstalledType); + if (installedTypeItr != metadata.end()) + { + options.CurrentlyInstalledType = Manifest::ConvertToInstallerTypeEnum(installedTypeItr->second); + } + + auto installedScopeItr = metadata.find(Repository::PackageVersionMetadata::InstalledScope); + if (installedScopeItr != metadata.end()) + { + options.CurrentlyInstalledScope = Manifest::ConvertToScopeEnum(installedScopeItr->second); + } + + auto userIntentLocaleItr = metadata.find(Repository::PackageVersionMetadata::UserIntentLocale); + if (userIntentLocaleItr != metadata.end()) + { + options.PreviousUserIntentLocale = userIntentLocaleItr->second; + } + + auto installedLocaleItr = metadata.find(Repository::PackageVersionMetadata::InstalledLocale); + if (installedLocaleItr != metadata.end()) + { + options.CurrentlyInstalledLocale = installedLocaleItr->second; + } + + if (includeAllowedArchitectures) + { + auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); + if (userIntentItr != metadata.end()) + { + // For upgrade, user intent from previous install is considered requirement + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(userIntentItr->second)); + } + else + { + auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledArchitecture); + if (installedItr != metadata.end()) + { + // For upgrade, previous installed architecture should be considered first preference and is always allowed. + // Then check settings requirements and preferences. + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(installedItr->second)); + } + + std::vector requiredArchitectures = Settings::User().Get(); + std::vector optionalArchitectures = Settings::User().Get(); + + if (!requiredArchitectures.empty()) + { + // Required architecture list from settings if applicable + options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), requiredArchitectures.begin(), requiredArchitectures.end()); + } + else + { + // Preferred architecture list from settings if applicable, add Unknown to indicate allowing remaining applicable + if (!optionalArchitectures.empty()) + { + options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), optionalArchitectures.begin(), optionalArchitectures.end()); + } + + options.AllowedArchitectures.emplace_back(Utility::Architecture::Unknown); + } + } + } + } } diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h index e9a5b5edbd..0497646015 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include +#include namespace AppInstaller::Repository @@ -25,4 +26,16 @@ namespace AppInstaller::Repository // Gets the available IPackage corresponding to the given source identifier. std::shared_ptr GetAvailablePackageFromSource(const std::shared_ptr& composite, const std::string_view sourceIdentifier); + + struct LatestApplicableVersionData + { + std::shared_ptr LatestApplicableVersion; + bool UpdateAvailable = false; + }; + + // Determines the default install version and whether an update is available. + LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite); + + // Fills the options from the given metadata, optionally including the allowed architectures. + void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures = true); } diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp index 7ead74755d..a5de126464 100644 --- a/src/AppInstallerTestExeInstaller/main.cpp +++ b/src/AppInstallerTestExeInstaller/main.cpp @@ -200,7 +200,7 @@ void WriteToUninstallRegistry( } // Set ModifyPath Property Value - if (LONG res = RegSetValueEx(hkey, L"ModifyPath", NULL, REG_EXPAND_SZ, (LPBYTE)modifyPath.c_str(), (DWORD)(modifyPath.wstring().length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + if (LONG res = RegSetValueEx(hkey, L"ModifyPath", NULL, REG_EXPAND_SZ, (LPBYTE)modifyPathString.c_str(), (DWORD)(modifyPathString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) { out << "Failed to write ModifyPath value. Error Code: " << res << std::endl; } diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index f029c5a87b..e83eaa9975 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -11,7 +11,6 @@ #include "PackageInstallerInstalledStatus.h" #include "CheckInstalledStatusResult.h" #include -#include #include #include #include @@ -72,60 +71,17 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::call_once(m_latestApplicableVersionOnceFlag, [&]() { - using namespace AppInstaller::Pinning; + auto data = AppInstaller::Repository::GetLatestApplicableVersion(m_package); - auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(m_package); - auto installedVersion = AppInstaller::Repository::GetInstalledVersion(m_package); + m_updateAvailable = data.UpdateAvailable; - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); - - AppInstaller::CLI::Execution::COMContext context; - AppInstaller::Repository::IPackageVersion::Metadata installationMetadata = - installedVersion ? installedVersion->GetMetadata() : AppInstaller::Repository::IPackageVersion::Metadata{}; - AppInstaller::CLI::Workflow::ManifestComparator manifestComparator{ context, installationMetadata }; - - std::shared_ptr latestApplicableVersion; - auto availableVersionKeys = availableVersions->GetVersionKeys(); - for (const auto& availableVersionKey : availableVersionKeys) - { - auto availableVersion = availableVersions->GetVersion(availableVersionKey); - - if (installedVersion && !evaluator.IsUpdate(availableVersion)) - { - // Version too low or different channel for upgrade - continue; - } - - if (evaluator.EvaluatePinType(availableVersion) != AppInstaller::Pinning::PinType::Unknown) - { - // Pinned - continue; - } - - auto manifestComparatorResult = manifestComparator.GetPreferredInstaller(availableVersion->GetManifest()); - if (!manifestComparatorResult.installer.has_value()) - { - // No applicable installer - continue; - } - - latestApplicableVersion = availableVersion; - if (installedVersion) - { - m_updateAvailable = true; - } - - break; - } - - if (latestApplicableVersion) + if (data.LatestApplicableVersion) { // DefaultInstallVersion hasn't been created yet, create and populate it. // DefaultInstallVersion is the latest applicable version of the internal package object. auto latestVersionImpl = winrt::make_self>(); - latestVersionImpl->Initialize(std::move(latestApplicableVersion)); + latestVersionImpl->Initialize(std::move(data.LatestApplicableVersion)); m_latestApplicableVersion = *latestVersionImpl; } }); @@ -141,7 +97,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation } else if (AvailableVersions().Size() > 0) { - return GetPackageVersionInfo(AvailableVersions().GetAt(0)); + return GetPackageVersionInfo(m_availableVersions.GetAt(0)); } else { diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp index 5366f6d21b..6d2d245c57 100644 --- a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp +++ b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp @@ -12,7 +12,7 @@ #include "CatalogPackageMetadata.h" #include "ComContext.h" #include "Workflows/WorkflowBase.h" -#include "Workflows/ManifestComparator.h" +#include #include "winget/RepositorySearch.h" #include "AppInstallerVersions.h" #include "Converters.h" @@ -26,6 +26,42 @@ namespace winrt::Microsoft::Management::Deployment::implementation { + namespace + { + // Do the same thing as PopulateContextFromInstallOptions but without all the string conversions. + AppInstaller::Manifest::ManifestComparator::Options GetComparatorOptionsFromInstallOptions(InstallOptions options) + { + AppInstaller::Manifest::ManifestComparator::Options result; + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + result.RequestedInstallerType = installerType; + } + + auto manifestScope = GetManifestScope(options.PackageInstallScope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + result.RequestedInstallerScope = manifestScope.first; + result.AllowUnknownScope = manifestScope.second; + } + + if (options.AllowedArchitectures().Size() != 0) + { + for (auto architecture : options.AllowedArchitectures()) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + result.AllowedArchitectures.push_back(convertedArchitecture.value()); + } + } + } + + return result; + } + } + void PackageVersionInfo::Initialize(std::shared_ptr<::AppInstaller::Repository::IPackageVersion> packageVersion) { m_packageVersion = std::move(packageVersion); @@ -138,20 +174,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation } bool PackageVersionInfo::HasApplicableInstaller(InstallOptions options) { - AppInstaller::CLI::Execution::COMContext context; - PopulateContextFromInstallOptions(&context, options); - AppInstaller::Repository::IPackageVersion::Metadata installationMetadata; - AppInstaller::CLI::Workflow::ManifestComparator manifestComparator{ context, installationMetadata }; + AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); auto result = manifestComparator.GetPreferredInstaller(manifest); return result.installer.has_value(); } winrt::Microsoft::Management::Deployment::PackageInstallerInfo PackageVersionInfo::GetApplicableInstaller(InstallOptions options) { - AppInstaller::CLI::Execution::COMContext context; - PopulateContextFromInstallOptions(&context, options); - AppInstaller::Repository::IPackageVersion::Metadata installationMetadata; - AppInstaller::CLI::Workflow::ManifestComparator manifestComparator{ context, installationMetadata }; + AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); auto result = manifestComparator.GetPreferredInstaller(manifest);