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);